From d8184ab6065eeb2d1ba78c16f9f8d57d484c485c Mon Sep 17 00:00:00 2001 From: Gavin Morrow Date: Sun, 21 Sep 2025 20:53:24 -0400 Subject: [PATCH] Handle repeated fields --- src/protobuf_decode_gleam.gleam | 36 ++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/protobuf_decode_gleam.gleam b/src/protobuf_decode_gleam.gleam index 93f80b0..69871ff 100644 --- a/src/protobuf_decode_gleam.gleam +++ b/src/protobuf_decode_gleam.gleam @@ -1,4 +1,5 @@ import gleam/bit_array +import gleam/dict import gleam/dynamic.{type Dynamic} import gleam/dynamic/decode.{type Decoder} import gleam/int @@ -29,7 +30,8 @@ fn read_fields(bits: BitArray, acc: List(Field)) -> Result(Dynamic, ParseError) case bits { <<>> -> acc - |> list.map(field_as_pair) + |> repeated_to_list + |> dict.to_list |> dynamic.properties |> Ok bits -> { @@ -39,6 +41,34 @@ fn read_fields(bits: BitArray, acc: List(Field)) -> Result(Dynamic, ParseError) } } +fn repeated_to_list(fields: List(Field)) -> dict.Dict(Dynamic, Dynamic) { + let fields = { + use fields, Field(key:, value:) <- list.fold(over: fields, from: dict.new()) + + let value = case dict.get(fields, key) { + Ok(existing_value) -> { + // If the existing value is already a list, append to it + // Otherwise, turn it into a list + case decode.run(existing_value, decode.list(of: decode.dynamic)) { + // NOTE: this puts the values in reverse order + Ok(existing_values) -> [value, ..existing_values] |> dynamic.list + Error(_) -> [value, existing_value] |> dynamic.list + } + } + // If there is no existing value, don't change the value + Error(Nil) -> value + } + dict.insert(into: fields, for: key, insert: value) + } + + // The repeated values must be in order, so un-reverse them here + use _key, field <- dict.map_values(in: fields) + case decode.run(field, decode.list(of: decode.dynamic)) { + Ok(values) -> list.reverse(values) |> dynamic.list + Error(_) -> field + } +} + type DecodeResult(t) = Result(t, ParseError) @@ -55,10 +85,6 @@ type Field { Field(key: Dynamic, value: Dynamic) } -fn field_as_pair(field: Field) { - #(field.key, field.value) -} - fn wire_type_read_fn(ty: WireType) -> fn(BitArray) -> ValueResult { case ty { wire_type.VarInt -> read_varint -- 2.51.2