Decode the protobuf wire format in Gleam!
3

Configure Feed

Select the types of activity you want to include in your feed.

Make all values lists to handle duplicate fields

Gavin Morrow (Sep 22, 2025, 8:57 PM EDT) 06b4df7d 76e9ae75

+37 -24
+23 -4
src/decoders.gleam
··· 1 1 import gleam/bit_array 2 2 import gleam/dynamic/decode.{type Decoder} 3 + import gleam/list 3 4 4 5 import internal/util 5 6 import protobuf_decode_gleam.{parse} 6 7 8 + fn single(of decoder: Decoder(t)) -> Decoder(t) { 9 + use values <- decode.then(decode.list(of: decoder)) 10 + 11 + // I choose to use assert instead of `decode.failure` because the decode api 12 + // requires passing a name and default value, which is clunky and would've 13 + // added two parameters to this function. 14 + // The only times this could fail are programmer error on my part or using 15 + // this decoder on data not produced in a compatible way with this library. 16 + let assert Ok(value) = list.last(values) 17 + value |> decode.success 18 + } 19 + 20 + // Allows the decoders to be used for either single or repeated fields 21 + fn single_or_raw(decoder: Decoder(t)) -> Decoder(t) { 22 + decode.one_of(decoder, or: [single(of: decoder)]) 23 + } 24 + 7 25 pub fn protobuf( 26 + // Passed as a function so recursive decoders are easier 8 27 using decoder: fn() -> Decoder(t), 9 28 named name: String, 10 29 default default: t, 11 30 ) -> Decoder(t) { 12 - use bits <- decode.then(decode.bit_array) 31 + use bits <- decode.then(single_or_raw(decode.bit_array)) 13 32 14 33 let value = parse(from: bits, using: decoder()) 15 34 case value { ··· 19 38 } 20 39 21 40 pub fn uint() -> Decoder(Int) { 22 - use bits <- decode.then(decode.bit_array) 41 + use bits <- decode.then(single_or_raw(decode.bit_array)) 23 42 util.bit_array_to_uint(bits) |> decode.success 24 43 } 25 44 26 45 pub fn fixed(size: Int) -> Decoder(Int) { 27 - use bits <- decode.then(decode.bit_array) 46 + use bits <- decode.then(single_or_raw(decode.bit_array)) 28 47 let assert <<num:unsigned-little-size(size)>> = bits 29 48 num |> decode.success 30 49 } 31 50 32 51 pub fn string() -> Decoder(String) { 33 - use bits <- decode.then(decode.bit_array) 52 + use bits <- decode.then(single_or_raw(decode.bit_array)) 34 53 35 54 let str = bit_array.to_string(bits) 36 55 case str {
+14 -20
src/protobuf_decode_gleam.gleam
··· 50 50 51 51 fn repeated_to_list(fields: List(Field)) -> dict.Dict(Dynamic, Dynamic) { 52 52 let fields = { 53 - use fields, Field(key:, value:) <- list.fold(over: fields, from: dict.new()) 53 + // Every field is a list of values for two reasons: 54 + // a) expanded repeated values are encoded as repeated fields 55 + // b) if a non-repeating field is defined twice, then the last value should 56 + // be considered the correct one. since the parser doesn't know which 57 + // fields are repeating, it parses all fields as a list and then the 58 + // decoders will handle choosing which value(s) to keep. 59 + let acc: dict.Dict(Dynamic, List(Dynamic)) = dict.new() 60 + use fields, Field(key:, value:) <- list.fold(over: fields, from: acc) 54 61 55 - let value = case dict.get(fields, key) { 56 - Ok(existing_value) -> { 57 - // If the existing value is already a list, append to it 58 - // Otherwise, turn it into a list 59 - case decode.run(existing_value, decode.list(of: decode.dynamic)) { 60 - // NOTE: this puts the values in reverse order 61 - Ok(existing_values) -> [value, ..existing_values] |> dynamic.list 62 - Error(_) -> [value, existing_value] |> dynamic.list 63 - } 64 - } 65 - // If there is no existing value, don't change the value 66 - Error(Nil) -> value 67 - } 68 - dict.insert(into: fields, for: key, insert: value) 62 + use existing_values <- dict.upsert(in: fields, update: key) 63 + let existing_values = option.unwrap(existing_values, or: []) 64 + [value, ..existing_values] 69 65 } 70 66 71 67 // The repeated values must be in order, so un-reverse them here 72 - use _key, field <- dict.map_values(in: fields) 73 - case decode.run(field, decode.list(of: decode.dynamic)) { 74 - Ok(values) -> list.reverse(values) |> dynamic.list 75 - Error(_) -> field 76 - } 68 + dict.map_values(in: fields, with: fn(_key, field) { 69 + field |> list.reverse |> dynamic.list 70 + }) 77 71 } 78 72 79 73 type DecodeResult(t) =