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