serde_keyvalue/
lib.rs

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
347mod key_values;
348
349#[cfg(feature = "argh_derive")]
350pub use argh;
351pub use key_values::from_key_values;
352pub use key_values::ErrorKind;
353pub use key_values::KeyValueDeserializer;
354pub use key_values::ParseError;
355#[cfg(feature = "argh_derive")]
356pub use serde_keyvalue_derive::FromKeyValues;