Skip to main content

FieldCodec

Trait FieldCodec 

Source
pub trait FieldCodec {
    // Required methods
    fn encode_value(&self, source: Reader<'_>) -> Result<JsonValue>;
    fn decode_value(
        &self,
        source: &JsonValue,
        target: Builder<'_>,
    ) -> Result<()>;

    // Provided method
    fn decode_member(
        &self,
        source: &JsonValue,
        target: Builder<'_>,
        field: Field,
    ) -> Result<()> { ... }
}
Expand description

A custom JSON representation for a single Cap’n Proto value.

Implement this to override how a field, or every value of a given type, is converted to and from JSON. It is required for AnyPointer and interface fields, which carry no schema the codec could drive itself; it is merely useful for everything else, when the default mapping is not the shape you want on the wire.

A FieldCodec is attached to a Codec by one of with_field_override, with_type_override or with_named_codec.

§Implementing

The trait has two required methods and one that you will need to override more often than its default suggests:

  • encode_value is handed the Cap’n Proto value and returns the JsonValue to write in its place.
  • decode_value is handed a parsed JsonValue and a builder already positioned at the target value, and populates it.
  • decode_member is handed the parent struct builder plus the field to write, and so gets to decide how the target is created.

Which of the two decode methods is called depends on how the codec is bound — see the note below.

For simple cases a pair of closures is easier than a named type; see make_field_codec.

use capnp_json::{FieldCodec, JsonValue};

/// Encodes a struct with `seconds`/`nanos` fields as a single number.
struct Timestamp;

impl FieldCodec for Timestamp {
  fn encode_value(
    &self,
    source: capnp::dynamic_value::Reader<'_>,
  ) -> capnp::Result<JsonValue> {
    let source: capnp::dynamic_struct::Reader<'_> = source.downcast();
    let seconds: i64 = source.get_named("seconds")?.downcast();
    let nanos: i64 = source.get_named("nanos")?.downcast();
    Ok(JsonValue::Number(seconds as f64 + nanos as f64 / 1e9))
  }

  fn decode_value(
    &self,
    source: &JsonValue,
    target: capnp::dynamic_value::Builder<'_>,
  ) -> capnp::Result<()> {
    let JsonValue::Number(value) = source else {
      return Err(capnp::Error::failed("expected a number".into()));
    };
    let mut target: capnp::dynamic_struct::Builder<'_> = target.downcast();
    target.set_named("seconds", (value.trunc() as i64).into())?;
    target.set_named("nanos", ((value.fract() * 1e9) as i64).into())?;
    Ok(())
  }
}

§Which decode method is called

When the codec is bound to a field — via with_field_override, with_type_override, or $Rust.codec on a field — decode_member is called. When it is bound to a struct type via $Rust.codec on the struct declaration, decode_value is called with a builder for that struct.

The default decode_member initialises the field and delegates to decode_value. That works for struct, list and AnyPointer fields, but fails for primitive, text, data and enum fields, because those cannot be initialised. If your codec targets one of those, override decode_member and use set on the parent builder instead:

fn decode_member(
  &self,
  source: &JsonValue,
  mut target: capnp::dynamic_struct::Builder<'_>,
  field: capnp::schema::Field,
) -> capnp::Result<()> {
  let JsonValue::Number(value) = source else {
    return Err(capnp::Error::failed("expected a number".into()));
  };
  target.set(field, (*value as i32).into())
}

Required Methods§

Source

fn encode_value(&self, source: Reader<'_>) -> Result<JsonValue>

Convert a Cap’n Proto value into the JSON that should stand for it.

source is the value being encoded: the field’s value when the codec is bound to a field, or the struct itself when bound to a struct type. For a field of a list type this is called once per element, with source being the element.

The returned JsonValue is serialised by this crate, so no escaping or quoting is needed. Returning JsonValue::DataBuffer is an error.

Source

fn decode_value(&self, source: &JsonValue, target: Builder<'_>) -> Result<()>

Populate an already-created Cap’n Proto value from JSON.

target is a builder for the value itself, not for its parent; use decode_member if you need to create the value rather than fill it in.

This is the method called for codecs bound to a struct type via $Rust.codec, and — through the default decode_member — for codecs bound to struct, list and AnyPointer fields.

Provided Methods§

Source

fn decode_member( &self, source: &JsonValue, target: Builder<'_>, field: Field, ) -> Result<()>

Write one field of a struct from JSON.

Called when this codec is bound to a field. target is the containing struct’s builder and field identifies the field to write, so an implementation controls how the value is created — by init for pointer-typed fields, or by set for everything else.

This is only called when the field is actually present in the JSON object; an absent field is left at its default.

The default implementation initialises the field and forwards to decode_value, which is only valid for struct, list and AnyPointer fields — see the note on the trait.

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementations on Foreign Types§

Source§

impl<F, G> FieldCodec for (F, G)
where F: Fn(Reader<'_>) -> Result<JsonValue>, G: Fn(&JsonValue, Builder<'_>) -> Result<()>,

A pair of closures (encode, decode) is a FieldCodec. Usually reached through make_field_codec rather than written out.

Source§

fn encode_value(&self, source: Reader<'_>) -> Result<JsonValue>

Source§

fn decode_value(&self, source: &JsonValue, target: Builder<'_>) -> Result<()>

Source§

impl<T: FieldCodec + ?Sized> FieldCodec for &T

Lets a &T be used wherever a FieldCodec is expected, so one codec instance can be shared between several Codecs. Every method is forwarded, including decode_member, so a codec behaves the same by reference as it does by value.

Source§

fn encode_value(&self, source: Reader<'_>) -> Result<JsonValue>

Source§

fn decode_value(&self, source: &JsonValue, target: Builder<'_>) -> Result<()>

Source§

fn decode_member( &self, source: &JsonValue, target: Builder<'_>, field: Field, ) -> Result<()>

Implementors§