Decode the protobuf wire format in Gleam! protobin.hexdocs.pm/
gleam protobuf
3

Configure Feed

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

Clean up naming and add documentation

Gavin Morrow (Sep 28, 2025, 9:12 PM EDT) 2a20ca27 3be729fd

+31 -23
+2 -2
src/decoders.gleam
··· 7 7 8 8 import internal/util 9 9 import protobuf_decode_gleam.{ 10 - type BytePos, type DecodeResult, type ValueParser, Parsed, parse, read_varint, 10 + type BytePos, type DecodeResult, type ValueParser, Parsed, parse, parse_varint, 11 11 } 12 12 13 13 /// Decode a repeated field that may be either packed or expanded. ··· 95 95 96 96 pub fn uint() -> Decoder(Int) { 97 97 use bits <- decode.then(single_or_raw(decode.bit_array)) 98 - use bits <- decode.then(case read_varint(bits, 0) { 98 + use bits <- decode.then(case parse_varint(bits, 0) { 99 99 Ok(Parsed(value:, rest: <<>>, pos: _)) -> decode.success(value) 100 100 _ -> decode.failure(<<>>, "uint") 101 101 })
+26 -18
src/protobuf_decode_gleam.gleam
··· 93 93 Field(key: Dynamic, value: Dynamic) 94 94 } 95 95 96 - fn wire_type_consume_fn(ty: WireType) -> ValueParser { 96 + fn wire_type_read_fn(ty: WireType) -> ValueParser { 97 97 case ty { 98 - wire_type.VarInt -> consume_varint 98 + wire_type.VarInt -> read_varint 99 99 wire_type.I64 -> read_fixed(64) 100 100 wire_type.Len -> read_len 101 101 wire_type.I32 -> read_fixed(32) ··· 103 103 } 104 104 105 105 fn read_field(bits: BitArray, tag_pos: BytePos) -> DecodeResult(Parsed(Field)) { 106 - use Parsed(value: tag, rest: bits, pos:) <- result.try(read_varint( 106 + use Parsed(value: tag, rest: bits, pos:) <- result.try(parse_varint( 107 107 bits, 108 108 tag_pos, 109 109 )) ··· 124 124 UnknownWireType(wire_type, pos: tag_pos), 125 125 )) 126 126 127 - let consume = wire_type_consume_fn(wire_type) 127 + let read = wire_type_read_fn(wire_type) 128 128 use value: Parsed(Dynamic) <- result.try( 129 - consume(bits, pos) |> result.map(parsed_map(_, dynamic.bit_array)), 129 + read(bits, pos) |> result.map(parsed_map(_, dynamic.bit_array)), 130 130 ) 131 131 132 132 let field = ··· 141 141 pub type ValueParser = 142 142 fn(BitArray, BytePos) -> ValueResult 143 143 144 - pub fn consume_varint(bits: BitArray, pos: BytePos) -> ValueResult { 145 - consume_varint_acc(bits, <<>>, pos) 144 + /// Reads the bits from a varint and returns *all* of them. They cannot be 145 + /// parsed as a uint. 146 + /// 147 + /// Specifically, the continuation bits are included, so the value's bit size 148 + /// will be a multiple of 8. 149 + pub fn read_varint(bits: BitArray, pos: BytePos) -> ValueResult { 150 + read_varint_acc(bits, <<>>, pos) 146 151 } 147 152 148 - fn consume_varint_acc( 149 - bits: BitArray, 150 - acc: BitArray, 151 - pos: BytePos, 152 - ) -> ValueResult { 153 + fn read_varint_acc(bits: BitArray, acc: BitArray, pos: BytePos) -> ValueResult { 153 154 case bits { 154 155 <<0:size(1), n:bits-size(7), rest:bytes>> -> { 155 156 let bit = <<0:size(1), n:bits>> ··· 158 159 } 159 160 <<1:size(1), n:bits-size(7), rest:bytes>> -> { 160 161 let bit = <<1:size(1), n:bits>> 161 - consume_varint_acc(rest, bit_array.concat([acc, bit]), pos + 1) 162 + read_varint_acc(rest, bit_array.concat([acc, bit]), pos + 1) 162 163 } 163 164 bits -> Error(InvalidVarInt(leftover_bits: bits, acc:, pos:)) 164 165 } 165 166 } 166 167 167 - pub fn read_varint(bits: BitArray, pos: BytePos) -> ValueResult { 168 - read_varint_acc(bits, <<>>, pos) 168 + /// Reads the bits from a varint and parses them a BitArray. The returned bits 169 + /// can be parsed as a uint. 170 + /// 171 + /// For a decoder that does this, use `decoders.uint()`. 172 + /// 173 + /// The continuation bits are not included, so the value's bit size will be a 174 + /// multiple of 7. 175 + pub fn parse_varint(bits: BitArray, pos: BytePos) -> ValueResult { 176 + parse_varint_acc(bits, <<>>, pos) 169 177 } 170 178 171 - fn read_varint_acc(bits: BitArray, acc: BitArray, pos: BytePos) -> ValueResult { 179 + fn parse_varint_acc(bits: BitArray, acc: BitArray, pos: BytePos) -> ValueResult { 172 180 case bits { 173 181 <<0:size(1), n:bits-size(7), rest:bytes>> -> { 174 182 let acc = bit_array.concat([n, acc]) 175 183 Ok(Parsed(value: acc, rest:, pos: pos + 1)) 176 184 } 177 185 <<1:size(1), n:bits-size(7), rest:bytes>> -> 178 - read_varint_acc(rest, bit_array.concat([n, acc]), pos + 1) 186 + parse_varint_acc(rest, bit_array.concat([n, acc]), pos + 1) 179 187 bits -> { 180 188 Error(InvalidVarInt(leftover_bits: bits, acc:, pos:)) 181 189 } ··· 197 205 fn read_len(bits: BitArray, len_pos: BytePos) -> ValueResult { 198 206 // First, read the length of the value 199 207 // It is encoded as a varint immediately after the tag 200 - use Parsed(value: len, rest: bits, pos:) <- result.try(read_varint( 208 + use Parsed(value: len, rest: bits, pos:) <- result.try(parse_varint( 201 209 bits, 202 210 len_pos, 203 211 ))
+3 -3
test/protobuf_decode_gleam_test.gleam
··· 4 4 import simplifile as file 5 5 6 6 import decoders 7 - import protobuf_decode_gleam.{Parsed, consume_varint, parse} 7 + import protobuf_decode_gleam.{Parsed, read_varint, parse} 8 8 9 9 pub fn main() -> Nil { 10 10 gleeunit.main() ··· 197 197 fn packed_fields_decoder() -> Decoder(PackedFields) { 198 198 use packed <- decode.field( 199 199 3, 200 - decoders.multiple(of: decoders.uint(), using: consume_varint), 200 + decoders.multiple(of: decoders.uint(), using: read_varint), 201 201 ) 202 202 use expanded <- decode.field( 203 203 4, 204 - decoders.multiple(of: decoders.uint(), using: consume_varint), 204 + decoders.multiple(of: decoders.uint(), using: read_varint), 205 205 ) 206 206 PackedFields(packed:, expanded:) |> decode.success 207 207 }