-module(thrifty@reader). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/thrifty/reader.gleam"). -export([from_bit_array/1, with_options/2, position/1, read_i8/1, read_double/1, read_varint/1, read_bool_element/1, read_i16/1, read_i32/1, read_i64/1, read_binary/1, read_string/1, skip_value/2, read_struct/1]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -file("src/thrifty/reader.gleam", 41). ?DOC( " Helpers for reading Compact Protocol values with an immutable `Reader`.\n" "\n" " Public API contract (high level):\n" " - from_bit_array/1: create a `Reader` positioned at the beginning of `data`.\n" " - read_i8/read_i16/read_i32/read_i64: read the corresponding signed\n" " integer and return Ok(#(value, reader)) or an Error(DecodeError).\n" " - read_double: read IEEE-754 little-endian double returning Ok(#(float, reader)).\n" " - read_binary/read_string: read a length-prefixed binary (varint length)\n" " and return Ok(#(bytes, reader)) or Ok(#(string, reader)) for strings.\n" " - read_struct: parse a struct returning the list of field headers and the\n" " reader positioned after the struct payload.\n" " - skip_value: skip a value of the given field type efficiently and return\n" " the advanced reader.\n" "\n" " All public readers return `Result(#(T, types.Reader), types.DecodeError)` to\n" " make incremental parsing and error handling explicit. Examples and common\n" " error cases are documented on specific functions below.\n" ). -spec from_bit_array(bitstring()) -> thrifty@types:reader(). from_bit_array(Data) -> {reader, Data, 0, {reader_options, 64, 65536, 8388608, accept_canonical_only}}. -file("src/thrifty/reader.gleam", 45). -spec with_options(bitstring(), thrifty@types:reader_options()) -> thrifty@types:reader(). with_options(Data, Options) -> {reader, Data, 0, Options}. -file("src/thrifty/reader.gleam", 53). ?DOC(" Current byte offset inside the reader.\n"). -spec position(thrifty@types:reader()) -> integer(). position(Reader) -> {reader, _, Byte_pos, _} = Reader, Byte_pos. -file("src/thrifty/reader.gleam", 513). ?DOC( " Map a Result containing a value and a reader to a Result containing only\n" " the reader. Utility used by skip helpers to ignore parsed values while\n" " preserving the updated reader or propagating errors.\n" ). -spec map_reader( {ok, {any(), thrifty@types:reader()}} | {error, thrifty@types:decode_error()} ) -> {ok, thrifty@types:reader()} | {error, thrifty@types:decode_error()}. map_reader(Result) -> case Result of {error, E} -> {error, E}; {ok, {_, Reader}} -> {ok, Reader} end. -file("src/thrifty/reader.gleam", 522). -spec set_position( thrifty@types:reader(), integer(), thrifty@types:reader_options() ) -> thrifty@types:reader(). set_position(Reader, Byte_pos, Options) -> {reader, Data, _, _} = Reader, {reader, Data, Byte_pos, Options}. -file("src/thrifty/reader.gleam", 59). ?DOC(" Read an i8 value.\n"). -spec read_i8(thrifty@types:reader()) -> {ok, {integer(), thrifty@types:reader()}} | {error, thrifty@types:decode_error()}. read_i8(Reader) -> {reader, Data, Byte_pos, Options} = Reader, case gleam_stdlib:bit_array_slice(Data, Byte_pos, 1) of {error, _} -> {error, unexpected_end_of_input}; {ok, Bits} -> case Bits of <> -> {ok, {Value, set_position(Reader, Byte_pos + 1, Options)}}; _ -> {error, {invalid_wire_format, <<"Invalid byte"/utf8>>}} end end. -file("src/thrifty/reader.gleam", 94). -spec read_double(thrifty@types:reader()) -> {ok, {float(), thrifty@types:reader()}} | {error, thrifty@types:decode_error()}. read_double(Reader) -> {reader, Data, Byte_pos, Options} = Reader, case gleam_stdlib:bit_array_slice(Data, Byte_pos, 8) of {error, _} -> {error, unexpected_end_of_input}; {ok, Bits} -> case Bits of <> -> {ok, {Value, set_position(Reader, Byte_pos + 8, Options)}}; _ -> {error, {invalid_wire_format, <<"Invalid double"/utf8>>}} end end. -file("src/thrifty/reader.gleam", 109). -spec read_varint(thrifty@types:reader()) -> {ok, {integer(), thrifty@types:reader()}} | {error, thrifty@types:decode_error()}. read_varint(Reader) -> {reader, Data, Byte_pos, Options} = Reader, case thrifty@varint:decode_varint(Data, Byte_pos) of {error, E} -> {error, E}; {ok, {Value, Next_pos}} -> {ok, {Value, set_position(Reader, Next_pos, Options)}} end. -file("src/thrifty/reader.gleam", 181). ?DOC( " Read a boolean element stored as a single byte (used for boolean values\n" " inside containers such as list / set / map values).\n" "\n" " Encoding & canonical mapping:\n" " - Valid element bytes are `1` and `2`. This library interprets them as\n" " `1 -> True` and `2 -> False` (matching the inline boolean type nibble\n" " mapping used in field headers).\n" "\n" " Validation behaviour:\n" " - `types.ReaderOptions.bool_element_policy` controls validation. See\n" " `thrifty/types.gleam` for the available `BoolElementPolicy` values.\n" "\n" " This function returns Ok(#(Bool, Reader)) on success, advancing the\n" " reader past the element byte, or Error(types.DecodeError) on invalid or\n" " truncated input.\n" ). -spec read_bool_element(thrifty@types:reader()) -> {ok, {boolean(), thrifty@types:reader()}} | {error, thrifty@types:decode_error()}. read_bool_element(Reader) -> {reader, Data, Byte_pos, Options} = Reader, case gleam_stdlib:bit_array_slice(Data, Byte_pos, 1) of {error, _} -> {error, unexpected_end_of_input}; {ok, Bits} -> case Bits of <> -> case erlang:element(5, Options) of accept_canonical_only -> case Value of 1 -> {ok, {true, set_position( Reader, Byte_pos + 1, Options )}}; 2 -> {ok, {false, set_position( Reader, Byte_pos + 1, Options )}}; _ -> {error, {invalid_wire_format, <<"Invalid boolean element"/utf8>>}} end; accept_both -> case Value of 1 -> {ok, {true, set_position( Reader, Byte_pos + 1, Options )}}; 2 -> {ok, {false, set_position( Reader, Byte_pos + 1, Options )}}; _ -> {error, {invalid_wire_format, <<"Invalid boolean element"/utf8>>}} end end; _ -> {error, {invalid_wire_format, <<"Invalid boolean element"/utf8>>}} end end. -file("src/thrifty/reader.gleam", 480). ?DOC( " Skip a single element of type `elem_type`.\n" "\n" " Inputs\n" " - `reader`: current `types.Reader` positioned at the element.\n" " - `elem_type`: element type descriptor.\n" " - `depth`: current recursion depth.\n" "\n" " Outputs\n" " - `Ok(types.Reader)` positioned after the element on success.\n" " - `Error(types.DecodeError)` if the element is malformed or limits are exceeded.\n" ). -spec skip_bytes(thrifty@types:reader(), integer()) -> {ok, thrifty@types:reader()} | {error, thrifty@types:decode_error()}. skip_bytes(Reader, Count) -> {reader, Data, Byte_pos, Options} = Reader, case gleam_stdlib:bit_array_slice(Data, Byte_pos, Count) of {error, _} -> {error, unexpected_end_of_input}; {ok, _} -> {ok, set_position(Reader, Byte_pos + Count, Options)} end. -file("src/thrifty/reader.gleam", 500). ?DOC( " Skip `count` bytes in the reader, returning an advanced reader.\n" "\n" " Inputs\n" " - `reader`: current `types.Reader`.\n" " - `count`: number of bytes to skip.\n" "\n" " Outputs\n" " - `Ok(types.Reader)` advanced `count` bytes on success.\n" " - `Error(types.UnexpectedEndOfInput)` when there are not enough bytes.\n" ). -spec read_zigzag_integer(thrifty@types:reader(), fun((integer()) -> integer())) -> {ok, {integer(), thrifty@types:reader()}} | {error, thrifty@types:decode_error()}. read_zigzag_integer(Reader, Decode) -> case read_varint(Reader) of {error, E} -> {error, E}; {ok, {Bits, Next_reader}} -> {ok, {Decode(Bits), Next_reader}} end. -file("src/thrifty/reader.gleam", 76). ?DOC( " Read a zigzag-encoded i16/i32/i64 depending on the helper used. Returns\n" " Ok(#(value, reader)) on success or Error(types.DecodeError) on failure.\n" ). -spec read_i16(thrifty@types:reader()) -> {ok, {integer(), thrifty@types:reader()}} | {error, thrifty@types:decode_error()}. read_i16(Reader) -> read_zigzag_integer(Reader, fun thrifty@zigzag:decode_i32/1). -file("src/thrifty/reader.gleam", 82). -spec read_i32(thrifty@types:reader()) -> {ok, {integer(), thrifty@types:reader()}} | {error, thrifty@types:decode_error()}. read_i32(Reader) -> read_zigzag_integer(Reader, fun thrifty@zigzag:decode_i32/1). -file("src/thrifty/reader.gleam", 88). -spec read_i64(thrifty@types:reader()) -> {ok, {integer(), thrifty@types:reader()}} | {error, thrifty@types:decode_error()}. read_i64(Reader) -> read_zigzag_integer(Reader, fun thrifty@zigzag:decode_i64/1). -file("src/thrifty/reader.gleam", 541). ?DOC( " Set the reader's byte position and options, returning a new immutable reader.\n" "\n" " Inputs\n" " - `reader`: existing `types.Reader` instance.\n" " - `byte_pos`: new byte offset to set.\n" " - `options`: `types.ReaderOptions` to attach to the new reader.\n" "\n" " Outputs\n" " - A `types.Reader` referencing the same underlying data with updated\n" " position and options.\n" ). -spec ensure_depth(thrifty@types:reader(), integer()) -> {ok, nil} | {error, thrifty@types:decode_error()}. ensure_depth(Reader, Depth) -> {reader, _, _, Options} = Reader, case Depth > erlang:element(2, Options) of true -> {error, {invalid_wire_format, <<"Exceeded maximum depth"/utf8>>}}; false -> {ok, nil} end. -file("src/thrifty/reader.gleam", 552). -spec ensure_limit(integer(), integer(), binary()) -> {ok, nil} | {error, thrifty@types:decode_error()}. ensure_limit(Value, Limit, Message) -> case Value > Limit of true -> {error, {invalid_wire_format, Message}}; false -> {ok, nil} end. -file("src/thrifty/reader.gleam", 120). -spec read_binary(thrifty@types:reader()) -> {ok, {bitstring(), thrifty@types:reader()}} | {error, thrifty@types:decode_error()}. read_binary(Reader) -> {reader, Data, Byte_pos, Options} = Reader, case thrifty@varint:decode_varint(Data, Byte_pos) of {error, E} -> {error, E}; {ok, {Length, Next_pos}} -> case ensure_limit( Length, erlang:element(4, Options), <<"Binary length exceeds max_string_bytes"/utf8>> ) of {error, E@1} -> {error, E@1}; {ok, nil} -> case gleam_stdlib:bit_array_slice(Data, Next_pos, Length) of {error, _} -> {error, unexpected_end_of_input}; {ok, Bytes} -> {ok, {Bytes, set_position( Reader, Next_pos + Length, Options )}} end end end. -file("src/thrifty/reader.gleam", 153). ?DOC( " Read a UTF-8 string. This uses `read_binary/1` and then converts to a\n" " Gleam `String`. Returns Error(types.InvalidWireFormat) if bytes are not valid\n" " UTF-8. Example:\n" "\n" " let r0 = reader.from_bit_array(writer.write_string(\"hello\"))\n" " let Ok(#(s, r1)) = reader.read_string(r0)\n" " s |> should.equal(\"hello\")\n" ). -spec read_string(thrifty@types:reader()) -> {ok, {binary(), thrifty@types:reader()}} | {error, thrifty@types:decode_error()}. read_string(Reader) -> case read_binary(Reader) of {error, E} -> {error, E}; {ok, {Bytes, Next_reader}} -> case gleam@bit_array:to_string(Bytes) of {error, _} -> {error, {invalid_wire_format, <<"Invalid UTF-8 string"/utf8>>}}; {ok, Value} -> {ok, {Value, Next_reader}} end end. -file("src/thrifty/reader.gleam", 242). -spec read_struct_loop( thrifty@types:reader(), integer(), integer(), list(thrifty@types:field_header()) ) -> {ok, {list(thrifty@types:field_header()), thrifty@types:reader()}} | {error, thrifty@types:decode_error()}. read_struct_loop(Reader, Depth, Last_field_id, Acc) -> case thrifty@field:read_field_header(Reader, Last_field_id) of {error, E} -> {error, E}; {ok, {{field_header, Field_id, Field_type}, After_header}} -> case Field_type of stop -> {ok, {lists:reverse(Acc), After_header}}; _ -> case skip_value_at_depth( After_header, Field_type, Depth + 1 ) of {error, E@1} -> {error, E@1}; {ok, After_value} -> read_struct_loop( After_value, Depth, Field_id, [{field_header, Field_id, Field_type} | Acc] ) end end end. -file("src/thrifty/reader.gleam", 266). -spec skip_value_at_depth( thrifty@types:reader(), thrifty@types:field_type(), integer() ) -> {ok, thrifty@types:reader()} | {error, thrifty@types:decode_error()}. skip_value_at_depth(Reader, Field_type, Depth) -> case ensure_depth(Reader, Depth) of {error, E} -> {error, E}; {ok, nil} -> case Field_type of bool_true -> {ok, Reader}; bool_false -> {ok, Reader}; stop -> {ok, Reader}; byte -> skip_bytes(Reader, 1); i16 -> map_reader(read_i16(Reader)); i32 -> map_reader(read_i32(Reader)); i64 -> map_reader(read_i64(Reader)); double -> map_reader(read_double(Reader)); binary -> map_reader(read_binary(Reader)); list -> skip_list_or_set(Reader, Depth); set -> skip_list_or_set(Reader, Depth); map -> skip_map(Reader, Depth); struct -> map_reader(read_struct_at_depth(Reader, Depth)) end end. -file("src/thrifty/reader.gleam", 232). -spec read_struct_at_depth(thrifty@types:reader(), integer()) -> {ok, {list(thrifty@types:field_header()), thrifty@types:reader()}} | {error, thrifty@types:decode_error()}. read_struct_at_depth(Reader, Depth) -> case ensure_depth(Reader, Depth) of {error, E} -> {error, E}; {ok, nil} -> read_struct_loop(Reader, Depth, 0, []) end. -file("src/thrifty/reader.gleam", 219). ?DOC( " Skip a value of the given `field_type`. Useful when parsing structs and\n" " encountering unknown/ignored fields. This function enforces depth/size\n" " limits configured in `ReaderOptions` and returns the advanced reader or an\n" " error. Example:\n" "\n" " let r0 = reader.from_bit_array(some_encoded_list)\n" " let Ok(r1) = reader.skip_value(r0, types.List)\n" ). -spec skip_value(thrifty@types:reader(), thrifty@types:field_type()) -> {ok, thrifty@types:reader()} | {error, thrifty@types:decode_error()}. skip_value(Reader, Field_type) -> skip_value_at_depth(Reader, Field_type, 1). -file("src/thrifty/reader.gleam", 226). -spec read_struct(thrifty@types:reader()) -> {ok, {list(thrifty@types:field_header()), thrifty@types:reader()}} | {error, thrifty@types:decode_error()}. read_struct(Reader) -> read_struct_at_depth(Reader, 1). -file("src/thrifty/reader.gleam", 447). ?DOC( " Skip `count` map entries (key followed by value), returning the reader\n" " advanced after all entries or an error.\n" "\n" " Inputs\n" " - `count`: number of key/value pairs to skip.\n" " - `key_type`, `value_type`: element types for keys and values.\n" " - `reader`: current `types.Reader` positioned at the first key.\n" " - `depth`: current recursion depth.\n" "\n" " Outputs\n" " - `Ok(types.Reader)` after all entries are skipped.\n" " - `Error(types.DecodeError)` if any entry fails to skip.\n" ). -spec skip_element( thrifty@types:reader(), thrifty@container:element_type(), integer() ) -> {ok, thrifty@types:reader()} | {error, thrifty@types:decode_error()}. skip_element(Reader, Elem_type, Depth) -> case Elem_type of bool_type -> map_reader(read_bool_element(Reader)); i8_type -> skip_bytes(Reader, 1); i16_type -> map_reader(read_i16(Reader)); i32_type -> map_reader(read_i32(Reader)); i64_type -> map_reader(read_i64(Reader)); double_type -> map_reader(read_double(Reader)); binary_type -> map_reader(read_binary(Reader)); list_type -> skip_list_or_set(Reader, Depth + 1); set_type -> skip_list_or_set(Reader, Depth + 1); map_type -> skip_map(Reader, Depth + 1); struct_type -> map_reader(read_struct_at_depth(Reader, Depth + 1)) end. -file("src/thrifty/reader.gleam", 292). -spec skip_list_or_set(thrifty@types:reader(), integer()) -> {ok, thrifty@types:reader()} | {error, thrifty@types:decode_error()}. skip_list_or_set(Reader, Depth) -> case ensure_depth(Reader, Depth) of {error, E} -> {error, E}; {ok, nil} -> {reader, Data, Byte_pos, Options} = Reader, case thrifty@container:decode_list_header(Data, Byte_pos) of {error, E@1} -> {error, E@1}; {ok, {Size, Elem_type, Next_pos}} -> case ensure_limit( Size, erlang:element(3, Options), <<"Exceeded container size limit"/utf8>> ) of {error, E@2} -> {error, E@2}; {ok, nil} -> skip_elements( Size, Elem_type, set_position(Reader, Next_pos, Options), Depth + 1 ) end end end. -file("src/thrifty/reader.gleam", 378). ?DOC( " Skip a map container: decode its header, enforce limits, and skip all\n" " key/value pairs recursively.\n" "\n" " Inputs\n" " - `reader`: current `types.Reader` positioned at a map header.\n" " - `depth`: current recursion depth.\n" "\n" " Outputs\n" " - `Ok(types.Reader)` with the reader positioned after the map on success.\n" " - `Error(types.DecodeError)` on malformed input or if configured limits are exceeded.\n" ). -spec skip_elements( integer(), thrifty@container:element_type(), thrifty@types:reader(), integer() ) -> {ok, thrifty@types:reader()} | {error, thrifty@types:decode_error()}. skip_elements(Count, Elem_type, Reader, Depth) -> case Count of 0 -> {ok, Reader}; _ -> case skip_element(Reader, Elem_type, Depth) of {error, E} -> {error, E}; {ok, Next_reader} -> skip_elements(Count - 1, Elem_type, Next_reader, Depth) end end. -file("src/thrifty/reader.gleam", 407). ?DOC( " Skip `count` elements of `elem_type`, returning the reader advanced past\n" " all elements or an error if any element skip fails.\n" "\n" " Inputs\n" " - `count`: number of elements to skip.\n" " - `elem_type`: element type descriptor.\n" " - `reader`: current `types.Reader` positioned at the first element.\n" " - `depth`: current recursion depth.\n" "\n" " Outputs\n" " - `Ok(types.Reader)` positioned after the skipped elements.\n" " - `Error(types.DecodeError)` when an element cannot be skipped or limits are exceeded.\n" ). -spec skip_map_entries( integer(), thrifty@container:element_type(), thrifty@container:element_type(), thrifty@types:reader(), integer() ) -> {ok, thrifty@types:reader()} | {error, thrifty@types:decode_error()}. skip_map_entries(Count, Key_type, Value_type, Reader, Depth) -> case Count of 0 -> {ok, Reader}; _ -> case skip_element(Reader, Key_type, Depth) of {error, E} -> {error, E}; {ok, After_key} -> case skip_element(After_key, Value_type, Depth) of {error, E@1} -> {error, E@1}; {ok, After_value} -> skip_map_entries( Count - 1, Key_type, Value_type, After_value, Depth ) end end end. -file("src/thrifty/reader.gleam", 335). ?DOC( " Skip a list or set container: decode its header, enforce container limits,\n" " and skip all contained elements recursively.\n" "\n" " Inputs\n" " - `reader`: current `types.Reader` positioned at a list/set header.\n" " - `depth`: current recursion depth.\n" "\n" " Outputs\n" " - `Ok(types.Reader)` with the reader positioned after the container on success.\n" " - `Error(types.DecodeError)` on header decode failures, unexpected end, or if\n" " container limits are exceeded.\n" ). -spec skip_map(thrifty@types:reader(), integer()) -> {ok, thrifty@types:reader()} | {error, thrifty@types:decode_error()}. skip_map(Reader, Depth) -> case ensure_depth(Reader, Depth) of {error, E} -> {error, E}; {ok, nil} -> {reader, Data, Byte_pos, Options} = Reader, case thrifty@container:decode_map_header(Data, Byte_pos) of {error, E@1} -> {error, E@1}; {ok, {Size, Key_type, Value_type, Next_pos}} -> case ensure_limit( Size, erlang:element(3, Options), <<"Exceeded container size limit"/utf8>> ) of {error, E@2} -> {error, E@2}; {ok, nil} -> skip_map_entries( Size, Key_type, Value_type, set_position(Reader, Next_pos, Options), Depth + 1 ) end end end.