crosvm/serde_keyvalue/src/lib.rs
Dennis Kempin 4fea399df9 Reformat imports
crosvm is switching the import style to use one import per line.
While more verbose, this will greatly reduce the occurence of merge
conflicts going forward.

Note: This is using a nightly feature of rustfmt. So it's a one-off
re-format only. We are considering adding a nightly toolchain to
enable the feature permanently.

BUG=b:239937122
TEST=CQ

Change-Id: Id2dd4dbdc0adfc4f8f3dd1d09da1daafa2a39992
Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/3784345
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Tested-by: Dennis Kempin <denniskempin@google.com>
Commit-Queue: Dennis Kempin <denniskempin@google.com>
2022-07-28 00:15:50 +00:00

296 lines
9.8 KiB
Rust

// Copyright 2022 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! A lightweight serde deserializer for strings containing key-value pairs separated by commas, as
//! commonly found in command-line parameters.
//!
//! Say your program takes a command-line option of the form:
//!
//! ```text
//! --foo type=bar,active,nb_threads=8
//! ```
//!
//! This crate provides a [from_key_values] function that deserializes these key-values into a
//! configuration structure. Since it uses serde, the same configuration structure can also be
//! created from any other supported source (such as a TOML or YAML configuration file) that uses
//! the same keys.
//!
//! Integration with the [argh](https://github.com/google/argh) command-line parser is also
//! provided via the `argh_derive` feature.
//!
//! The deserializer supports parsing signed and unsigned integers, booleans, strings (quoted or
//! not), paths, and enums inside a top-level struct. The order in which the fields appear in the
//! string is not important.
//!
//! Simple example:
//!
//! ```
//! use serde_keyvalue::from_key_values;
//! use serde::Deserialize;
//!
//! #[derive(Debug, PartialEq, Deserialize)]
//! struct Config {
//! path: String,
//! threads: u8,
//! active: bool,
//! }
//!
//! let config: Config = from_key_values("path=/some/path,threads=16,active=true").unwrap();
//! assert_eq!(config, Config { path: "/some/path".into(), threads: 16, active: true });
//!
//! let config: Config = from_key_values("threads=16,active=true,path=/some/path").unwrap();
//! assert_eq!(config, Config { path: "/some/path".into(), threads: 16, active: true });
//! ```
//!
//! As a convenience the name of the first field of a struct can be omitted:
//!
//! ```
//! # use serde_keyvalue::from_key_values;
//! # use serde::Deserialize;
//! #[derive(Debug, PartialEq, Deserialize)]
//! struct Config {
//! path: String,
//! threads: u8,
//! active: bool,
//! }
//!
//! let config: Config = from_key_values("/some/path,threads=16,active=true").unwrap();
//! assert_eq!(config, Config { path: "/some/path".into(), threads: 16, active: true });
//! ```
//!
//! Fields that are behind an `Option` can be omitted, in which case they will be `None`.
//!
//! ```
//! # use serde_keyvalue::from_key_values;
//! # use serde::Deserialize;
//! #[derive(Debug, PartialEq, Deserialize)]
//! struct Config {
//! path: Option<String>,
//! threads: u8,
//! active: bool,
//! }
//!
//! let config: Config = from_key_values("path=/some/path,threads=16,active=true").unwrap();
//! assert_eq!(config, Config { path: Some("/some/path".into()), threads: 16, active: true });
//!
//! let config: Config = from_key_values("threads=16,active=true").unwrap();
//! assert_eq!(config, Config { path: None, threads: 16, active: true });
//! ```
//!
//! Alternatively, the serde `default` attribute can be used on select fields or on the whole
//! struct to make unspecified fields be assigned their default value. In the following example only
//! the `path` parameter must be specified.
//!
//! ```
//! # use serde_keyvalue::from_key_values;
//! # use serde::Deserialize;
//! #[derive(Debug, PartialEq, Deserialize)]
//! struct Config {
//! path: String,
//! #[serde(default)]
//! threads: u8,
//! #[serde(default)]
//! active: bool,
//! }
//!
//! let config: Config = from_key_values("path=/some/path").unwrap();
//! assert_eq!(config, Config { path: "/some/path".into(), threads: 0, active: false });
//! ```
//!
//! A function providing a default value can also be specified, see the [serde documentation for
//! field attributes](https://serde.rs/field-attrs.html) for details.
//!
//! Booleans can be `true` or `false`, or take no value at all, in which case they will be `true`.
//! Combined with default values this allows to implement flags very easily:
//!
//! ```
//! # use serde_keyvalue::from_key_values;
//! # use serde::Deserialize;
//! #[derive(Debug, Default, PartialEq, Deserialize)]
//! #[serde(default)]
//! struct Config {
//! active: bool,
//! delayed: bool,
//! pooled: bool,
//! }
//!
//! let config: Config = from_key_values("active=true,delayed=false,pooled=true").unwrap();
//! assert_eq!(config, Config { active: true, delayed: false, pooled: true });
//!
//! let config: Config = from_key_values("active,pooled").unwrap();
//! assert_eq!(config, Config { active: true, delayed: false, pooled: true });
//! ```
//!
//! Strings can be quoted, which is useful if they e.g. need to include a comma. Quoted strings can
//! also contain escaped characters, where any character after a `\` is repeated as-is:
//!
//! ```
//! # use serde_keyvalue::from_key_values;
//! # use serde::Deserialize;
//! #[derive(Debug, PartialEq, Deserialize)]
//! struct Config {
//! path: String,
//! }
//!
//! let config: Config = from_key_values(r#"path="/some/\"strange\"/pa,th""#).unwrap();
//! assert_eq!(config, Config { path: r#"/some/"strange"/pa,th"#.into() });
//! ```
//!
//! Enums can be directly specified by name. It is recommended to use the `rename_all` serde
//! container attribute to make them parseable using snake or kebab case representation. Serde's
//! `rename` and `alias` field attributes can also be used to provide shorter values:
//!
//! ```
//! # use serde_keyvalue::from_key_values;
//! # use serde::Deserialize;
//! #[derive(Debug, PartialEq, Deserialize)]
//! #[serde(rename_all="kebab-case")]
//! enum Mode {
//! Slow,
//! Fast,
//! #[serde(rename="ludicrous")]
//! LudicrousSpeed,
//! }
//!
//! #[derive(Deserialize, PartialEq, Debug)]
//! struct Config {
//! mode: Mode,
//! }
//!
//! let config: Config = from_key_values("mode=slow").unwrap();
//! assert_eq!(config, Config { mode: Mode::Slow });
//!
//! let config: Config = from_key_values("mode=ludicrous").unwrap();
//! assert_eq!(config, Config { mode: Mode::LudicrousSpeed });
//! ```
//!
//! Enums taking a single value should use the `flatten` field attribute in order to be inferred
//! from their variant key directly:
//!
//! ```
//! # use serde_keyvalue::from_key_values;
//! # use serde::Deserialize;
//! #[derive(Debug, PartialEq, Deserialize)]
//! #[serde(rename_all="kebab-case")]
//! enum Mode {
//! // Work with a local file.
//! File(String),
//! // Work with a remote URL.
//! Url(String),
//! }
//!
//! #[derive(Deserialize, PartialEq, Debug)]
//! struct Config {
//! #[serde(flatten)]
//! mode: Mode,
//! }
//!
//! let config: Config = from_key_values("file=/some/path").unwrap();
//! assert_eq!(config, Config { mode: Mode::File("/some/path".into()) });
//!
//! let config: Config = from_key_values("url=https://www.google.com").unwrap();
//! assert_eq!(config, Config { mode: Mode::Url("https://www.google.com".into()) });
//! ```
//!
//! The `flatten` attribute can also be used to embed one struct within another one and parse both
//! from the same string:
//!
//! ```
//! # use serde_keyvalue::from_key_values;
//! # use serde::Deserialize;
//! #[derive(Debug, PartialEq, Deserialize)]
//! struct BaseConfig {
//! enabled: bool,
//! num_threads: u8,
//! }
//!
//! #[derive(Debug, PartialEq, Deserialize)]
//! struct Config {
//! #[serde(flatten)]
//! base: BaseConfig,
//! path: String,
//! }
//!
//! let config: Config = from_key_values("path=/some/path,enabled,num_threads=16").unwrap();
//! assert_eq!(
//! config,
//! Config {
//! path: "/some/path".into(),
//! base: BaseConfig {
//! num_threads: 16,
//! enabled: true,
//! }
//! }
//! );
//! ```
//!
//! If an enum's variants are made of structs, it should take the `untagged` container attribute so
//! it can be inferred directly from the fields of the embedded structs:
//!
//! ```
//! # use serde_keyvalue::from_key_values;
//! # use serde::Deserialize;
//! #[derive(Debug, PartialEq, Deserialize)]
//! #[serde(untagged)]
//! enum Mode {
//! // Work with a local file.
//! File {
//! path: String,
//! #[serde(default)]
//! read_only: bool,
//! },
//! // Work with a remote URL.
//! Remote {
//! server: String,
//! port: u16,
//! }
//! }
//!
//! #[derive(Debug, PartialEq, Deserialize)]
//! struct Config {
//! #[serde(flatten)]
//! mode: Mode,
//! }
//!
//! let config: Config = from_key_values("path=/some/path").unwrap();
//! assert_eq!(config, Config { mode: Mode::File { path: "/some/path".into(), read_only: false } });
//!
//! let config: Config = from_key_values("server=google.com,port=80").unwrap();
//! assert_eq!(config, Config { mode: Mode::Remote { server: "google.com".into(), port: 80 } });
//! ```
//!
//! Using this crate, parsing errors and invalid or missing fields are precisely reported:
//!
//! ```
//! # use serde_keyvalue::from_key_values;
//! # use serde::Deserialize;
//! #[derive(Debug, PartialEq, Deserialize)]
//! struct Config {
//! path: String,
//! threads: u8,
//! active: bool,
//! }
//!
//! let config = from_key_values::<Config>("path=/some/path,active=true").unwrap_err();
//! assert_eq!(format!("{}", config), "missing field `threads`");
//! ```
//!
//! Most of the serde [container](https://serde.rs/container-attrs.html) and
//! [field](https://serde.rs/field-attrs.html) attributes can be applied to your configuration
//! struct. Most useful ones include
//! [`deny_unknown_fields`](https://serde.rs/container-attrs.html#deny_unknown_fields) to report an
//! error if an unknown field is met in the input, and
//! [`deserialize_with`](https://serde.rs/field-attrs.html#deserialize_with) to use a custom
//! deserialization function for a specific field.
#![deny(missing_docs)]
mod key_values;
#[cfg(feature = "argh_derive")]
pub use argh;
pub use key_values::from_key_values;
pub use key_values::ErrorKind;
pub use key_values::ParseError;
#[cfg(feature = "argh_derive")]
pub use serde_keyvalue_derive::FromKeyValues;