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}