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