xref: /aosp_15_r20/external/crosvm/serde_keyvalue/src/lib.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1 // Copyright 2022 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 //! A lightweight serde deserializer for strings containing key-value pairs separated by commas, as
6 //! commonly found in command-line parameters.
7 //!
8 //! Say your program takes a command-line option of the form:
9 //!
10 //! ```text
11 //! --foo type=bar,active,nb_threads=8
12 //! ```
13 //!
14 //! This crate provides a [from_key_values] function that deserializes these key-values into a
15 //! configuration structure. Since it uses serde, the same configuration structure can also be
16 //! created from any other supported source (such as a TOML or YAML configuration file) that uses
17 //! the same keys.
18 //!
19 //! Integration with the [argh](https://github.com/google/argh) command-line parser is also
20 //! provided via the `argh_derive` feature.
21 //!
22 //! The deserializer supports parsing signed and unsigned integers, booleans, strings (quoted or
23 //! not), paths, and enums inside a top-level struct. The order in which the fields appear in the
24 //! string is not important.
25 //!
26 //! Simple example:
27 //!
28 //! ```
29 //! use serde_keyvalue::from_key_values;
30 //! use serde::Deserialize;
31 //!
32 //! #[derive(Debug, PartialEq, Deserialize)]
33 //! struct Config {
34 //!     path: String,
35 //!     threads: u8,
36 //!     active: bool,
37 //! }
38 //!
39 //! let config: Config = from_key_values("path=/some/path,threads=16,active=true").unwrap();
40 //! assert_eq!(config, Config { path: "/some/path".into(), threads: 16, active: true });
41 //!
42 //! let config: Config = from_key_values("threads=16,active=true,path=/some/path").unwrap();
43 //! assert_eq!(config, Config { path: "/some/path".into(), threads: 16, active: true });
44 //! ```
45 //!
46 //! As a convenience the name of the first field of a struct can be omitted:
47 //!
48 //! ```
49 //! # use serde_keyvalue::from_key_values;
50 //! # use serde::Deserialize;
51 //! #[derive(Debug, PartialEq, Deserialize)]
52 //! struct Config {
53 //!     path: String,
54 //!     threads: u8,
55 //!     active: bool,
56 //! }
57 //!
58 //! let config: Config = from_key_values("/some/path,threads=16,active=true").unwrap();
59 //! assert_eq!(config, Config { path: "/some/path".into(), threads: 16, active: true });
60 //! ```
61 //!
62 //! Fields that are behind an `Option` can be omitted, in which case they will be `None`.
63 //!
64 //! ```
65 //! # use serde_keyvalue::from_key_values;
66 //! # use serde::Deserialize;
67 //! #[derive(Debug, PartialEq, Deserialize)]
68 //! struct Config {
69 //!     path: Option<String>,
70 //!     threads: u8,
71 //!     active: bool,
72 //! }
73 //!
74 //! let config: Config = from_key_values("path=/some/path,threads=16,active=true").unwrap();
75 //! assert_eq!(config, Config { path: Some("/some/path".into()), threads: 16, active: true });
76 //!
77 //! let config: Config = from_key_values("threads=16,active=true").unwrap();
78 //! assert_eq!(config, Config { path: None, threads: 16, active: true });
79 //! ```
80 //!
81 //! Alternatively, the serde `default` attribute can be used on select fields or on the whole
82 //! struct to make unspecified fields be assigned their default value. In the following example only
83 //! the `path` parameter must be specified.
84 //!
85 //! ```
86 //! # use serde_keyvalue::from_key_values;
87 //! # use serde::Deserialize;
88 //! #[derive(Debug, PartialEq, Deserialize)]
89 //! struct Config {
90 //!     path: String,
91 //!     #[serde(default)]
92 //!     threads: u8,
93 //!     #[serde(default)]
94 //!     active: bool,
95 //! }
96 //!
97 //! let config: Config = from_key_values("path=/some/path").unwrap();
98 //! assert_eq!(config, Config { path: "/some/path".into(), threads: 0, active: false });
99 //! ```
100 //!
101 //! A function providing a default value can also be specified, see the [serde documentation for
102 //! field attributes](https://serde.rs/field-attrs.html) for details.
103 //!
104 //! Booleans can be `true` or `false`, or take no value at all, in which case they will be `true`.
105 //! Combined with default values this allows to implement flags very easily:
106 //!
107 //! ```
108 //! # use serde_keyvalue::from_key_values;
109 //! # use serde::Deserialize;
110 //! #[derive(Debug, Default, PartialEq, Deserialize)]
111 //! #[serde(default)]
112 //! struct Config {
113 //!     active: bool,
114 //!     delayed: bool,
115 //!     pooled: bool,
116 //! }
117 //!
118 //! let config: Config = from_key_values("active=true,delayed=false,pooled=true").unwrap();
119 //! assert_eq!(config, Config { active: true, delayed: false, pooled: true });
120 //!
121 //! let config: Config = from_key_values("active,pooled").unwrap();
122 //! assert_eq!(config, Config { active: true, delayed: false, pooled: true });
123 //! ```
124 //!
125 //! Strings can be quoted, which is useful if they need to include a comma or a bracket, which are
126 //! considered separators for unquoted strings. Quoted strings can also contain escaped characters,
127 //! where any character after a `\` is repeated as-is:
128 //!
129 //! ```
130 //! # use serde_keyvalue::from_key_values;
131 //! # use serde::Deserialize;
132 //! #[derive(Debug, PartialEq, Deserialize)]
133 //! struct Config {
134 //!     path: String,
135 //! }
136 //!
137 //! let config: Config = from_key_values(r#"path="/some/\"strange\"/pa,th""#).unwrap();
138 //! assert_eq!(config, Config { path: r#"/some/"strange"/pa,th"#.into() });
139 //! ```
140 //!
141 //! Tuples and vectors are allowed and must be specified between `[` and `]`:
142 //!
143 //! ```
144 //! # use serde_keyvalue::from_key_values;
145 //! # use serde::Deserialize;
146 //! #[derive(Debug, PartialEq, Deserialize)]
147 //! struct Layout {
148 //!     resolution: (u16, u16),
149 //!     scanlines: Vec<u16>,
150 //! }
151 //!
152 //! let layout: Layout = from_key_values("resolution=[320,200],scanlines=[0,64,128]").unwrap();
153 //! assert_eq!(layout, Layout { resolution: (320, 200), scanlines: vec![0, 64, 128] });
154 //! ```
155 //!
156 //! Enums can be directly specified by name. It is recommended to use the `rename_all` serde
157 //! container attribute to make them parseable using snake or kebab case representation. Serde's
158 //! `rename` and `alias` field attributes can also be used to provide shorter values:
159 //!
160 //! ```
161 //! # use serde_keyvalue::from_key_values;
162 //! # use serde::Deserialize;
163 //! #[derive(Debug, PartialEq, Deserialize)]
164 //! #[serde(rename_all="kebab-case")]
165 //! enum Mode {
166 //!     Slow,
167 //!     Fast,
168 //!     #[serde(rename="ludicrous")]
169 //!     LudicrousSpeed,
170 //! }
171 //!
172 //! #[derive(Deserialize, PartialEq, Debug)]
173 //! struct Config {
174 //!     mode: Mode,
175 //! }
176 //!
177 //! let config: Config = from_key_values("mode=slow").unwrap();
178 //! assert_eq!(config, Config { mode: Mode::Slow });
179 //!
180 //! let config: Config = from_key_values("mode=ludicrous").unwrap();
181 //! assert_eq!(config, Config { mode: Mode::LudicrousSpeed });
182 //! ```
183 //!
184 //! A nice use of enums is along with sets, where it allows to e.g. easily specify flags:
185 //!
186 //! ```
187 //! # use std::collections::BTreeSet;
188 //! # use serde_keyvalue::from_key_values;
189 //! # use serde::Deserialize;
190 //! #[derive(Deserialize, PartialEq, Eq, Debug, PartialOrd, Ord)]
191 //! #[serde(rename_all = "kebab-case")]
192 //! enum Flags {
193 //!     Awesome,
194 //!     Fluffy,
195 //!     Transparent,
196 //! }
197 //! #[derive(Deserialize, PartialEq, Debug)]
198 //! struct TestStruct {
199 //!     flags: BTreeSet<Flags>,
200 //! }
201 //!
202 //! let res: TestStruct = from_key_values("flags=[awesome,fluffy]").unwrap();
203 //! assert_eq!(
204 //!     res,
205 //!     TestStruct {
206 //!         flags: BTreeSet::from([Flags::Awesome, Flags::Fluffy]),
207 //!     }
208 //! );
209 //! ```
210 //!
211 //! Enums taking a single value can use the `flatten` field attribute in order to be inferred from
212 //! their variant key directly:
213 //!
214 //! ```
215 //! # use serde_keyvalue::from_key_values;
216 //! # use serde::Deserialize;
217 //! #[derive(Debug, PartialEq, Deserialize)]
218 //! #[serde(rename_all="kebab-case")]
219 //! enum Mode {
220 //!     // Work with a local file.
221 //!     File(String),
222 //!     // Work with a remote URL.
223 //!     Url(String),
224 //! }
225 //!
226 //! #[derive(Deserialize, PartialEq, Debug)]
227 //! struct Config {
228 //!     #[serde(flatten)]
229 //!     mode: Mode,
230 //! }
231 //!
232 //! let config: Config = from_key_values("file=/some/path").unwrap();
233 //! assert_eq!(config, Config { mode: Mode::File("/some/path".into()) });
234 //!
235 //! let config: Config = from_key_values("url=https://www.google.com").unwrap();
236 //! assert_eq!(config, Config { mode: Mode::Url("https://www.google.com".into()) });
237 //! ```
238 //!
239 //! The `flatten` attribute can also be used to embed one struct within another one and parse both
240 //! from the same string:
241 //!
242 //! ```
243 //! # use serde_keyvalue::from_key_values;
244 //! # use serde::Deserialize;
245 //! #[derive(Debug, PartialEq, Deserialize)]
246 //! struct BaseConfig {
247 //!     enabled: bool,
248 //!     num_threads: u8,
249 //! }
250 //!
251 //! #[derive(Debug, PartialEq, Deserialize)]
252 //! struct Config {
253 //!     #[serde(flatten)]
254 //!     base: BaseConfig,
255 //!     path: String,
256 //! }
257 //!
258 //! let config: Config = from_key_values("path=/some/path,enabled,num_threads=16").unwrap();
259 //! assert_eq!(
260 //!     config,
261 //!     Config {
262 //!         path: "/some/path".into(),
263 //!         base: BaseConfig {
264 //!             num_threads: 16,
265 //!             enabled: true,
266 //!         }
267 //!     }
268 //! );
269 //! ```
270 //!
271 //! If an enum's variants are made of structs, it can take the `untagged` container attribute to be
272 //! inferred directly from the fields of the embedded structs:
273 //!
274 //! ```
275 //! # use serde_keyvalue::from_key_values;
276 //! # use serde::Deserialize;
277 //! #[derive(Debug, PartialEq, Deserialize)]
278 //! #[serde(untagged)]
279 //! enum Mode {
280 //!     // Work with a local file.
281 //!     File {
282 //!         path: String,
283 //!         #[serde(default)]
284 //!         read_only: bool,
285 //!     },
286 //!     // Work with a remote URL.
287 //!     Remote {
288 //!         server: String,
289 //!         port: u16,
290 //!     }
291 //! }
292 //!
293 //! #[derive(Debug, PartialEq, Deserialize)]
294 //! struct Config {
295 //!     #[serde(flatten)]
296 //!     mode: Mode,
297 //! }
298 //!
299 //! let config: Config = from_key_values("path=/some/path").unwrap();
300 //! assert_eq!(config, Config { mode: Mode::File { path: "/some/path".into(), read_only: false } });
301 //!
302 //! let config: Config = from_key_values("server=google.com,port=80").unwrap();
303 //! assert_eq!(config, Config { mode: Mode::Remote { server: "google.com".into(), port: 80 } });
304 //! ```
305 //!
306 //! Using this crate, parsing errors and invalid or missing fields are precisely reported:
307 //!
308 //! ```
309 //! # use serde_keyvalue::from_key_values;
310 //! # use serde::Deserialize;
311 //! #[derive(Debug, PartialEq, Deserialize)]
312 //! struct Config {
313 //!     path: String,
314 //!     threads: u8,
315 //!     active: bool,
316 //! }
317 //!
318 //! let config = from_key_values::<Config>("path=/some/path,active=true").unwrap_err();
319 //! assert_eq!(format!("{}", config), "missing field `threads`");
320 //! ```
321 //!
322 //! Most of the serde [container](https://serde.rs/container-attrs.html) and
323 //! [field](https://serde.rs/field-attrs.html) attributes can be applied to your configuration
324 //! struct. Most useful ones include
325 //! [`deny_unknown_fields`](https://serde.rs/container-attrs.html#deny_unknown_fields) to report an
326 //! error if an unknown field is met in the input, and
327 //! [`deserialize_with`](https://serde.rs/field-attrs.html#deserialize_with) to use a custom
328 //! deserialization function for a specific field.
329 //!
330 //! Be aware that the use of `flatten` comes with some severe limitations. Because type information
331 //! is not available to the deserializer, it will try to determine the type of fields using the
332 //! input as its sole hint. For instance, any number will be returned as an integer type, and if the
333 //! parsed structure was actually expecting a number as a string, then an error will occur.
334 //! Struct enums also cannot be flattened and won't be recognized at all.
335 //!
336 //! For these reasons, it is discouraged to use `flatten` except when neither the embedding not the
337 //! flattened structs has a member of string type.
338 //!
339 //! Most of the time, similar functionality can be obtained by implementing a custom deserializer
340 //! that parses the embedding struct's member and then the flattened struct in a specific order
341 //! using the [`key_values::KeyValueDeserializer`] interface directly.
342 //!
343 //! Another limitation of using `flatten` that is inherent to serde is that it won't allow
344 //! `deny_unknown_fields` to be used in either the embedding or the flattened struct.
345 #![deny(missing_docs)]
346 
347 mod key_values;
348 
349 #[cfg(feature = "argh_derive")]
350 pub use argh;
351 pub use key_values::from_key_values;
352 pub use key_values::ErrorKind;
353 pub use key_values::KeyValueDeserializer;
354 pub use key_values::ParseError;
355 #[cfg(feature = "argh_derive")]
356 pub use serde_keyvalue_derive::FromKeyValues;
357