pub struct Codec<'env> { /* private fields */ }Expand description
A JSON codec for Cap’n Proto messages.
A Codec holds the custom FieldCodecs to apply while encoding and
decoding. If you need none, to_json and from_json are equivalent to
Codec::new().encode(..) and Codec::new().decode(..) and are more
convenient.
A Codec is built up with the with_* methods, which consume and return
it:
use capnp_json::{make_field_codec, Codec, JsonValue};
let codec = Codec::new().with_named_codec(
"shouty",
make_field_codec(
|source: capnp::dynamic_value::Reader<'_>| {
let text: capnp::text::Reader<'_> = source.downcast();
Ok(JsonValue::String(text.to_str()?.to_uppercase()))
},
|_source: &JsonValue, _target: capnp::dynamic_value::Builder<'_>| Ok(()),
),
);The 'env lifetime is that of the data borrowed by the registered codecs;
for codecs that own everything they use, it is 'static.
§Reuse and threads
Encoding and decoding take &self and keep no state between calls, so one
Codec serves any number of messages and building one is cheap. Reuse it
if it is convenient; nothing is lost by not doing so.
A Codec is neither Send nor Sync, so give each thread its own. In
async code that means building one where it is used rather than holding it
in shared state: Codec::new() costs a handful of nanoseconds, so the only
reason to keep one alive is the custom FieldCodecs registered on it. If
you need those in shared state, store something Send + Sync that builds
the codec — a factory closure — and call it per request, or keep one in a
thread_local!.
This is not a bound that could simply be added. It comes from the schema
types: field_overrides is keyed on capnp::schema::Field, which holds
readers over the generated schema data, and those hold raw pointers. The
same is true of capnp::dynamic_value::Reader and its Builder, so the
values being encoded are !Send too — an encode or decode is inherently
one synchronous stretch, and a Send Codec would only ever buy the
ability to store one, never to await part-way through.
§Which codec wins
At most one FieldCodec applies to any given value. When encoding or
decoding a struct field, the first match of the following is used:
- a
$Rust.codec("name")annotation on the field, resolved against the names registered withwith_named_codec; - a
with_field_overrideregistered for exactly that field; - a
with_type_overrideregistered for the field’s declared type.
If none matches and the value is a struct, a $Rust.codec("name")
annotation on the struct’s own declaration is used if one is present.
A $Rust.codec name that is not registered is silently ignored and the
default encoding applies.
§Scope of overrides
Both overrides are matched against a field, which has two consequences worth knowing:
- Neither applies to the root value passed to
encodeordecode, since that is not reached through a field. Use a$Rust.codecannotation on the struct declaration for that. - For a field of type
List(T), a type override must be registered forList(T)rather than forT— one registered forTwill not fire for the elements. A$Rust.codecannotation on a list field behaves the other way round: it is applied to each element in turn.
Implementations§
Source§impl<'env> Codec<'env>
impl<'env> Codec<'env>
Sourcepub fn new() -> Self
pub fn new() -> Self
Create a codec with no custom FieldCodecs registered.
The result encodes and decodes exactly as to_json and from_json
do.
Sourcepub fn new_with_options(options: CodecOptions) -> Self
pub fn new_with_options(options: CodecOptions) -> Self
Create a codec with no custom FieldCodecs registered, and the given
CodecOptions.
Equivalent to new other than the options; read
CodecOptions::recursion_limit before raising the recursion limit.
Sourcepub fn with_field_override(
self,
field: Field,
codec: impl FieldCodec + 'env,
) -> Self
pub fn with_field_override( self, field: Field, codec: impl FieldCodec + 'env, ) -> Self
Use codec for one specific field of one specific struct type.
field is obtained from the field’s containing
StructSchema, which in turn comes from
the generated Owned type:
use capnp::introspect::Introspect;
let capnp::introspect::TypeVariant::Struct(schema) =
my_schema_capnp::my_struct::Owned::introspect().which()
else {
unreachable!("my_struct is a struct");
};
let field = capnp::schema::StructSchema::new(schema)
.get_field_by_name("myField")?;
let codec = Codec::new().with_field_override(field, MyFieldCodec);This is the most specific binding and takes precedence over
with_type_override; see
which codec wins. Registering a second codec
for the same field replaces the first.
Sourcepub fn with_type_override(
self,
typ: Type,
codec: impl FieldCodec + 'env,
) -> Self
pub fn with_type_override( self, typ: Type, codec: impl FieldCodec + 'env, ) -> Self
Use codec for every field whose declared type is typ.
The type comes from the generated Owned type of whatever you want to
override:
use capnp::introspect::Introspect;
let codec = Codec::new()
.with_type_override(my_schema_capnp::timestamp::Owned::introspect(), Timestamp);Matching is on the field’s declared type, so read the note on scope before using this for list element types. Registering a second codec for the same type replaces the first.
Sourcepub fn with_named_codec(
self,
name: impl Into<String>,
codec: impl FieldCodec + 'env,
) -> Self
pub fn with_named_codec( self, name: impl Into<String>, codec: impl FieldCodec + 'env, ) -> Self
Register codec under name, for use by $Rust.codec annotations.
This puts the choice of representation in the schema, next to the data
it describes, rather than in the code that builds the Codec:
using Rust = import "/rust-json.capnp";
struct Reading {
takenAt @0 :Int64 $Rust.codec("iso8601");
}
struct Duration $Rust.codec("iso8601-duration") {
seconds @0 :Int64;
}let codec = Codec::new().with_named_codec("iso8601", Iso8601);The annotation may be applied to a field or to a struct declaration. On a field it takes precedence over both override maps; on a struct it applies wherever a value of that struct type is encoded, including at the root. A name with no matching registration is ignored and the default encoding is used, so a codec registered under the wrong name fails silently rather than loudly.
The annotation is declared in this crate’s rust-json.capnp. Copy that
file somewhere on your schema import path, and point capnpc at this
crate for its file ID:
capnpc::CompilerCommand::new()
.crate_provides("capnp_json", [0xf955e504bf781ac6])
.file("my_schema.capnp")
.run()
.expect("compiling schema");If the schema also uses the $Json.* annotations, list both file IDs:
[0x8ef99297a43a5e34, 0xf955e504bf781ac6].
Sourcepub fn encode<'msg>(&self, reader: impl Into<Reader<'msg>>) -> Result<String>
pub fn encode<'msg>(&self, reader: impl Into<Reader<'msg>>) -> Result<String>
Encode a Cap’n Proto struct as a JSON string.
The value mapping is described in the
module documentation. Returns an error if reader
is not a struct, if the message contains an AnyPointer or interface
field with no codec registered for it, or if a registered
FieldCodec fails.
Use encode_to to write into an existing buffer or
stream instead of allocating a String.
Sourcepub fn encode_to<'msg, W: Write>(
&self,
writer: &mut W,
reader: impl Into<Reader<'msg>>,
) -> Result<()>
pub fn encode_to<'msg, W: Write>( &self, writer: &mut W, reader: impl Into<Reader<'msg>>, ) -> Result<()>
Encode a Cap’n Proto struct as JSON, writing it to writer.
Behaves as encode but streams the output, so it does
not hold the whole document in memory. The JSON is written as UTF-8.
Output is written in many small pieces, so wrap unbuffered destinations
such as File or TcpStream in a BufWriter.
If an error occurs part-way through, a partial document will already have been written.
Sourcepub fn decode<'segments>(
&self,
json: &str,
builder: impl Into<Builder<'segments>>,
) -> Result<()>
pub fn decode<'segments>( &self, json: &str, builder: impl Into<Builder<'segments>>, ) -> Result<()>
Decode a JSON string into a Cap’n Proto struct builder.
The top-level JSON value must be an object, and builder must therefore
be a struct builder. Fields absent from the JSON keep the values already
in builder; JSON fields with no counterpart in the schema are ignored.
builder is not cleared first, so decoding into a builder that has
already been populated merges into it.
Returns an error if json is malformed, if a value cannot be coerced to
its field’s declared type, or if a registered FieldCodec fails. On
error, builder may have been partially populated.
Read the compatibility notes before decoding input you do not control — in particular, deeply nested JSON can exhaust the stack.