Skip to main content

Codec

Struct Codec 

Source
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:

  1. a $Rust.codec("name") annotation on the field, resolved against the names registered with with_named_codec;
  2. a with_field_override registered for exactly that field;
  3. a with_type_override registered 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 encode or decode, since that is not reached through a field. Use a $Rust.codec annotation on the struct declaration for that.
  • For a field of type List(T), a type override must be registered for List(T) rather than for T — one registered for T will not fire for the elements. A $Rust.codec annotation on a list field behaves the other way round: it is applied to each element in turn.

Implementations§

Source§

impl<'env> Codec<'env>

Source

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.

Source

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.

Source

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.

Source

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.

Source

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].

Source

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.

Source

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.

Source

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.

Trait Implementations§

Source§

impl Default for Codec<'_>

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl<'env> !RefUnwindSafe for Codec<'env>

§

impl<'env> !Send for Codec<'env>

§

impl<'env> !Sync for Codec<'env>

§

impl<'env> !UnwindSafe for Codec<'env>

§

impl<'env> Freeze for Codec<'env>

§

impl<'env> Unpin for Codec<'env>

§

impl<'env> UnsafeUnpin for Codec<'env>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.