diff --git a/src/protobuf_decode_gleam.gleam b/src/protobuf_decode_gleam.gleam index a6861be..aa74e24 100644 --- a/src/protobuf_decode_gleam.gleam +++ b/src/protobuf_decode_gleam.gleam @@ -2,11 +2,11 @@ import gleam/bit_array import gleam/dynamic.{type Dynamic} import gleam/dynamic/decode.{type Decoder} import gleam/int +import gleam/list import gleam/option.{type Option, None, Some} -import gleam/pair import gleam/result -pub fn decode( +pub fn parse( from bits: BitArray, using decoder: Decoder(t), ) -> Result(t, DecodeError) { @@ -14,14 +14,14 @@ pub fn decode( decode.run(data, decoder) |> result.map_error(UnableToDecode) } -pub fn decoder( +pub fn decode_protobuf( using decoder: fn() -> Decoder(t), named name: String, default default: t, ) -> Decoder(t) { use bits <- decode.then(decode.bit_array) - let value = decode(from: bits, using: decoder()) + let value = parse(from: bits, using: decoder()) case value { Ok(value) -> decode.success(value) Error(_) -> decode.failure(default, name) @@ -45,31 +45,59 @@ fn parse_wire_type(i: Int) -> Option(WireType) { } } +fn wire_type_read_fn(ty: WireType) -> fn(BitArray) -> ValueResult { + case ty { + VarInt -> read_varint + I64 -> read_fixed(64) + Len -> read_len + I32 -> read_fixed(32) + } +} + pub type DecodeError { UnknownWireType(Int) InvalidVarInt(leftover_bits: BitArray, acc: BitArray) InvalidFixed(size: Int, bits: BitArray) - UnableToDecode(List(decode.DecodeError)) InvalidLen(len: Int, value_bits: BitArray) + UnableToDecode(List(decode.DecodeError)) } -fn read_fields( - bits: BitArray, - acc: List(#(Dynamic, Dynamic)), -) -> Result(Dynamic, DecodeError) { +fn read_fields(bits: BitArray, acc: List(Field)) -> Result(Dynamic, DecodeError) { case bits { - <<>> -> Ok(dynamic.properties(acc)) + <<>> -> + acc + |> list.map(field_as_pair) + |> dynamic.properties + |> Ok bits -> { - use #(prop, bits) <- result.try(read_field(bits)) + use Parsed(value: prop, rest: bits) <- result.try(read_field(bits)) read_fields(bits, [prop, ..acc]) } } } -fn read_field( - bits: BitArray, -) -> Result(#(#(Dynamic, Dynamic), BitArray), DecodeError) { - use #(tag, bits) <- result.try(read_varint(bits)) +type DecodeResult(t) = + Result(t, DecodeError) + +type Parsed(t) { + Parsed(value: t, rest: BitArray) +} + +fn parsed_map(of parsed: Parsed(t), with fun: fn(t) -> u) -> Parsed(u) { + let Parsed(value:, rest:) = parsed + Parsed(value: fun(value), rest:) +} + +type Field { + Field(key: Dynamic, value: Dynamic) +} + +fn field_as_pair(field: Field) { + #(field.key, field.value) +} + +fn read_field(bits: BitArray) -> DecodeResult(Parsed(Field)) { + use Parsed(value: tag, rest: bits) <- result.try(read_varint(bits)) let tag = bit_array_to_uint(tag) let field_id = tag |> int.bitwise_shift_right(3) @@ -80,23 +108,20 @@ fn read_field( UnknownWireType(wire_type), )) - let read_fn: fn(BitArray) -> ValueResult = case wire_type { - VarInt -> read_varint - I64 -> read_fixed(64) - Len -> read_len - I32 -> read_fixed(32) - } - use #(value, rest): #(Dynamic, BitArray) <- result.try({ + let read_fn = wire_type_read_fn(wire_type) + use value: Parsed(Dynamic) <- result.try({ use value <- result.map(read_fn(bits)) - use value <- pair.map_first(value) - dynamic.bit_array(value) + parsed_map(value, dynamic.bit_array) }) - Ok(#(#(dynamic.int(field_id), value), rest)) + let field = + parsed_map(value, fn(value) { Field(key: dynamic.int(field_id), value:) }) + + Ok(field) } type ValueResult = - Result(#(BitArray, BitArray), DecodeError) + DecodeResult(Parsed(BitArray)) fn read_varint(bits: BitArray) -> ValueResult { read_varint_acc(bits, <<>>) @@ -106,7 +131,7 @@ fn read_varint_acc(bits: BitArray, acc: BitArray) -> ValueResult { case bits { <<0:size(1), n:bits-size(7), rest:bytes>> -> { let acc = bit_array.concat([n, acc]) - Ok(#(acc, rest)) + Ok(Parsed(value: acc, rest:)) } <<1:size(1), n:bits-size(7), rest:bytes>> -> read_varint_acc(rest, bit_array.concat([n, acc])) @@ -117,7 +142,7 @@ fn read_varint_acc(bits: BitArray, acc: BitArray) -> ValueResult { fn read_fixed(size: Int) -> fn(BitArray) -> ValueResult { fn(bits: BitArray) -> ValueResult { case bits { - <> -> Ok(#(num, rest)) + <> -> Ok(Parsed(value: num, rest:)) bits -> Error(InvalidFixed(size:, bits:)) } } @@ -126,7 +151,7 @@ fn read_fixed(size: Int) -> fn(BitArray) -> ValueResult { fn read_len(bits: BitArray) -> ValueResult { // First, read the length of the value // It is encoded as a varint immediately after the tag - use #(len, bits) <- result.try(read_varint(bits)) + use Parsed(value: len, rest: bits) <- result.try(read_varint(bits)) let len = bit_array_to_uint(len) // Just decoded a uint, so should be safe @@ -140,7 +165,7 @@ fn read_len(bits: BitArray) -> ValueResult { let assert Ok(rest) = bit_array.slice(from: bits, at: len, take: bit_array.byte_size(bits) - len) - Ok(#(value, rest)) + Ok(Parsed(value:, rest:)) } pub fn decode_uint() -> Decoder(Int) { diff --git a/test/protobuf_decode_gleam_test.gleam b/test/protobuf_decode_gleam_test.gleam index 3d58e9f..b64e725 100644 --- a/test/protobuf_decode_gleam_test.gleam +++ b/test/protobuf_decode_gleam_test.gleam @@ -2,7 +2,7 @@ import gleam/dynamic/decode.{type Decoder} import gleam/option import gleeunit import protobuf_decode_gleam.{ - decode, decode_fixed, decode_string, decode_uint, decoder, + decode_fixed, decode_protobuf, decode_string, decode_uint, parse, } import simplifile as file @@ -24,7 +24,11 @@ const default_person = Person( fn person_decoder() -> Decoder(Person) { let person_inner_decoder = - decoder(using: person_decoder, named: "Person", default: default_person) + decode_protobuf( + using: person_decoder, + named: "Person", + default: default_person, + ) use id <- decode.field(3, decode_fixed(64)) use age <- decode.field(1, decode_uint()) @@ -42,7 +46,7 @@ fn person_decoder() -> Decoder(Person) { pub fn person_pb_test() { let path = "./test/person.pb" let assert Ok(bits) = file.read_bits(from: path) - let assert Ok(person) = decode(from: bits, using: person_decoder()) + let assert Ok(person) = parse(from: bits, using: person_decoder()) assert person == Person( id: 42, @@ -83,7 +87,7 @@ pub fn two_ints_test() { 0x04, >> - let assert Ok(data) = decode(from: bits, using: two_ints_decoder()) + let assert Ok(data) = parse(from: bits, using: two_ints_decoder()) assert data == Test(id: 150, age: 80_150) } @@ -94,7 +98,7 @@ type Gtfs { fn gtfs_decoder() -> Decoder(Gtfs) { use header <- decode.field( 1, - decoder( + decode_protobuf( using: feed_header_decoder, named: "FeedHeader", default: feed_header_default, @@ -117,7 +121,7 @@ fn feed_header_decoder() -> Decoder(FeedHeader) { pub fn gtfs_test() { let path = "./test/gtfs-short.pb" let assert Ok(bits) = file.read_bits(from: path) - let assert Ok(gtfs) = decode(from: bits, using: gtfs_decoder()) + let assert Ok(gtfs) = parse(from: bits, using: gtfs_decoder()) echo gtfs