thrift_runtime_json (thrift_runtime v0.7.0)

View Source

The Apache Thrift JSON protocol: every value is a JSON document whose shape carries the Thrift types. A message is the array [1,"name",type,seqid,payload]; a struct is an object keyed by field id where every value is a one-key object naming the type ("i32", "str", "rec", ...); a list or set is ["i32",2,1,2], a map ["i32","i32",2,{"1":10}]. Wire-compatible with the vendored Apache thrift_json_protocol — the golden suite runs the official JSON client against a JSON server of ours.

One deliberate bend to fit this codebase's pure-codec shape: the envelope's closing ] is folded into the TOP-LEVEL struct decode (decode/2 on a struct consumes one optional trailing ]), and decode_message/1 returns the payload including it. Nested values are never touched by this — a struct inside a list keeps the ] that closes the list — but it makes the payload self-contained: the completeness probe of the buffered transport (an empty descriptor over the message tail) accounts for every byte exactly, like it does for the binary and compact protocols.

Struct keys of maps are limited to what a JSON object key can carry: booleans, integers, doubles and strings. A map keyed by a struct, list, set or map compiles to {j_unsupported, ...} and both encode and decode refuse it (the reference implementation would produce invalid JSON). uuid is likewise uncarryable. Doubles are written with 16 fractional digits, mirroring the reference round-trip.

Descriptors compile once (see thrift_runtime_binary, the type vocabulary is shared): inline {struct, {Kind, Fields}} descriptors are memoized per process under exact equality, {struct, {ref, M, N}} schemas resolve through M:struct_info/1 into a persistent_term cache. Generated types modules also probe their specialized json_codec/1 pair — the encode side everywhere, the decode side carries the non-folding struct body and the bracket fold is applied by whichever top-level entry calls it (decode/2, never decode_nested/2). Decoding is strict — declared fields whose type tag disagrees with the descriptor are rejected, unknown fields are skipped by their wire tag, required fields must be present, trailing bytes answer {error, trailing}.

Summary

Functions

Decodes one value of Type from the front of Binary; the rest comes back, more marks a valid but truncated input.

Decodes TApplicationException from an EXCEPTION message payload.

Decodes a message envelope.

Non-folding struct decode for refs nested inside containers and struct fields: probes the generated JSON codec pair and falls back to the generic nested decode.

Encodes Value as Type, or fails with a tagged error.

Encodes TApplicationException as an EXCEPTION message payload (a struct object; the envelope writes the bracket).

Encodes a message envelope around Payload (already encoded as a struct): the JSON array [1,"name",type,seqid,payload]. The closing bracket is written here and consumed by the payload's top-level struct decode on the way back.

Skips one JSON value of wire tag Tag (e.g. <<"i32">>, <<"lst">>) at the front of Binary; the rest comes back, more marks a valid but truncated input. Exported for generated JSON codecs: their decode loops skip unknown struct fields through this function, keeping skip-on-unknown-field semantics in one place.

Types

base_type()

-type base_type() :: thrift_runtime_binary:base_type().

decode_result(Value)

-type decode_result(Value) :: thrift_runtime_binary:decode_result(Value).

enum_desc()

-type enum_desc() :: thrift_runtime_binary:enum_desc().

j_struct()

-type j_struct() ::
          #{kind := struct | union | exception,
            fields := [{integer(), j_type(), binary()}],
            by_id := #{integer() => {j_type(), binary()}},
            required := #{integer() => binary()},
            required_names := [binary()]}.

j_type()

-type j_type() ::
          {j_base, base_type()} |
          {j_enum, enum_desc()} |
          {j_list, j_type()} |
          {j_set, j_type()} |
          {j_map, j_type(), j_type()} |
          {j_struct, j_struct()} |
          {j_ref, module(), binary()} |
          {j_unsupported, type_desc()}.

type_desc()

-type type_desc() :: thrift_runtime_binary:type_desc().

Functions

decode/2

Decodes one value of Type from the front of Binary; the rest comes back, more marks a valid but truncated input.

On a struct the optional closing ] of a message envelope is consumed after the struct proper (see the moduledoc).

decode_application_exception(Bin)

-spec decode_application_exception(binary()) ->
                                      decode_result(thrift_runtime_binary:application_exception()).

Decodes TApplicationException from an EXCEPTION message payload.

decode_message(Bin)

-spec decode_message(binary()) ->
                        {ok,
                         thrift_runtime_binary:message_type(),
                         binary(),
                         non_neg_integer(),
                         binary()} |
                        more |
                        {error, term()}.

Decodes a message envelope.

Returns {ok, Type, Name, SeqId, Payload} where Payload spans the struct object plus the envelope's closing ] — exactly the bytes the strict struct decode consumes — then more for a truncated envelope or a tagged error otherwise.

decode_nested/2

-spec decode_nested(thrift_runtime_binary:type_desc(), binary()) -> decode_result(term()).

Non-folding struct decode for refs nested inside containers and struct fields: probes the generated JSON codec pair and falls back to the generic nested decode.

The envelope's closing ] is NOT consumed — only the top-level decode/2 entry points fold it (see the moduledoc).

encode/2

-spec encode(thrift_runtime_binary:type_desc(), term()) -> {ok, iodata()} | {error, term()}.

Encodes Value as Type, or fails with a tagged error.

encode_application_exception(Message, Code)

-spec encode_application_exception(binary(), non_neg_integer()) -> {ok, iodata()} | {error, term()}.

Encodes TApplicationException as an EXCEPTION message payload (a struct object; the envelope writes the bracket).

encode_message(Type, Name, SeqId, Payload)

-spec encode_message(thrift_runtime_binary:message_type(), binary(), non_neg_integer(), iodata()) ->
                        iodata().

Encodes a message envelope around Payload (already encoded as a struct): the JSON array [1,"name",type,seqid,payload]. The closing bracket is written here and consumed by the payload's top-level struct decode on the way back.

skip_by_tag/2

-spec skip_by_tag(binary(), binary()) -> {ok, binary()} | more | {error, term()}.

Skips one JSON value of wire tag Tag (e.g. <<"i32">>, <<"lst">>) at the front of Binary; the rest comes back, more marks a valid but truncated input. Exported for generated JSON codecs: their decode loops skip unknown struct fields through this function, keeping skip-on-unknown-field semantics in one place.