capnp_json/lib.rs
1//! A [Cap'n Proto](https://capnproto.org) JSON codec, implementing the codec
2//! defined in [`json.capnp`].
3//!
4//! The wire format is compatible with the C++ `capnp::JsonCodec` that ships
5//! with Cap'n Proto: messages encoded by this crate can be decoded by the C++
6//! codec, and vice-versa. Encoding and decoding are driven entirely by the
7//! schema's runtime type information, so no per-type derive or code generation
8//! beyond `capnpc` is required.
9//!
10//! # Quick start
11//!
12//! [`to_json`] turns any struct reader into a JSON string, and [`from_json`]
13//! fills a struct builder from one:
14//!
15//! ```ignore
16//! use capnp::message;
17//! use capnp_json::{from_json, to_json};
18//!
19//! let mut builder = message::Builder::new_default();
20//! let root: my_schema_capnp::my_struct::Builder<'_> = builder.init_root();
21//! // ... populate `root` ...
22//!
23//! let json: String = to_json(root.reborrow_as_reader())?;
24//!
25//! let mut decoded = message::Builder::new_default();
26//! let decoded_root: my_schema_capnp::my_struct::Builder<'_> =
27//! decoded.init_root();
28//! from_json(&json, decoded_root)?;
29//! ```
30//!
31//! Both are thin wrappers around [`Codec`], which is what you need as soon as
32//! you want custom encodings for particular fields or types — see
33//! [custom codecs](#custom-codecs) below.
34//!
35//! # Type mapping
36//!
37//! | Cap'n Proto | JSON |
38//! | --- | --- |
39//! | `Void` | `null` |
40//! | `Bool` | `true` / `false` |
41//! | `Int8`/`Int16`/`Int32`, `UInt8`/`UInt16`/`UInt32` | number |
42//! | `Int64`, `UInt64` | **string** holding a decimal integer |
43//! | `Float32`, `Float64` | number, except `NaN`, `Infinity` and `-Infinity`, which are strings |
44//! | `Text` | string |
45//! | `Data` | array of byte-valued numbers, unless `$Json.base64` or `$Json.hex` is applied |
46//! | enum | string naming the enumerant, or a number if the ordinal is not in the schema |
47//! | struct, group | object |
48//! | `List(T)` | array |
49//! | `AnyPointer`, interface | not representable; an error unless a custom codec is registered |
50//!
51//! 64-bit integers are strings because JSON numbers are IEEE-754 doubles and
52//! cannot represent the full 64-bit range exactly. When decoding, both the
53//! string and the number form are accepted for `Int64`/`UInt64`.
54//!
55//! Integer fields are range-checked on decode, as they are in C++: a number
56//! outside the field's range, or one with a fractional part, is an error
57//! rather than being silently clamped or truncated. `300` is not an `Int8`
58//! and `1.9` is not an `Int32`. The same applies to the elements of a `Data`
59//! array, which must be whole numbers in `[0, 255]`.
60//!
61//! Float fields are *not* range-checked, also matching C++: a magnitude too
62//! large for a `Float32` becomes an infinity. Enum ordinals given as numbers
63//! are not checked either.
64//!
65//! On encode, a field is omitted entirely when it is unset — for pointer
66//! fields (text, data, lists, structs) that means a null pointer. On decode, a
67//! field absent from the JSON is left at its schema default, and a JSON field
68//! that does not correspond to anything in the schema is ignored.
69//!
70//! A null pointer and an absent field are the same thing in Cap'n Proto, so a
71//! JSON `null` for a `Text`, `Data`, `List` or struct field means the field
72//! was not set, rather than that it holds an empty value; the field is left
73//! alone. `Void` is the exception, since `null` is its value rather than its
74//! absence, and `Float32`/`Float64` read `null` as `NaN`. For every other type
75//! `null` is an error. This is only true of *fields*: as a list element,
76//! `null` has to be a value, so `[null]` is an error for a list of text. All
77//! of this matches the C++ codec (`isPointerToJsonNull`), though that
78//! behaviour is on its main branch and not in any release yet.
79//!
80//! # JSON annotations
81//!
82//! To use any of the JSON annotations defined in [`json.capnp`], tell `capnpc`
83//! to resolve references to the annotation schema to this crate from your
84//! `build.rs`:
85//!
86//! ```ignore
87//! capnpc::CompilerCommand::new()
88//! .crate_provides("capnp_json", [0x8ef99297a43a5e34])
89//! .file("my_schema.capnp")
90//! .run()
91//! .expect("compiling schema");
92//! ```
93//!
94//! `0x8ef99297a43a5e34` is the file ID of `json.capnp`. The supported
95//! annotations are:
96//!
97//! - **`$Json.name("...")`** — use a different name for a field, enumerant,
98//! group or union member in the JSON representation.
99//! - **`$Json.flatten()`** / **`$Json.flatten(prefix = "p.")`** — splice a
100//! struct, group or union's members directly into the parent object rather
101//! than nesting them, optionally prefixing each name.
102//!
103//! Because a flattened field consumes no JSON nesting, flattening must
104//! terminate: a struct cannot flatten a field of its own type, directly or
105//! through a chain of other flattened fields and groups. [`validate_schema`]
106//! checks this and reports it the way the C++ codec does; encoding and
107//! decoding do not, since a schema is compile-time data and a cycle in one
108//! is a build-time mistake rather than a property of any input. Left
109//! unchecked, a cyclic schema is still rejected when decoded, but by the
110//! recursion limit and with a less pointed message.
111//! - **`$Json.discriminator(name = "kind", valueName = "value")`** — encode
112//! which member of a union is active as a sibling string field rather than
113//! by the presence of the member's own key.
114//! - **`$Json.base64`** / **`$Json.hex`** — encode a `Data` field (or the
115//! elements of a list of `Data`) as a Base64 or hex string instead of an
116//! array of byte values. Applying both to one field is an error, as is
117//! applying either to a field that is not `Data`.
118//!
119//! # Custom codecs
120//!
121//! Some things have no natural JSON form — `AnyPointer` and interface fields
122//! most obviously, but also domain types such as timestamps that you would
123//! rather see as an ISO-8601 string than as a struct. [`Codec`] lets you
124//! supply a [`FieldCodec`] for these, bound in one of three ways:
125//!
126//! - [`Codec::with_field_override`] — for one specific field of one specific
127//! struct.
128//! - [`Codec::with_type_override`] — for every field of a given type.
129//! - [`Codec::with_named_codec`] — for every field or struct tagged
130//! `$Rust.codec("name")` in the schema, which keeps the choice next to the
131//! data it applies to.
132//!
133//! The `$Rust.codec` annotation is defined in this crate's own
134//! `rust-json.capnp` (file ID `0xf955e504bf781ac6`); see
135//! [`Codec::with_named_codec`] for the `build.rs` setup it needs.
136//!
137//! # Compatibility notes
138//!
139//! The following are known divergences from the C++ codec. They matter mostly
140//! when decoding input from an untrusted or third-party producer:
141//!
142//! - Input after the top-level value is ignored rather than rejected.
143//! - A `Data` element above 255 is rejected. C++ intends the same — its error
144//! says "not an integer in [0, 255]" — but implements the bound as
145//! `byte(x) == x`, whose out-of-range `double`-to-`byte` conversion is
146//! undefined behaviour; in practice it accepts `256`, `300` and `511` and
147//! stores them modulo 256. Being stricter cannot break round-tripping,
148//! since the C++ encoder never emits such a value.
149//! - `\uXXXX` surrogate pairs are combined into the character they denote.
150//! C++ decodes each escape separately and produces WTF-8 — the surrogates
151//! encoded individually, which is not valid UTF-8 and which a Rust `String`
152//! cannot hold — and notes as much in a TODO. Unpaired surrogates are
153//! rejected here rather than replaced. This cannot affect round-tripping:
154//! the C++ encoder writes non-BMP characters as literal UTF-8, never as
155//! escapes.
156//! - Only `Int64`/`UInt64` accept the string form of an integer on decode;
157//! C++ accepts it for every integer width.
158//! - Duplicate keys within one JSON object are rejected.
159//! - Floats are written in Rust's `Display` form, which never uses exponent
160//! notation: `1e300` is emitted as 301 digits rather than as `1e300`. Both
161//! parse back to the same value.
162//!
163//! Output is not pretty-printed, and the `Value` / `Call` / `raw` extensions
164//! from `json.capnp` are not implemented.
165//!
166//! [`json.capnp`]: https://github.com/capnproto/capnproto/blob/master/c%2B%2B/src/capnp/compat/json.capnp
167
168#![warn(missing_docs)]
169
170use std::collections::{BTreeMap, HashMap};
171
172mod data;
173mod decode;
174mod encode;
175mod validate;
176
177#[allow(missing_docs)]
178mod schema {
179 capnp::generated_code!(pub mod json_capnp);
180}
181
182// The generated code for schemas annotated with `$Rust.codec` refers to this
183// module by path, so it has to be public even though it is not part of the
184// hand-written API. See `Codec::with_named_codec`.
185#[allow(missing_docs)]
186mod rust_json_schema {
187 capnp::generated_code!(pub mod rust_json_capnp);
188}
189
190#[doc(hidden)]
191pub use rust_json_schema::rust_json_capnp;
192#[doc(hidden)]
193pub use schema::json_capnp;
194
195#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
196enum DataEncoding {
197 #[default]
198 Default,
199 Base64,
200 Hex,
201}
202
203struct EncodingOptions<'schema, 'prefix> {
204 prefix: &'prefix std::borrow::Cow<'schema, str>,
205 name: &'schema str,
206 field: Option<capnp::schema::Field>,
207 flatten: Option<json_capnp::flatten_options::Reader<'schema>>,
208 discriminator: Option<json_capnp::discriminator_options::Reader<'schema>>,
209 data_encoding: DataEncoding,
210 codec: Option<&'schema str>,
211}
212
213impl Default for EncodingOptions<'_, '_> {
214 fn default() -> Self {
215 Self {
216 prefix: &std::borrow::Cow::Borrowed(""),
217 name: "",
218 field: None,
219 flatten: None,
220 discriminator: None,
221 data_encoding: DataEncoding::Default,
222 codec: None,
223 }
224 }
225}
226
227impl<'schema, 'prefix> EncodingOptions<'schema, 'prefix> {
228 fn from_field(
229 prefix: &'prefix std::borrow::Cow<'schema, str>,
230 field: capnp::schema::Field,
231 ) -> capnp::Result<Self> {
232 let mut options = Self {
233 prefix,
234 name: field.get_proto().get_name()?.to_str()?,
235 field: Some(field),
236 flatten: None,
237 discriminator: None,
238 data_encoding: DataEncoding::Default,
239 codec: None,
240 };
241
242 for anno in field.get_annotations()?.iter() {
243 match anno.get_id() {
244 rust_json_capnp::codec::ID => {
245 options.codec = Some(
246 anno
247 .get_value()?
248 .downcast::<capnp::text::Reader>()
249 .to_str()?,
250 );
251 }
252 json_capnp::name::ID => {
253 options.name = anno
254 .get_value()?
255 .downcast::<capnp::text::Reader>()
256 .to_str()?;
257 }
258 json_capnp::base64::ID => {
259 if options.data_encoding != DataEncoding::Default {
260 return Err(capnp::Error::failed(
261 "Cannot specify both base64 and hex annotations on the same field"
262 .into(),
263 ));
264 }
265 options.data_encoding = DataEncoding::Base64;
266 }
267 json_capnp::hex::ID => {
268 if options.data_encoding != DataEncoding::Default {
269 return Err(capnp::Error::failed(
270 "Cannot specify both base64 and hex annotations on the same field"
271 .into(),
272 ));
273 }
274 options.data_encoding = DataEncoding::Hex;
275 }
276 json_capnp::flatten::ID => {
277 options.flatten = Some(
278 anno
279 .get_value()?
280 .downcast_struct::<json_capnp::flatten_options::Owned>(),
281 );
282 }
283 json_capnp::discriminator::ID => {
284 options.discriminator = Some(
285 anno
286 .get_value()?
287 .downcast_struct::<json_capnp::discriminator_options::Owned>(),
288 );
289 }
290 _ => {}
291 }
292 }
293 if options.data_encoding != DataEncoding::Default {
294 let mut element_type = field.get_type();
295 while let capnp::introspect::TypeVariant::List(sub_element_type) =
296 element_type.which()
297 {
298 element_type = sub_element_type;
299 }
300 if !matches!(element_type.which(), capnp::introspect::TypeVariant::Data) {
301 return Err(capnp::Error::failed(
302 "base64/hex annotation can only be applied to Data fields".into(),
303 ));
304 }
305 }
306 Ok(options)
307 }
308}
309
310/// Check that a schema can be represented as JSON at all.
311///
312/// At present this means checking that `$Json.flatten` terminates. A flattened
313/// field splices its members into the parent's JSON object instead of nesting
314/// them, so a struct that flattens a field of its own type — directly, or
315/// through a chain of other flattened fields and groups — describes an object
316/// of infinite width. The C++ codec rejects such a schema outright with
317/// "cyclic JSON flattening detected"; this function is how you ask for the
318/// same verdict.
319///
320/// `T` is the generated `Owned` type of the struct you encode or decode as the
321/// root. Every struct reachable from it is checked too — including through
322/// plain fields and list element types — so validating the root type covers
323/// the whole message.
324///
325/// # When to call this
326///
327/// Once, at startup or from a test — not per message. A schema is compile-time
328/// data: `capnpc` generates it and nothing can change it at runtime, so a
329/// cyclic flatten is a mistake in your `.capnp` file rather than a property of
330/// any particular input. Encoding and decoding therefore do *not* run this
331/// check, and paying for it on every call would be a permanent tax to
332/// re-discover a build-time bug.
333///
334/// ```ignore
335/// #[test]
336/// fn schema_is_json_encodable() {
337/// capnp_json::validate_schema::<my_schema_capnp::my_struct::Owned>()
338/// .expect("schema must be JSON-encodable");
339/// }
340/// ```
341///
342/// Skipping it is not dangerous, only less informative: a cyclic schema still
343/// gets rejected when decoded, by the recursion limit
344/// ([`CodecOptions::recursion_limit`]), just with a message that points at the
345/// depth rather than at the cycle.
346pub fn validate_schema<T: capnp::traits::OwnedStruct>() -> capnp::Result<()> {
347 // `OwnedStruct` is only implemented for struct types, so the other variants
348 // are unreachable; report rather than panic if that ever stops holding.
349 let capnp::introspect::TypeVariant::Struct(raw) = T::introspect().which()
350 else {
351 return Err(capnp::Error::failed(
352 "validate_schema requires a struct type".into(),
353 ));
354 };
355 validate::check_flattening_terminates(capnp::schema::StructSchema::new(raw))
356}
357
358/// Encode a Cap'n Proto struct as a JSON string.
359///
360/// `reader` accepts anything that converts into a
361/// [`capnp::dynamic_value::Reader`] — typically a struct reader obtained from
362/// `message::Reader::get_root()` or `message::Builder::reborrow_as_reader()`.
363/// The value must be a struct, since the top-level JSON value is an object.
364///
365/// The mapping from Cap'n Proto values to JSON, and the effect of the
366/// `$Json.*` annotations, are described in the
367/// [module documentation](crate#type-mapping); all of it matches the C++
368/// `capnp::JsonCodec`. The output is compact, with no insignificant
369/// whitespace.
370///
371/// This is [`Codec::new().encode(reader)`](Codec::encode). Use a [`Codec`]
372/// directly to register custom [`FieldCodec`]s — which is required for
373/// messages containing `AnyPointer` or interface fields.
374///
375/// ```ignore
376/// let json: String = capnp_json::to_json(root.reborrow_as_reader())?;
377/// ```
378pub fn to_json<'msg>(
379 reader: impl Into<capnp::dynamic_value::Reader<'msg>>,
380) -> capnp::Result<String> {
381 Codec::new().encode(reader)
382}
383
384/// Decode a JSON string into a Cap'n Proto struct builder.
385///
386/// `builder` accepts anything that converts into a
387/// [`capnp::dynamic_value::Builder`]; it must be a struct builder, since JSON
388/// objects map to Cap'n Proto structs. The value mapping and annotations are
389/// the same as for [`to_json`].
390///
391/// Fields absent from the JSON keep whatever `builder` already holds, and
392/// JSON fields with no counterpart in the schema are ignored.
393///
394/// Returns an error if `json` is malformed, if the top-level JSON value is
395/// not an object, or if any field's value cannot be coerced to its declared
396/// Cap'n Proto type. On error, `builder` may have been partially populated.
397///
398/// This is [`Codec::new().decode(json, builder)`](Codec::decode). Read
399/// [the compatibility notes](crate#compatibility-notes) before decoding input
400/// you do not control.
401///
402/// ```ignore
403/// capnp_json::from_json(&json, root)?;
404/// ```
405pub fn from_json<'segments>(
406 json: &str,
407 builder: impl Into<capnp::dynamic_value::Builder<'segments>>,
408) -> capnp::Result<()> {
409 Codec::new().decode(json, builder)
410}
411
412/// An in-memory JSON value.
413///
414/// This is the currency of the [`FieldCodec`] trait: a codec produces a
415/// `JsonValue` when encoding and is handed one when decoding. The codec itself
416/// never deals with JSON syntax — serialising and parsing are handled by this
417/// crate.
418///
419/// ```
420/// use capnp_json::JsonValue;
421///
422/// let value = JsonValue::Array(vec![
423/// JsonValue::String("hello".into()),
424/// JsonValue::Number(42.0),
425/// JsonValue::Null,
426/// ]);
427/// assert_eq!(value, value.clone());
428/// ```
429///
430/// # Numbers
431///
432/// [`Number`](JsonValue::Number) is an `f64`, matching JSON's own numeric
433/// model. A codec that needs the full 64-bit integer range should encode to
434/// [`String`](JsonValue::String), which is what this crate does for `Int64`
435/// and `UInt64` fields.
436///
437/// # Object ordering
438///
439/// [`Object`](JsonValue::Object) is a [`BTreeMap`], so its members are written
440/// in sorted key order. JSON objects are unordered by definition, so no order
441/// is more correct than another, but a *deterministic* one matters: the same
442/// value must encode to the same bytes every time, or golden-file tests,
443/// response caching and anything signing the output stop working. Sorted order
444/// is also the canonical form specified by RFC 8785.
445///
446/// Members of *schema* structs never pass through this map — they are written
447/// in schema declaration order — so this affects only the objects a custom
448/// [`FieldCodec`] builds.
449///
450/// Duplicate keys are rejected when parsing, so an `Object` decoded by this
451/// crate never loses a member.
452// FIXME: The String valued below could be Cow<'input, str> as they only really
453// need to be allocated if the input contains escaped characters. That would be
454// a little more tricky lower down, but not by a lot.
455#[derive(Debug, Clone, PartialEq)]
456pub enum JsonValue {
457 /// JSON `null`. Also the encoding of a Cap'n Proto `Void`.
458 Null,
459 /// JSON `true` or `false`.
460 Boolean(bool),
461 /// A JSON number. See [the note on numbers](JsonValue#numbers).
462 Number(f64),
463 /// A JSON string, already unescaped.
464 String(String),
465 /// A JSON array.
466 Array(Vec<JsonValue>),
467 /// A JSON object. See [the note on ordering](JsonValue#object-ordering).
468 Object(BTreeMap<String, JsonValue>),
469
470 /// Internal scratch space used while decoding `Data` fields; not part of
471 /// the JSON data model.
472 ///
473 /// Decoding a `Data` field has to hand out a `capnp::data::Reader`
474 /// borrowing from somewhere, and the decoded bytes have no other home, so
475 /// they are parked in the [`JsonValue`] being decoded. A [`FieldCodec`]
476 /// will never be given this variant and should never construct one; treat
477 /// it as an unreachable case.
478 // FIXME: Remove this from the public type and use a wrapper inside decode
479 #[doc(hidden)]
480 DataBuffer(Vec<u8>),
481}
482
483/// A custom JSON representation for a single Cap'n Proto value.
484///
485/// Implement this to override how a field, or every value of a given type, is
486/// converted to and from JSON. It is *required* for `AnyPointer` and interface
487/// fields, which carry no schema the codec could drive itself; it is merely
488/// useful for everything else, when the default mapping is not the shape you
489/// want on the wire.
490///
491/// A `FieldCodec` is attached to a [`Codec`] by one of
492/// [`with_field_override`](Codec::with_field_override),
493/// [`with_type_override`](Codec::with_type_override) or
494/// [`with_named_codec`](Codec::with_named_codec).
495///
496/// # Implementing
497///
498/// The trait has two required methods and one that you will need to override
499/// more often than its default suggests:
500///
501/// - [`encode_value`](FieldCodec::encode_value) is handed the Cap'n Proto
502/// value and returns the [`JsonValue`] to write in its place.
503/// - [`decode_value`](FieldCodec::decode_value) is handed a parsed
504/// [`JsonValue`] and a builder already positioned at the target value, and
505/// populates it.
506/// - [`decode_member`](FieldCodec::decode_member) is handed the *parent*
507/// struct builder plus the field to write, and so gets to decide how the
508/// target is created.
509///
510/// Which of the two decode methods is called depends on how the codec is
511/// bound — see [the note below](#which-decode-method-is-called).
512///
513/// For simple cases a pair of closures is easier than a named type; see
514/// [`make_field_codec`].
515///
516/// ```
517/// use capnp_json::{FieldCodec, JsonValue};
518///
519/// /// Encodes a struct with `seconds`/`nanos` fields as a single number.
520/// struct Timestamp;
521///
522/// impl FieldCodec for Timestamp {
523/// fn encode_value(
524/// &self,
525/// source: capnp::dynamic_value::Reader<'_>,
526/// ) -> capnp::Result<JsonValue> {
527/// let source: capnp::dynamic_struct::Reader<'_> = source.downcast();
528/// let seconds: i64 = source.get_named("seconds")?.downcast();
529/// let nanos: i64 = source.get_named("nanos")?.downcast();
530/// Ok(JsonValue::Number(seconds as f64 + nanos as f64 / 1e9))
531/// }
532///
533/// fn decode_value(
534/// &self,
535/// source: &JsonValue,
536/// target: capnp::dynamic_value::Builder<'_>,
537/// ) -> capnp::Result<()> {
538/// let JsonValue::Number(value) = source else {
539/// return Err(capnp::Error::failed("expected a number".into()));
540/// };
541/// let mut target: capnp::dynamic_struct::Builder<'_> = target.downcast();
542/// target.set_named("seconds", (value.trunc() as i64).into())?;
543/// target.set_named("nanos", ((value.fract() * 1e9) as i64).into())?;
544/// Ok(())
545/// }
546/// }
547/// ```
548///
549/// # Which decode method is called
550///
551/// When the codec is bound to a *field* — via `with_field_override`,
552/// `with_type_override`, or `$Rust.codec` on a field —
553/// [`decode_member`](FieldCodec::decode_member) is called. When it is bound to
554/// a *struct type* via `$Rust.codec` on the struct declaration,
555/// [`decode_value`](FieldCodec::decode_value) is called with a builder for
556/// that struct.
557///
558/// The default `decode_member` initialises the field and delegates to
559/// `decode_value`. That works for struct, list and `AnyPointer` fields, but
560/// **fails for primitive, text, data and enum fields**, because those cannot
561/// be `init`ialised. If your codec targets one of those, override
562/// `decode_member` and use `set` on the parent builder instead:
563///
564/// ```
565/// # use capnp_json::{FieldCodec, JsonValue};
566/// # struct Celsius;
567/// # impl FieldCodec for Celsius {
568/// # fn encode_value(&self, _: capnp::dynamic_value::Reader<'_>)
569/// # -> capnp::Result<JsonValue> { Ok(JsonValue::Null) }
570/// # fn decode_value(&self, _: &JsonValue, _: capnp::dynamic_value::Builder<'_>)
571/// # -> capnp::Result<()> { Ok(()) }
572/// fn decode_member(
573/// &self,
574/// source: &JsonValue,
575/// mut target: capnp::dynamic_struct::Builder<'_>,
576/// field: capnp::schema::Field,
577/// ) -> capnp::Result<()> {
578/// let JsonValue::Number(value) = source else {
579/// return Err(capnp::Error::failed("expected a number".into()));
580/// };
581/// target.set(field, (*value as i32).into())
582/// }
583/// # }
584/// ```
585pub trait FieldCodec {
586 /// Convert a Cap'n Proto value into the JSON that should stand for it.
587 ///
588 /// `source` is the value being encoded: the field's value when the codec is
589 /// bound to a field, or the struct itself when bound to a struct type. For
590 /// a field of a list type this is called once per element, with `source`
591 /// being the element.
592 ///
593 /// The returned [`JsonValue`] is serialised by this crate, so no escaping
594 /// or quoting is needed. Returning [`JsonValue::DataBuffer`] is an error.
595 fn encode_value(
596 &self,
597 source: capnp::dynamic_value::Reader<'_>,
598 ) -> capnp::Result<JsonValue>;
599
600 /// Populate an already-created Cap'n Proto value from JSON.
601 ///
602 /// `target` is a builder for the value itself, not for its parent; use
603 /// [`decode_member`](FieldCodec::decode_member) if you need to create the
604 /// value rather than fill it in.
605 ///
606 /// This is the method called for codecs bound to a struct type via
607 /// `$Rust.codec`, and — through the default `decode_member` — for codecs
608 /// bound to struct, list and `AnyPointer` fields.
609 fn decode_value(
610 &self,
611 source: &JsonValue,
612 target: capnp::dynamic_value::Builder<'_>,
613 ) -> capnp::Result<()>;
614
615 /// Write one field of a struct from JSON.
616 ///
617 /// Called when this codec is bound to a field. `target` is the *containing*
618 /// struct's builder and `field` identifies the field to write, so an
619 /// implementation controls how the value is created — by `init` for
620 /// pointer-typed fields, or by `set` for everything else.
621 ///
622 /// This is only called when the field is actually present in the JSON
623 /// object; an absent field is left at its default.
624 ///
625 /// The default implementation initialises the field and forwards to
626 /// [`decode_value`](FieldCodec::decode_value), which is only valid for
627 /// struct, list and `AnyPointer` fields — see
628 /// [the note on the trait](FieldCodec#which-decode-method-is-called).
629 fn decode_member(
630 &self,
631 source: &JsonValue,
632 target: capnp::dynamic_struct::Builder<'_>,
633 field: capnp::schema::Field,
634 ) -> capnp::Result<()> {
635 self.decode_value(source, target.init(field)?)
636 }
637}
638
639/// Lets a `&T` be used wherever a [`FieldCodec`] is expected, so one codec
640/// instance can be shared between several [`Codec`]s. Every method is
641/// forwarded, including [`decode_member`](FieldCodec::decode_member), so a
642/// codec behaves the same by reference as it does by value.
643impl<T: FieldCodec + ?Sized> FieldCodec for &T {
644 fn encode_value(
645 &self,
646 source: capnp::dynamic_value::Reader<'_>,
647 ) -> capnp::Result<JsonValue> {
648 (**self).encode_value(source)
649 }
650 fn decode_value(
651 &self,
652 source: &JsonValue,
653 target: capnp::dynamic_value::Builder<'_>,
654 ) -> capnp::Result<()> {
655 (**self).decode_value(source, target)
656 }
657 fn decode_member(
658 &self,
659 source: &JsonValue,
660 target: capnp::dynamic_struct::Builder<'_>,
661 field: capnp::schema::Field,
662 ) -> capnp::Result<()> {
663 (**self).decode_member(source, target, field)
664 }
665}
666
667/// A pair of closures `(encode, decode)` is a [`FieldCodec`]. Usually reached
668/// through [`make_field_codec`] rather than written out.
669// implement FieldCodec for any (fn, fn) pair that matches the signature
670impl<F, G> FieldCodec for (F, G)
671where
672 F: Fn(capnp::dynamic_value::Reader<'_>) -> capnp::Result<JsonValue>,
673 G: Fn(&JsonValue, capnp::dynamic_value::Builder<'_>) -> capnp::Result<()>,
674{
675 fn encode_value(
676 &self,
677 source: capnp::dynamic_value::Reader<'_>,
678 ) -> capnp::Result<JsonValue> {
679 (self.0)(source)
680 }
681 fn decode_value(
682 &self,
683 source: &JsonValue,
684 target: capnp::dynamic_value::Builder<'_>,
685 ) -> capnp::Result<()> {
686 (self.1)(source, target)
687 }
688}
689
690/// Build a [`FieldCodec`] from an encoder and a decoder closure.
691///
692/// A shorthand for the cases that do not need a named type — the two closures
693/// correspond to [`FieldCodec::encode_value`] and
694/// [`FieldCodec::decode_value`]. The resulting codec uses the default
695/// [`decode_member`](FieldCodec::decode_member), so it is only suitable for
696/// struct, list and `AnyPointer` fields; for a primitive, text, data or enum
697/// field, implement [`FieldCodec`] directly and override `decode_member`.
698///
699/// ```
700/// use capnp_json::{make_field_codec, JsonValue};
701///
702/// // Represent a struct's `text` field as the whole JSON value.
703/// let codec = make_field_codec(
704/// |source: capnp::dynamic_value::Reader<'_>| {
705/// let source: capnp::dynamic_struct::Reader<'_> = source.downcast();
706/// let text: capnp::text::Reader<'_> = source.get_named("text")?.downcast();
707/// Ok(JsonValue::String(text.to_str()?.to_owned()))
708/// },
709/// |source: &JsonValue, target: capnp::dynamic_value::Builder<'_>| {
710/// let JsonValue::String(text) = source else {
711/// return Err(capnp::Error::failed("expected a string".into()));
712/// };
713/// let mut target: capnp::dynamic_struct::Builder<'_> = target.downcast();
714/// target.set_named("text", text.as_str().into())
715/// },
716/// );
717/// # let _ = codec;
718/// ```
719pub fn make_field_codec<'env>(
720 encode_fn: impl Fn(capnp::dynamic_value::Reader<'_>) -> capnp::Result<JsonValue>
721 + 'env,
722 decode_fn: impl Fn(&JsonValue, capnp::dynamic_value::Builder<'_>) -> capnp::Result<()>
723 + 'env,
724) -> impl FieldCodec + 'env {
725 (encode_fn, decode_fn)
726}
727
728/// Encoding and decoding options for a [`Codec`].
729///
730/// Construct with [`Default`] and adjust what you need, so that options added
731/// in future versions keep their defaults:
732///
733/// ```
734/// use capnp_json::{Codec, CodecOptions};
735///
736/// let codec = Codec::new_with_options(CodecOptions {
737/// recursion_limit: 32,
738/// ..Default::default()
739/// });
740/// # let _ = codec;
741/// ```
742#[derive(Debug, Clone, PartialEq, Eq)]
743pub struct CodecOptions {
744 /// How deeply decoding will recurse before giving up. Defaults to 64,
745 /// matching the C++ codec's `maxNestingDepth`.
746 ///
747 /// Decoding is recursive, so without a bound, deeply nested input exhausts
748 /// the stack and aborts the process — which is not a catchable error in
749 /// Rust. The limit turns that into an ordinary `Err`.
750 ///
751 /// It bounds two things: the nesting depth of the JSON itself, and the
752 /// depth of the walk over the schema. The second is not implied by the
753 /// first, because a struct that flattens a field of its own type recurses
754 /// on the schema without descending into the JSON at all.
755 ///
756 /// A limit of `N` admits `N` nested arrays or objects. Scalars do not count
757 /// against it, so the boundary is the same one the C++ codec applies at the
758 /// same numeric setting.
759 ///
760 /// **Raising this reintroduces the crash it exists to prevent.** The safe
761 /// ceiling depends on your build profile and the stack size of the thread
762 /// doing the decoding; measured on a 1 MiB stack, decoding survived depth
763 /// 1600 in release but overflowed at depth 200 in debug. The default is
764 /// comfortably safe in both. Lowering it is always safe.
765 pub recursion_limit: usize,
766}
767
768impl Default for CodecOptions {
769 fn default() -> Self {
770 Self {
771 recursion_limit: 64,
772 }
773 }
774}
775
776/// A JSON codec for Cap'n Proto messages.
777///
778/// A `Codec` holds the custom [`FieldCodec`]s to apply while encoding and
779/// decoding. If you need none, [`to_json`] and [`from_json`] are equivalent to
780/// `Codec::new().encode(..)` and `Codec::new().decode(..)` and are more
781/// convenient.
782///
783/// A `Codec` is built up with the `with_*` methods, which consume and return
784/// it:
785///
786/// ```
787/// use capnp_json::{make_field_codec, Codec, JsonValue};
788///
789/// let codec = Codec::new().with_named_codec(
790/// "shouty",
791/// make_field_codec(
792/// |source: capnp::dynamic_value::Reader<'_>| {
793/// let text: capnp::text::Reader<'_> = source.downcast();
794/// Ok(JsonValue::String(text.to_str()?.to_uppercase()))
795/// },
796/// |_source: &JsonValue, _target: capnp::dynamic_value::Builder<'_>| Ok(()),
797/// ),
798/// );
799/// # let _ = codec;
800/// ```
801///
802/// The `'env` lifetime is that of the data borrowed by the registered codecs;
803/// for codecs that own everything they use, it is `'static`.
804///
805/// # Reuse and threads
806///
807/// Encoding and decoding take `&self` and keep no state between calls, so one
808/// `Codec` serves any number of messages and building one is cheap. Reuse it
809/// if it is convenient; nothing is lost by not doing so.
810///
811/// A `Codec` is neither `Send` nor `Sync`, so give each thread its own. In
812/// async code that means building one where it is used rather than holding it
813/// in shared state: `Codec::new()` costs a handful of nanoseconds, so the only
814/// reason to keep one alive is the custom [`FieldCodec`]s registered on it. If
815/// you need those in shared state, store something `Send + Sync` that builds
816/// the codec — a factory closure — and call it per request, or keep one in a
817/// [`thread_local!`](std::thread_local).
818///
819/// This is not a bound that could simply be added. It comes from the schema
820/// types: `field_overrides` is keyed on [`capnp::schema::Field`], which holds
821/// readers over the generated schema data, and those hold raw pointers. The
822/// same is true of [`capnp::dynamic_value::Reader`] and its `Builder`, so the
823/// *values* being encoded are `!Send` too — an encode or decode is inherently
824/// one synchronous stretch, and a `Send` `Codec` would only ever buy the
825/// ability to store one, never to await part-way through.
826///
827/// # Which codec wins
828///
829/// At most one [`FieldCodec`] applies to any given value. When encoding or
830/// decoding a struct field, the first match of the following is used:
831///
832/// 1. a `$Rust.codec("name")` annotation on the field, resolved against the
833/// names registered with [`with_named_codec`](Codec::with_named_codec);
834/// 2. a [`with_field_override`](Codec::with_field_override) registered for
835/// exactly that field;
836/// 3. a [`with_type_override`](Codec::with_type_override) registered for the
837/// field's declared type.
838///
839/// If none matches and the value is a struct, a `$Rust.codec("name")`
840/// annotation on the *struct's own declaration* is used if one is present.
841///
842/// A `$Rust.codec` name that is not registered is silently ignored and the
843/// default encoding applies.
844///
845/// # Scope of overrides
846///
847/// Both overrides are matched against a *field*, which has two consequences
848/// worth knowing:
849///
850/// - Neither applies to the root value passed to [`encode`](Codec::encode) or
851/// [`decode`](Codec::decode), since that is not reached through a field.
852/// Use a `$Rust.codec` annotation on the struct declaration for that.
853/// - For a field of type `List(T)`, a type override must be registered for
854/// `List(T)` rather than for `T` — one registered for `T` will not fire for
855/// the elements. A `$Rust.codec` annotation on a list field behaves the
856/// other way round: it is applied to each element in turn.
857pub struct Codec<'env> {
858 field_overrides: HashMap<capnp::schema::Field, Box<dyn FieldCodec + 'env>>,
859 type_overrides: HashMap<capnp::introspect::Type, Box<dyn FieldCodec + 'env>>,
860 registry: HashMap<String, Box<dyn FieldCodec + 'env>>,
861
862 options: CodecOptions,
863}
864
865impl<'env> Codec<'env> {
866 /// Create a codec with no custom [`FieldCodec`]s registered.
867 ///
868 /// The result encodes and decodes exactly as [`to_json`] and [`from_json`]
869 /// do.
870 pub fn new() -> Self {
871 Self::new_with_options(CodecOptions::default())
872 }
873
874 /// Create a codec with no custom [`FieldCodec`]s registered, and the given
875 /// [`CodecOptions`].
876 ///
877 /// Equivalent to [`new`](Codec::new) other than the options; read
878 /// [`CodecOptions::recursion_limit`] before raising the recursion limit.
879 pub fn new_with_options(options: CodecOptions) -> Self {
880 Self {
881 field_overrides: HashMap::new(),
882 type_overrides: HashMap::new(),
883 registry: HashMap::new(),
884 options,
885 }
886 }
887
888 /// Use `codec` for one specific field of one specific struct type.
889 ///
890 /// `field` is obtained from the field's containing
891 /// [`StructSchema`](capnp::schema::StructSchema), which in turn comes from
892 /// the generated `Owned` type:
893 ///
894 /// ```ignore
895 /// use capnp::introspect::Introspect;
896 ///
897 /// let capnp::introspect::TypeVariant::Struct(schema) =
898 /// my_schema_capnp::my_struct::Owned::introspect().which()
899 /// else {
900 /// unreachable!("my_struct is a struct");
901 /// };
902 /// let field = capnp::schema::StructSchema::new(schema)
903 /// .get_field_by_name("myField")?;
904 ///
905 /// let codec = Codec::new().with_field_override(field, MyFieldCodec);
906 /// ```
907 ///
908 /// This is the most specific binding and takes precedence over
909 /// [`with_type_override`](Codec::with_type_override); see
910 /// [which codec wins](Codec#which-codec-wins). Registering a second codec
911 /// for the same field replaces the first.
912 pub fn with_field_override(
913 mut self,
914 field: capnp::schema::Field,
915 codec: impl FieldCodec + 'env,
916 ) -> Self {
917 self.field_overrides.insert(field, Box::new(codec));
918 self
919 }
920
921 /// Use `codec` for every field whose declared type is `typ`.
922 ///
923 /// The type comes from the generated `Owned` type of whatever you want to
924 /// override:
925 ///
926 /// ```ignore
927 /// use capnp::introspect::Introspect;
928 ///
929 /// let codec = Codec::new()
930 /// .with_type_override(my_schema_capnp::timestamp::Owned::introspect(), Timestamp);
931 /// ```
932 ///
933 /// Matching is on the field's *declared* type, so read
934 /// [the note on scope](Codec#scope-of-overrides) before using this for
935 /// list element types. Registering a second codec for the same type
936 /// replaces the first.
937 pub fn with_type_override(
938 mut self,
939 typ: capnp::introspect::Type,
940 codec: impl FieldCodec + 'env,
941 ) -> Self {
942 self.type_overrides.insert(typ, Box::new(codec));
943 self
944 }
945
946 /// Register `codec` under `name`, for use by `$Rust.codec` annotations.
947 ///
948 /// This puts the choice of representation in the schema, next to the data
949 /// it describes, rather than in the code that builds the [`Codec`]:
950 ///
951 /// ```capnp
952 /// using Rust = import "/rust-json.capnp";
953 ///
954 /// struct Reading {
955 /// takenAt @0 :Int64 $Rust.codec("iso8601");
956 /// }
957 ///
958 /// struct Duration $Rust.codec("iso8601-duration") {
959 /// seconds @0 :Int64;
960 /// }
961 /// ```
962 ///
963 /// ```ignore
964 /// let codec = Codec::new().with_named_codec("iso8601", Iso8601);
965 /// ```
966 ///
967 /// The annotation may be applied to a field or to a struct declaration. On
968 /// a field it takes precedence over both override maps; on a struct it
969 /// applies wherever a value of that struct type is encoded, including at
970 /// the root. A name with no matching registration is ignored and the
971 /// default encoding is used, so a codec registered under the wrong name
972 /// fails silently rather than loudly.
973 ///
974 /// The annotation is declared in this crate's `rust-json.capnp`. Copy that
975 /// file somewhere on your schema import path, and point `capnpc` at this
976 /// crate for its file ID:
977 ///
978 /// ```ignore
979 /// capnpc::CompilerCommand::new()
980 /// .crate_provides("capnp_json", [0xf955e504bf781ac6])
981 /// .file("my_schema.capnp")
982 /// .run()
983 /// .expect("compiling schema");
984 /// ```
985 ///
986 /// If the schema also uses the `$Json.*` annotations, list both file IDs:
987 /// `[0x8ef99297a43a5e34, 0xf955e504bf781ac6]`.
988 pub fn with_named_codec(
989 mut self,
990 name: impl Into<String>,
991 codec: impl FieldCodec + 'env,
992 ) -> Self {
993 self.registry.insert(name.into(), Box::new(codec));
994 self
995 }
996
997 /// Encode a Cap'n Proto struct as a JSON string.
998 ///
999 /// The value mapping is described in the
1000 /// [module documentation](crate#type-mapping). Returns an error if `reader`
1001 /// is not a struct, if the message contains an `AnyPointer` or interface
1002 /// field with no codec registered for it, or if a registered
1003 /// [`FieldCodec`] fails.
1004 ///
1005 /// Use [`encode_to`](Codec::encode_to) to write into an existing buffer or
1006 /// stream instead of allocating a `String`.
1007 pub fn encode<'msg>(
1008 &self,
1009 reader: impl Into<capnp::dynamic_value::Reader<'msg>>,
1010 ) -> capnp::Result<String> {
1011 let mut writer = std::io::Cursor::new(Vec::with_capacity(4096));
1012 self.encode_to(&mut writer, reader)?;
1013 String::from_utf8(writer.into_inner()).map_err(|e| {
1014 capnp::Error::failed(format!(
1015 "Failed to convert JSON bytes to string: {}",
1016 e
1017 ))
1018 })
1019 }
1020
1021 /// Encode a Cap'n Proto struct as JSON, writing it to `writer`.
1022 ///
1023 /// Behaves as [`encode`](Codec::encode) but streams the output, so it does
1024 /// not hold the whole document in memory. The JSON is written as UTF-8.
1025 ///
1026 /// Output is written in many small pieces, so wrap unbuffered destinations
1027 /// such as `File` or `TcpStream` in a [`BufWriter`](std::io::BufWriter).
1028 ///
1029 /// If an error occurs part-way through, a partial document will already
1030 /// have been written.
1031 pub fn encode_to<'msg, W: std::io::Write>(
1032 &self,
1033 writer: &mut W,
1034 reader: impl Into<capnp::dynamic_value::Reader<'msg>>,
1035 ) -> capnp::Result<()> {
1036 let capnp::dynamic_value::Reader::Struct(reader) = reader.into() else {
1037 return Err(capnp::Error::failed(
1038 "Top-level value must be a struct".into(),
1039 ));
1040 };
1041 encode::serialize_json_to(self, writer, reader)
1042 }
1043
1044 /// Decode a JSON string into a Cap'n Proto struct builder.
1045 ///
1046 /// The top-level JSON value must be an object, and `builder` must therefore
1047 /// be a struct builder. Fields absent from the JSON keep the values already
1048 /// in `builder`; JSON fields with no counterpart in the schema are ignored.
1049 /// `builder` is not cleared first, so decoding into a builder that has
1050 /// already been populated merges into it.
1051 ///
1052 /// Returns an error if `json` is malformed, if a value cannot be coerced to
1053 /// its field's declared type, or if a registered [`FieldCodec`] fails. On
1054 /// error, `builder` may have been partially populated.
1055 ///
1056 /// Read [the compatibility notes](crate#compatibility-notes) before
1057 /// decoding input you do not control — in particular, deeply nested JSON
1058 /// can exhaust the stack.
1059 pub fn decode<'segments>(
1060 &self,
1061 json: &str,
1062 builder: impl Into<capnp::dynamic_value::Builder<'segments>>,
1063 ) -> capnp::Result<()> {
1064 let capnp::dynamic_value::Builder::Struct(builder) = builder.into() else {
1065 return Err(capnp::Error::failed(
1066 "Top-level JSON value must be an object".into(),
1067 ));
1068 };
1069 decode::parse(self, json, builder)
1070 }
1071}
1072
1073impl Default for Codec<'_> {
1074 fn default() -> Self {
1075 Self::new()
1076 }
1077}