Skip to main content

structio/
options.rs

1//! Compile-time options, for reading and for writing.
2//!
3//! Options are a *type*, not a value. [`Options`] carries one associated
4//! constant per setting, a policy type implements it, and the parsers and
5//! writers are generic over that type. Every setting is therefore a `const`
6//! inside the code that consults it, so the branch folds away before the
7//! optimizer ever sees it and the unselected behaviour costs no code at all.
8//!
9//! This is the shape Glaze's `glz::opts` has, arrived at differently. Rust has
10//! no const generic parameter of struct type on stable, so `write<opts{...}>`
11//! has no direct translation; a trait with defaulted associated constants
12//! gets the same zero-cost dispatch, and gets a place to document each
13//! setting besides.
14//!
15//! ```
16//! use structio::{Pretty, to_string, to_string_with};
17//!
18//! # #[derive(Default)]
19//! # struct P { x: i32 }
20//! # structio::object!(P { x });
21//! let p = P { x: 1 };
22//! assert_eq!(to_string(&p), "{\"x\":1}");
23//! assert_eq!(to_string_with::<Pretty, _>(&p), "{\n  \"x\": 1\n}");
24//! ```
25//!
26//! # Writing your own
27//!
28//! The built-in policies below are the common cases. Any combination is a unit
29//! struct and an impl that overrides the constants it cares about; everything
30//! left out keeps its default, so a policy written today keeps compiling when
31//! a later release adds a setting.
32//!
33//! ```
34//! use structio::Options;
35//!
36//! /// Indented four spaces, with absent members left out, and tolerant of a
37//! /// document written against a newer version of the schema.
38//! #[derive(Clone, Copy)]
39//! pub struct Config;
40//!
41//! impl Options for Config {
42//!     const PRETTY: bool = true;
43//!     const INDENT: usize = 4;
44//!     const SKIP_NULL: bool = true;
45//!     const ERROR_ON_UNKNOWN_KEYS: bool = false;
46//! }
47//! ```
48//!
49//! # What it costs
50//!
51//! Code size, in exchange for speed: the read and write paths are compiled
52//! once per policy a program actually uses. One policy costs exactly what no
53//! policy parameter cost before it.
54//!
55//! # Reading and writing
56//!
57//! One trait covers both directions, so a policy names everything a program
58//! does with a document rather than making you carry two. A setting that
59//! belongs to one direction is simply ignored by the other:
60//! [`PRETTY`](Options::PRETTY) means nothing to a parser, and
61//! [`ERROR_ON_UNKNOWN_KEYS`](Options::ERROR_ON_UNKNOWN_KEYS) means nothing to
62//! a writer. Every writing entry point has a `_with` twin and so does every
63//! reading one; [`json::Documents`](crate::json::Documents) and
64//! [`json::Feed`](crate::json::Feed) take theirs from `with_options`, one more
65//! link in the builder chain they already have.
66
67/// How a value is read and written.
68///
69/// Implemented by [`Standard`], [`Pretty`], [`PrettyInlineArrays`],
70/// [`SkipNull`], [`SkipUnknown`], [`RequireKeys`] and [`AllowComments`], and
71/// by any policy type of your own.
72/// Every constant has a default, so an implementation states only what it
73/// changes.
74///
75/// The trait is a marker: no method, no value, and the implementing type is
76/// never constructed. It is named in the writer's type
77/// ([`json::Writer<'_, O>`](crate::json::Writer)) and read through
78/// `O::CONSTANT` at the point of use.
79pub trait Options: Copy {
80    /// Write JSON across multiple lines, indented by nesting depth.
81    ///
82    /// A member's colon gains a trailing space, each member goes on its own
83    /// line and so does each element unless
84    /// [`NEW_LINES_IN_ARRAYS`](Options::NEW_LINES_IN_ARRAYS) is off, and a
85    /// container's closing bracket lines up with the line its opening bracket
86    /// began. An empty container stays on one line as `{}` or `[]`, having
87    /// nothing to indent.
88    ///
89    /// BEVE ignores this: a binary document has no whitespace to put anywhere.
90    const PRETTY: bool = false;
91
92    /// Spaces per level of nesting under [`PRETTY`](Options::PRETTY).
93    ///
94    /// Ignored entirely when `PRETTY` is false.
95    const INDENT: usize = 2;
96
97    /// Give each element of an array a line of its own under
98    /// [`PRETTY`](Options::PRETTY).
99    ///
100    /// On by default, that being what indenting a document usually means.
101    /// Turn it off with [`PrettyInlineArrays`] for documents that are mostly
102    /// numbers: a hundred samples in a `Vec<f64>` is a hundred lines holding
103    /// one number each, where `[3.0, 4.0, 5.0]` says the same thing on one
104    /// line and says it better.
105    ///
106    /// Off, an array writes as `[a, b, c]`: no break after the opening
107    /// bracket or before the closing one, a space after each comma in their
108    /// place, and no level of indentation, since nothing is indented against
109    /// it. Objects are untouched, the ones inside an array included, so an
110    /// array of records opens as `[{` and the members below it are indented
111    /// one level from the line the array began on.
112    ///
113    /// Ignored when `PRETTY` is false, where nothing has a line of its own,
114    /// and by BEVE, which has no whitespace to place at all.
115    const NEW_LINES_IN_ARRAYS: bool = true;
116
117    /// Leave out object members that hold nothing.
118    ///
119    /// That means `None`, `()`, and any wrapper around them: a
120    /// `Box<Option<T>>` holding `None` is as absent as a bare `None`. A field
121    /// left out this way is simply missing from the document, which reading
122    /// treats as "keep what the destination already had", so a round trip
123    /// through `Default::default()` gets the `None` back.
124    ///
125    /// Absence is the test, not the spelling of the output. A `f64` holding
126    /// NaN writes as `null`, JSON having no other form for it, but it is a
127    /// number that is present and it stays. Skipping it would also disagree
128    /// with BEVE, which stores the NaN itself.
129    ///
130    /// **Struct members only.** A `None` inside a sequence still writes
131    /// `null`, because dropping it would shorten the sequence and change what
132    /// every later index means. A map's entries are likewise left alone: a
133    /// null value there is data rather than an absent field, and a map's
134    /// length is not known until it has been walked.
135    ///
136    /// Both formats honour this. BEVE pays slightly more for it than JSON,
137    /// since an object states its member count up front and the count is no
138    /// longer a compile-time constant once members can drop out.
139    const SKIP_NULL: bool = false;
140
141    /// Refuse a document that holds a key no field claims.
142    ///
143    /// On by default, which is Glaze's default too. A key that no field
144    /// claims is far more often a typo, a version skew, or the wrong document
145    /// entirely than it is something to pass over in silence, and silence is
146    /// the one response that cannot be recovered from: the value lands nowhere
147    /// and nothing says so. [`ErrorCode::UnknownKey`](crate::ErrorCode::UnknownKey)
148    /// says so.
149    ///
150    /// Turn it off with [`SkipUnknown`] to read a subset of a larger document,
151    /// or to accept one written by a newer version of a schema.
152    ///
153    /// **Object keys only.** An `array!` struct has no keys, and a length that
154    /// does not match is already an error. A map claims every key it is given
155    /// by definition. An enum's tag is not a key either, and turning this off
156    /// does not make an unrecognized one acceptable: a member with nowhere to
157    /// go can be stepped over and the object still read, but a variant with
158    /// nowhere to go leaves the value itself undecided, so an
159    /// [`ErrorCode::UnknownVariant`](crate::ErrorCode::UnknownVariant) stands
160    /// under every policy.
161    ///
162    /// Both formats honour this, and having it on is cheaper than having it
163    /// off: refusing a key costs one branch, where stepping over its value
164    /// costs a walk proportional to the size of the value, and a value under
165    /// an unknown key can be arbitrarily large. It is also the reading that
166    /// looks at strictly fewer bytes, which is what makes it the safer default
167    /// as well as the faster one.
168    const ERROR_ON_UNKNOWN_KEYS: bool = true;
169
170    /// Refuse a document that leaves a declared field out.
171    ///
172    /// Off by default, where
173    /// [`ERROR_ON_UNKNOWN_KEYS`](Options::ERROR_ON_UNKNOWN_KEYS) is on, and
174    /// the asymmetry is deliberate. Reading is into a value that already
175    /// exists, so a member the document does not mention means "keep what is
176    /// there" rather than "no data": that is what makes
177    /// [`read_into`](crate::read_into) a merge, and a default is a
178    /// perfectly good answer for a field a document had no opinion about.
179    /// Turning this on says the opposite, that the document is the whole
180    /// truth about the value, which is right for a wire format and wrong for
181    /// a patch.
182    ///
183    /// Turn it on with [`RequireKeys`]. What it refuses is an
184    /// [`ErrorCode::MissingKey`](crate::ErrorCode::MissingKey).
185    ///
186    /// **All or nothing, which most schemas are not.** A format with a
187    /// specification usually has mandatory members and optional ones in the
188    /// same object, and neither setting of this fits: off accepts a document
189    /// missing something mandatory, on refuses a valid document that left an
190    /// optional member out. Mark the mandatory ones `#[required]` in the
191    /// declaration instead, which is [`Keys::REQUIRED`](crate::Keys::REQUIRED),
192    /// and leave this alone. The two are a union, so a policy that requires
193    /// everything still does.
194    ///
195    /// **Object keys only**, for the same reasons the unknown-key setting is:
196    /// an `array!` struct is checked by length already, and a map has no
197    /// declared members to miss.
198    ///
199    /// A field of type `Option<T>` is not exempt. The test is whether the
200    /// member is *present*, not what it holds, so `null` satisfies it and
201    /// absence does not. Writing under [`SKIP_NULL`](Options::SKIP_NULL) and
202    /// reading under this therefore contradict each other by construction:
203    /// the writer drops exactly the members the reader insists on.
204    ///
205    /// Both formats honour this. It costs one `or` per member the schema
206    /// claimed and one comparison per object, against a bitmask that never
207    /// leaves a register, and nothing at all when it is off.
208    ///
209    /// **At most 64 fields.** The bookkeeping is one bit per field in a single
210    /// `u64`. A struct with more fields than that read under this option is a
211    /// compile error rather than a wider mask. The cap belongs to the option:
212    /// no other setting looks at the field count, and a struct of any width
213    /// still reads under every other policy. A `#[required]` field needs a bit
214    /// only for itself, so a wider struct may still mark one, as long as what
215    /// it marks is among the first 64 declared.
216    const ERROR_ON_MISSING_KEYS: bool = false;
217
218    /// Read JSONC: `//` to the end of the line, and `/* */`, anywhere
219    /// whitespace is allowed.
220    ///
221    /// Off by default, a comment being no part of JSON. Turn it on with
222    /// [`AllowComments`] for the documents people edit by hand, which is where
223    /// the dialect comes from: a configuration file, a fixture, a schema kept
224    /// under review.
225    ///
226    /// **Reading only.** Nothing writes a comment, because nothing holds one:
227    /// a comment carries no data, so a document read under this and written
228    /// back out comes back without it. **JSON only**, too. BEVE has no
229    /// whitespace and so has nowhere to put one.
230    ///
231    /// A comment goes wherever whitespace goes: before a value, around a
232    /// colon or a comma, between the last member and the closing brace. Not
233    /// inside a string, where `//` is two ordinary characters and always was.
234    ///
235    /// A comment is stepped over only when it is *complete*. A `/` that begins
236    /// nothing, and a `/*` that is never closed, are left exactly where they
237    /// are for the reader to fail on, so the error carries the offset of the
238    /// byte the comment began at rather than the end of the document.
239    ///
240    /// The streaming readers honour it too, and have to: they divide a stream
241    /// into values before the parser sees any of it, and a comment may hold a
242    /// brace. [`Mode::Lines`](crate::json::Mode::Lines) is the one place a
243    /// comment cannot span lines, a value's bytes being a line there, and a
244    /// comment between values is stepped over rather than checked as text,
245    /// belonging to no value's span. See the guide for both.
246    ///
247    /// It costs one comparison per run of whitespace, against a byte already
248    /// loaded, and nothing at all when it is off.
249    const ALLOW_COMMENTS: bool = false;
250
251    /// Count the bytes a document would occupy instead of assembling it.
252    ///
253    /// Not a setting to choose. It is how [`beve::size`](crate::beve::size)
254    /// reuses the writer as a tape measure: the only policy that turns it on
255    /// is private to this crate, and turning it on yourself would give you a
256    /// writer that produces an empty document. It lives on this trait because
257    /// the policy is the only type parameter every
258    /// [`beve::Write`](crate::beve::Write) implementation already carries, so
259    /// measuring reaches all of them without a signature changing anywhere.
260    ///
261    /// Honoured by the BEVE writer alone. The JSON side has no counterpart,
262    /// framing there being done from a buffer.
263    #[doc(hidden)]
264    const MEASURE: bool = false;
265
266    // Adding a constant here? If the BEVE writer will read it, forward it in
267    // `Measured` below as well. A constant that reaches the writer but not the
268    // forwarding makes `beve::size` describe a different document from the one
269    // the same policy writes, which is a wrong length in a frame header and
270    // nothing louder. Nothing enforces this: `SKIP_NULL` is the only constant
271    // the BEVE writer currently reads, so a test cannot distinguish the rest.
272}
273
274/// `O`'s settings, with the writer counting bytes instead of storing them.
275///
276/// Private, and deliberately: it is the one policy that makes a writer
277/// produce nothing, so the only things that may name it are the `size`
278/// entry points in [`beve`](crate::beve).
279///
280/// **Every constant of [`Options`] must be forwarded**, or a measurement stops
281/// describing the document the same policy would write.
282///
283/// Nothing checks that, and it is worth being exact about why rather than
284/// claiming a test covers it. [`SKIP_NULL`](Options::SKIP_NULL) is the only
285/// constant the BEVE writer reads at all, so every other policy in the crate
286/// produces byte-identical binary and no test can tell a forwarded constant
287/// from a dropped one: deleting the other six leaves the suite green. The list
288/// is therefore a promise kept by hand, and the thing that would break it is a
289/// *new* constant that the BEVE writer consults. The note at the foot of
290/// [`Options`] is where that gets caught, because it sits where such a
291/// constant would be added.
292///
293/// `fn() -> O` rather than `O`, matching the writer, so this type's auto
294/// traits do not depend on a policy it never holds a value of.
295#[derive(Clone, Copy)]
296pub(crate) struct Measured<O: Options>(core::marker::PhantomData<fn() -> O>);
297
298impl<O: Options> Options for Measured<O> {
299    const PRETTY: bool = O::PRETTY;
300    const INDENT: usize = O::INDENT;
301    const NEW_LINES_IN_ARRAYS: bool = O::NEW_LINES_IN_ARRAYS;
302    const SKIP_NULL: bool = O::SKIP_NULL;
303    const ERROR_ON_UNKNOWN_KEYS: bool = O::ERROR_ON_UNKNOWN_KEYS;
304    const ERROR_ON_MISSING_KEYS: bool = O::ERROR_ON_MISSING_KEYS;
305    const ALLOW_COMMENTS: bool = O::ALLOW_COMMENTS;
306    const MEASURE: bool = true;
307}
308
309/// Compact JSON, every declared member written, every unknown key refused.
310///
311/// The default everywhere.
312#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
313pub struct Standard;
314
315impl Options for Standard {}
316
317/// Indented JSON, two spaces per level.
318///
319/// ```
320/// # #[derive(Default)]
321/// # struct P { x: i32, y: Vec<i32> }
322/// # structio::object!(P { x, y });
323/// let out = structio::to_string_with::<structio::Pretty, _>(&P { x: 1, y: vec![2, 3] });
324/// assert_eq!(out, "{\n  \"x\": 1,\n  \"y\": [\n    2,\n    3\n  ]\n}");
325/// ```
326#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
327pub struct Pretty;
328
329impl Options for Pretty {
330    const PRETTY: bool = true;
331}
332
333/// Indented JSON, with each array kept on one line.
334///
335/// [`Pretty`] in every other respect: two spaces per level, a space after
336/// every colon, one object member per line. An array holds its elements on
337/// the line its opening bracket sits on, separated by `, `, which is what
338/// keeps a document of numeric data readable instead of a column one value
339/// wide. An object inside an array still breaks across lines.
340///
341/// ```
342/// # #[derive(Default)]
343/// # struct Sample { id: u32, values: Vec<f64> }
344/// # structio::object!(Sample { id, values });
345/// let s = Sample { id: 7, values: vec![1.5, 2.5, 3.5] };
346/// assert_eq!(
347///     structio::to_string_with::<structio::PrettyInlineArrays, _>(&s),
348///     "{\n  \"id\": 7,\n  \"values\": [1.5, 2.5, 3.5]\n}"
349/// );
350/// ```
351#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
352pub struct PrettyInlineArrays;
353
354impl Options for PrettyInlineArrays {
355    const PRETTY: bool = true;
356    const NEW_LINES_IN_ARRAYS: bool = false;
357}
358
359/// Compact, with null members left out.
360///
361/// ```
362/// # #[derive(Default)]
363/// # struct P { x: i32, note: Option<String> }
364/// # structio::object!(P { x, note });
365/// let p = P { x: 1, note: None };
366/// assert_eq!(structio::to_string(&p), "{\"x\":1,\"note\":null}");
367/// assert_eq!(structio::to_string_with::<structio::SkipNull, _>(&p), "{\"x\":1}");
368/// ```
369#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
370pub struct SkipNull;
371
372impl Options for SkipNull {
373    const SKIP_NULL: bool = true;
374}
375
376/// Unknown keys stepped over rather than refused.
377///
378/// The policy for reading a subset of a document you did not define, or one
379/// written against a newer version of your schema.
380///
381/// ```
382/// # #[derive(Default, Debug)]
383/// # struct Port { port: u16 }
384/// # structio::object!(Port { port });
385/// use structio::{ErrorCode, SkipUnknown, from_str, from_str_with};
386///
387/// let doc = r#"{"port":8080,"debug":true}"#;
388/// assert_eq!(from_str::<Port>(doc).unwrap_err().code, ErrorCode::UnknownKey);
389/// assert_eq!(from_str_with::<SkipUnknown, Port>(doc).unwrap().port, 8080);
390/// ```
391#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
392pub struct SkipUnknown;
393
394impl Options for SkipUnknown {
395    const ERROR_ON_UNKNOWN_KEYS: bool = false;
396}
397
398/// Every declared field required to be present, and every unknown key still
399/// refused.
400///
401/// The policy for a document that is meant to carry the whole value rather
402/// than a patch over one. It is the exact opposite of [`SkipUnknown`]: that
403/// one accepts a document that says more than the schema does, this one
404/// refuses a document that says less.
405///
406/// For a schema where only some members are mandatory, which is most of them,
407/// mark those `#[required]` in the declaration and read under the default
408/// policy. This is the blunt instrument, and it is the right one only where
409/// every member really is mandatory.
410///
411/// ```
412/// # #[derive(Default, Debug)]
413/// # struct Point { x: f64, y: f64 }
414/// # structio::object!(Point { x, y });
415/// use structio::{ErrorCode, RequireKeys, from_str, from_str_with};
416///
417/// let doc = r#"{"x":1.0}"#;
418/// assert_eq!(from_str::<Point>(doc).unwrap().y, 0.0);
419/// assert_eq!(
420///     from_str_with::<RequireKeys, Point>(doc).unwrap_err().code,
421///     ErrorCode::MissingKey
422/// );
423/// ```
424#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
425pub struct RequireKeys;
426
427impl Options for RequireKeys {
428    const ERROR_ON_MISSING_KEYS: bool = true;
429}
430
431/// JSONC: `//` and `/* */` comments accepted wherever whitespace is.
432///
433/// The policy for a document a person maintains rather than a program emits.
434/// Everything else is unchanged, so an unknown key is still refused.
435///
436/// ```
437/// # #[derive(Default, Debug)]
438/// # struct Limits { retries: u8, timeout_ms: u32 }
439/// # structio::object!(Limits { retries, timeout_ms });
440/// use structio::{AllowComments, from_str_with};
441///
442/// let config = r#"{
443///     // Give up after this many.
444///     "retries": 3,
445///     "timeout_ms": 500 /* per attempt */
446/// }"#;
447///
448/// let limits = from_str_with::<AllowComments, Limits>(config).unwrap();
449/// assert_eq!(limits.retries, 3);
450/// assert_eq!(limits.timeout_ms, 500);
451/// ```
452#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
453pub struct AllowComments;
454
455impl Options for AllowComments {
456    const ALLOW_COMMENTS: bool = true;
457}