| // Copyright 2022 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use std::borrow::Cow; |
| use std::fmt; |
| use std::fmt::Debug; |
| use std::fmt::Display; |
| use std::num::ParseIntError; |
| |
| use nom::branch::alt; |
| use nom::bytes::complete::escaped_transform; |
| use nom::bytes::complete::is_not; |
| use nom::bytes::complete::tag; |
| use nom::bytes::complete::take_while; |
| use nom::bytes::complete::take_while1; |
| use nom::character::complete::alphanumeric1; |
| use nom::character::complete::anychar; |
| use nom::character::complete::char; |
| use nom::character::complete::none_of; |
| use nom::combinator::map; |
| use nom::combinator::map_res; |
| use nom::combinator::opt; |
| use nom::combinator::peek; |
| use nom::combinator::recognize; |
| use nom::combinator::value; |
| use nom::combinator::verify; |
| use nom::sequence::delimited; |
| use nom::sequence::pair; |
| use nom::sequence::tuple; |
| use nom::AsChar; |
| use nom::Finish; |
| use nom::IResult; |
| use num_traits::Num; |
| use remain::sorted; |
| use serde::de; |
| use serde::Deserialize; |
| use serde::Deserializer; |
| use thiserror::Error; |
| |
| #[derive(Debug, Error, PartialEq, Eq)] |
| #[sorted] |
| #[non_exhaustive] |
| #[allow(missing_docs)] |
| /// Different kinds of errors that can be returned by the parser. |
| pub enum ErrorKind { |
| #[error("unexpected end of input")] |
| Eof, |
| #[error("expected a boolean")] |
| ExpectedBoolean, |
| #[error("expected ']'")] |
| ExpectedCloseBracket, |
| #[error("expected ','")] |
| ExpectedComma, |
| #[error("expected '='")] |
| ExpectedEqual, |
| #[error("expected an identifier")] |
| ExpectedIdentifier, |
| #[error("expected '['")] |
| ExpectedOpenBracket, |
| #[error("expected a string")] |
| ExpectedString, |
| #[error("\" and ' can only be used in quoted strings")] |
| InvalidCharInString, |
| #[error("invalid characters for number or number does not fit into its destination type")] |
| InvalidNumber, |
| #[error("serde error: {0}")] |
| SerdeError(String), |
| #[error("remaining characters in input")] |
| TrailingCharacters, |
| } |
| |
| /// Error that may be thown while parsing a key-values string. |
| #[derive(Debug, Error, PartialEq, Eq)] |
| pub struct ParseError { |
| /// Detailed error that occurred. |
| pub kind: ErrorKind, |
| /// Index of the error in the input string. |
| pub pos: usize, |
| } |
| |
| impl Display for ParseError { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| match &self.kind { |
| ErrorKind::SerdeError(s) => write!(f, "{s}"), |
| _ => write!(f, "{} at position {}", self.kind, self.pos), |
| } |
| } |
| } |
| |
| impl de::Error for ParseError { |
| fn custom<T>(msg: T) -> Self |
| where |
| T: fmt::Display, |
| { |
| Self { |
| kind: ErrorKind::SerdeError(msg.to_string()), |
| pos: 0, |
| } |
| } |
| } |
| |
| type Result<T> = std::result::Result<T, ParseError>; |
| |
| /// Returns `true` if `c` is a valid separator character. |
| fn is_separator(c: Option<char>) -> bool { |
| matches!(c, Some(',') | Some(']') | None) |
| } |
| |
| /// Nom parser for valid separators. |
| fn any_separator(s: &str) -> IResult<&str, Option<char>> { |
| let next_char = s.chars().next(); |
| |
| if is_separator(next_char) { |
| let pos = if let Some(c) = next_char { |
| c.len_utf8() |
| } else { |
| 0 |
| }; |
| Ok((&s[pos..], next_char)) |
| } else { |
| Err(nom::Err::Error(nom::error::Error::new( |
| s, |
| nom::error::ErrorKind::Char, |
| ))) |
| } |
| } |
| |
| /// Nom parser for valid strings. |
| /// |
| /// A string can be quoted (using single or double quotes) or not. If it is not quoted, the string |
| /// is assumed to continue until the next ',', '[', or ']' character. If it is escaped, it continues |
| /// until the next non-escaped quote. |
| /// |
| /// The returned value is a slice into the current input if no characters to unescape were met, |
| /// or a fully owned string if we had to unescape some characters. |
| fn any_string(s: &str) -> IResult<&str, Cow<str>> { |
| // Double-quoted strings may escape " and \ characters. Since escaped strings are modified, |
| // we need to return an owned `String` instead of just a slice in the input string. |
| let double_quoted = delimited( |
| char('"'), |
| alt(( |
| map( |
| escaped_transform( |
| none_of(r#"\""#), |
| '\\', |
| alt((value("\"", char('"')), value("\\", char('\\')))), |
| ), |
| Cow::Owned, |
| ), |
| map(tag(""), Cow::Borrowed), |
| )), |
| char('"'), |
| ); |
| |
| // Single-quoted strings do not escape characters. |
| let single_quoted = map( |
| delimited(char('\''), alt((is_not(r#"'"#), tag(""))), char('\'')), |
| Cow::Borrowed, |
| ); |
| |
| // Unquoted strings end with the next comma or bracket and may not contain a quote or bracket |
| // character or be empty. |
| let unquoted = map( |
| take_while1(|c: char| c != ',' && c != '"' && c != '\'' && c != '[' && c != ']'), |
| Cow::Borrowed, |
| ); |
| |
| alt((double_quoted, single_quoted, unquoted))(s) |
| } |
| |
| /// Nom parser for valid positive of negative numbers. |
| /// |
| /// Hexadecimal, octal, and binary values can be specified with the `0x`, `0o` and `0b` prefixes. |
| fn any_number<T>(s: &str) -> IResult<&str, T> |
| where |
| T: Num<FromStrRadixErr = ParseIntError>, |
| { |
| // Parses the number input and returns a tuple including the number itself (with its sign) and |
| // its radix. |
| // |
| // We move this non-generic part into its own function so it doesn't get monomorphized, which |
| // would increase the binary size more than needed. |
| fn parse_number(s: &str) -> IResult<&str, (Cow<str>, u32)> { |
| // Recognizes the sign prefix. |
| let sign = char('-'); |
| |
| // Recognizes the radix prefix. |
| let radix = alt(( |
| value(16, tag("0x")), |
| value(8, tag("0o")), |
| value(2, tag("0b")), |
| )); |
| |
| // Recognizes the trailing separator but do not consume it. |
| let separator = peek(any_separator); |
| |
| // Chain of parsers: sign (optional) and radix (optional), then sequence of alphanumerical |
| // characters. |
| // |
| // Then we take all 3 recognized elements and turn them into the string and radix to pass to |
| // `from_str_radix`. |
| map( |
| tuple((opt(sign), opt(radix), alphanumeric1, separator)), |
| |(sign, radix, number, _)| { |
| // If the sign was specified, we need to build a string that contains it for |
| // `from_str_radix` to parse the number accurately. Otherwise, simply borrow the |
| // remainder of the input. |
| let num_string = if let Some(sign) = sign { |
| Cow::Owned(sign.to_string() + number) |
| } else { |
| Cow::Borrowed(number) |
| }; |
| |
| (num_string, radix.unwrap_or(10)) |
| }, |
| )(s) |
| } |
| |
| map_res(parse_number, |(num_string, radix)| { |
| T::from_str_radix(&num_string, radix) |
| })(s) |
| } |
| |
| /// Nom parser for booleans. |
| fn any_bool(s: &str) -> IResult<&str, bool> { |
| let mut boolean = alt((value(true, tag("true")), value(false, tag("false")))); |
| |
| boolean(s) |
| } |
| |
| /// Nom parser for identifiers. An identifier may contain any alphanumeric character, as well as |
| /// '_' and '-' at any place excepted the first one which cannot be '-'. |
| /// |
| /// Usually identifiers are not allowed to start with a number, but we chose to allow this |
| /// here otherwise options like "mode=2d" won't parse if "2d" is an alias for an enum variant. |
| fn any_identifier(s: &str) -> IResult<&str, &str> { |
| let mut ident = recognize(pair( |
| verify(anychar, |&c| c.is_alphanum() || c == '_'), |
| take_while(|c: char| c.is_alphanum() || c == '_' || c == '-'), |
| )); |
| |
| ident(s) |
| } |
| |
| /// Serde deserializer for key-values strings. |
| pub struct KeyValueDeserializer<'de> { |
| /// Full input originally received for parsing. |
| original_input: &'de str, |
| /// Input currently remaining to parse. |
| input: &'de str, |
| /// If set, then `deserialize_identifier` will take and return its content the next time it is |
| /// called instead of trying to parse an identifier from the input. This is needed to allow the |
| /// name of the first field of a struct to be omitted, e.g. |
| /// |
| /// --block "/path/to/disk.img,ro=true" |
| /// |
| /// instead of |
| /// |
| /// --block "path=/path/to/disk.img,ro=true" |
| next_identifier: Option<&'de str>, |
| /// Whether the '=' sign has been parsed after a key. The absence of '=' is only valid for |
| /// boolean fields, in which case the field's value will be `true`. |
| has_equal: bool, |
| /// Whether the top structure has been parsed yet or not. The top structure is the only one |
| /// that does not require to be enclosed within braces. |
| top_struct_parsed: bool, |
| } |
| |
| impl<'de> From<&'de str> for KeyValueDeserializer<'de> { |
| fn from(input: &'de str) -> Self { |
| Self { |
| original_input: input, |
| input, |
| next_identifier: None, |
| has_equal: false, |
| top_struct_parsed: false, |
| } |
| } |
| } |
| |
| impl<'de> KeyValueDeserializer<'de> { |
| /// Return an `kind` error for the current position of the input. |
| pub fn error_here(&self, kind: ErrorKind) -> ParseError { |
| ParseError { |
| kind, |
| pos: self.original_input.len() - self.input.len(), |
| } |
| } |
| |
| /// Returns the next char in the input string without consuming it, or None |
| /// if we reached the end of input. |
| pub fn peek_char(&self) -> Option<char> { |
| self.input.chars().next() |
| } |
| |
| /// Skip the next char in the input string. |
| pub fn skip_char(&mut self) { |
| let _ = self.next_char(); |
| } |
| |
| /// Returns the next char in the input string and consume it, or returns |
| /// None if we reached the end of input. |
| pub fn next_char(&mut self) -> Option<char> { |
| let c = self.peek_char()?; |
| self.input = &self.input[c.len_utf8()..]; |
| Some(c) |
| } |
| |
| /// Confirm that we have a separator (i.e. ',' or ']') character or have reached the end of the |
| /// input string. |
| fn confirm_separator(&mut self) -> Result<()> { |
| // We must have a comma or end of input after a value. |
| match self.peek_char() { |
| Some(',') => { |
| let _ = self.next_char(); |
| Ok(()) |
| } |
| Some(']') | None => Ok(()), |
| Some(_) => Err(self.error_here(ErrorKind::ExpectedComma)), |
| } |
| } |
| |
| /// Attempts to parse an identifier, either for a key or for the value of an enum type. |
| pub fn parse_identifier(&mut self) -> Result<&'de str> { |
| let (remainder, res) = any_identifier(self.input) |
| .finish() |
| .map_err(|_| self.error_here(ErrorKind::ExpectedIdentifier))?; |
| |
| self.input = remainder; |
| Ok(res) |
| } |
| |
| /// Attempts to parse a string. |
| pub fn parse_string(&mut self) -> Result<Cow<'de, str>> { |
| let (remainder, res) = |
| any_string(self.input) |
| .finish() |
| .map_err(|e: nom::error::Error<_>| { |
| self.input = e.input; |
| // Any error means we did not have a well-formed string. |
| self.error_here(ErrorKind::ExpectedString) |
| })?; |
| |
| self.input = remainder; |
| |
| // The character following a string will be either a comma, a closing bracket, or EOS. If |
| // we have something else, this means an unquoted string should probably have been quoted. |
| if is_separator(self.peek_char()) { |
| Ok(res) |
| } else { |
| Err(self.error_here(ErrorKind::InvalidCharInString)) |
| } |
| } |
| |
| /// Attempt to parse a boolean. |
| pub fn parse_bool(&mut self) -> Result<bool> { |
| let (remainder, res) = |
| any_bool(self.input) |
| .finish() |
| .map_err(|e: nom::error::Error<_>| { |
| self.input = e.input; |
| self.error_here(ErrorKind::ExpectedBoolean) |
| })?; |
| |
| self.input = remainder; |
| Ok(res) |
| } |
| |
| /// Attempt to parse a positive or negative number. |
| pub fn parse_number<T>(&mut self) -> Result<T> |
| where |
| T: Num<FromStrRadixErr = ParseIntError>, |
| { |
| let (remainder, val) = any_number(self.input) |
| .finish() |
| .map_err(|_| self.error_here(ErrorKind::InvalidNumber))?; |
| |
| self.input = remainder; |
| Ok(val) |
| } |
| |
| /// Consume this deserializer and return a `TrailingCharacters` error if some input was |
| /// remaining. |
| /// |
| /// This is useful to confirm that the whole input has been consumed without any extra elements. |
| pub fn finish(self) -> Result<()> { |
| if self.input.is_empty() { |
| Ok(()) |
| } else { |
| Err(self.error_here(ErrorKind::TrailingCharacters)) |
| } |
| } |
| } |
| |
| impl<'de> de::MapAccess<'de> for KeyValueDeserializer<'de> { |
| type Error = ParseError; |
| |
| fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>> |
| where |
| K: de::DeserializeSeed<'de>, |
| { |
| // Detect end of input or struct. |
| match self.peek_char() { |
| None | Some(']') => return Ok(None), |
| _ => (), |
| } |
| |
| self.has_equal = false; |
| |
| let had_implicit_identifier = self.next_identifier.is_some(); |
| let val = seed.deserialize(&mut *self).map(Some)?; |
| // We just "deserialized" the content of `next_identifier`, so there should be no equal |
| // character in the input. We can return now. |
| if had_implicit_identifier { |
| self.has_equal = true; |
| return Ok(val); |
| } |
| |
| match self.peek_char() { |
| // We expect an equal after an identifier. |
| Some('=') => { |
| self.skip_char(); |
| self.has_equal = true; |
| Ok(val) |
| } |
| // Ok if we are parsing a boolean where an empty value means true. |
| c if is_separator(c) => Ok(val), |
| _ => Err(self.error_here(ErrorKind::ExpectedEqual)), |
| } |
| } |
| |
| fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value> |
| where |
| V: de::DeserializeSeed<'de>, |
| { |
| let val = seed.deserialize(&mut *self)?; |
| |
| self.confirm_separator()?; |
| |
| Ok(val) |
| } |
| } |
| |
| /// `MapAccess` for a map with no members specified. |
| /// |
| /// This is used to allow a struct enum type to be specified without `[` and `]`, in which case |
| /// all its members will take their default value: |
| /// |
| /// ``` |
| /// # use serde_keyvalue::from_key_values; |
| /// # use serde::Deserialize; |
| /// #[derive(Deserialize, PartialEq, Eq, Debug)] |
| /// #[serde(rename_all = "kebab-case")] |
| /// enum FlipMode { |
| /// Active { |
| /// #[serde(default)] |
| /// switch1: bool, |
| /// #[serde(default)] |
| /// switch2: bool, |
| /// }, |
| /// } |
| /// #[derive(Deserialize, PartialEq, Eq, Debug)] |
| /// struct TestStruct { |
| /// mode: FlipMode, |
| /// } |
| /// let res: TestStruct = from_key_values("mode=active").unwrap(); |
| /// assert_eq!( |
| /// res, |
| /// TestStruct { |
| /// mode: FlipMode::Active { |
| /// switch1: false, |
| /// switch2: false |
| /// } |
| /// } |
| /// ); |
| /// ``` |
| struct EmptyMapAccess; |
| |
| impl<'de> de::MapAccess<'de> for EmptyMapAccess { |
| type Error = ParseError; |
| |
| fn next_key_seed<K>(&mut self, _seed: K) -> Result<Option<K::Value>> |
| where |
| K: de::DeserializeSeed<'de>, |
| { |
| Ok(None) |
| } |
| |
| fn next_value_seed<V>(&mut self, _seed: V) -> Result<V::Value> |
| where |
| V: de::DeserializeSeed<'de>, |
| { |
| // Never reached because `next_key_seed` never returns a valid key. |
| unreachable!() |
| } |
| } |
| |
| impl<'de> de::EnumAccess<'de> for &mut KeyValueDeserializer<'de> { |
| type Error = ParseError; |
| type Variant = Self; |
| |
| fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant)> |
| where |
| V: de::DeserializeSeed<'de>, |
| { |
| let val = seed.deserialize(&mut *self)?; |
| Ok((val, self)) |
| } |
| } |
| |
| impl<'de> de::VariantAccess<'de> for &mut KeyValueDeserializer<'de> { |
| type Error = ParseError; |
| |
| fn unit_variant(self) -> Result<()> { |
| Ok(()) |
| } |
| |
| fn newtype_variant_seed<T>(self, _seed: T) -> Result<T::Value> |
| where |
| T: de::DeserializeSeed<'de>, |
| { |
| unimplemented!() |
| } |
| |
| fn tuple_variant<V>(self, len: usize, visitor: V) -> Result<V::Value> |
| where |
| V: de::Visitor<'de>, |
| { |
| self.deserialize_tuple(len, visitor) |
| } |
| |
| fn struct_variant<V>(self, _fields: &'static [&'static str], visitor: V) -> Result<V::Value> |
| where |
| V: de::Visitor<'de>, |
| { |
| if self.peek_char() == Some('[') { |
| self.next_char(); |
| let val = self.deserialize_map(visitor)?; |
| |
| if self.peek_char() != Some(']') { |
| Err(self.error_here(ErrorKind::ExpectedCloseBracket)) |
| } else { |
| self.next_char(); |
| Ok(val) |
| } |
| } else { |
| // The `EmptyMapAccess` failing to parse means that this enum must take arguments, i.e. |
| // that an opening bracket is expected. |
| visitor |
| .visit_map(EmptyMapAccess) |
| .map_err(|_| self.error_here(ErrorKind::ExpectedOpenBracket)) |
| } |
| } |
| } |
| |
| impl<'de> de::SeqAccess<'de> for KeyValueDeserializer<'de> { |
| type Error = ParseError; |
| |
| fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>> |
| where |
| T: de::DeserializeSeed<'de>, |
| { |
| if self.peek_char() == Some(']') { |
| return Ok(None); |
| } |
| |
| let value = seed.deserialize(&mut *self)?; |
| |
| self.confirm_separator()?; |
| |
| Ok(Some(value)) |
| } |
| } |
| |
| impl<'de> de::Deserializer<'de> for &mut KeyValueDeserializer<'de> { |
| type Error = ParseError; |
| |
| fn deserialize_any<V>(self, visitor: V) -> Result<V::Value> |
| where |
| V: serde::de::Visitor<'de>, |
| { |
| match self.peek_char() { |
| // If we have no value following, then we are dealing with a boolean flag. |
| c if is_separator(c) => return self.deserialize_bool(visitor), |
| // Opening bracket means we have a sequence. |
| Some('[') => return self.deserialize_seq(visitor), |
| _ => (), |
| } |
| |
| // This is ambiguous as technically any argument could be an unquoted string. However we |
| // don't have any type information here, so try to guess it on a best-effort basis... |
| if any_number::<i64>(self.input).is_ok() { |
| self.deserialize_i64(visitor) |
| } else if any_number::<u64>(self.input).is_ok() { |
| self.deserialize_u64(visitor) |
| } else if any_bool(self.input).is_ok() { |
| self.deserialize_bool(visitor) |
| } else { |
| self.deserialize_str(visitor) |
| } |
| } |
| |
| fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value> |
| where |
| V: serde::de::Visitor<'de>, |
| { |
| // It is valid to just mention a bool as a flag and not specify its value - in this case |
| // the value is set as `true`. |
| let val = if self.has_equal { |
| self.parse_bool()? |
| } else { |
| true |
| }; |
| visitor.visit_bool(val) |
| } |
| |
| fn deserialize_i8<V>(self, visitor: V) -> Result<V::Value> |
| where |
| V: serde::de::Visitor<'de>, |
| { |
| visitor.visit_i8(self.parse_number()?) |
| } |
| |
| fn deserialize_i16<V>(self, visitor: V) -> Result<V::Value> |
| where |
| V: serde::de::Visitor<'de>, |
| { |
| visitor.visit_i16(self.parse_number()?) |
| } |
| |
| fn deserialize_i32<V>(self, visitor: V) -> Result<V::Value> |
| where |
| V: serde::de::Visitor<'de>, |
| { |
| visitor.visit_i32(self.parse_number()?) |
| } |
| |
| fn deserialize_i64<V>(self, visitor: V) -> Result<V::Value> |
| where |
| V: serde::de::Visitor<'de>, |
| { |
| visitor.visit_i64(self.parse_number()?) |
| } |
| |
| fn deserialize_u8<V>(self, visitor: V) -> Result<V::Value> |
| where |
| V: serde::de::Visitor<'de>, |
| { |
| visitor.visit_u8(self.parse_number()?) |
| } |
| |
| fn deserialize_u16<V>(self, visitor: V) -> Result<V::Value> |
| where |
| V: serde::de::Visitor<'de>, |
| { |
| visitor.visit_u16(self.parse_number()?) |
| } |
| |
| fn deserialize_u32<V>(self, visitor: V) -> Result<V::Value> |
| where |
| V: serde::de::Visitor<'de>, |
| { |
| visitor.visit_u32(self.parse_number()?) |
| } |
| |
| fn deserialize_u64<V>(self, visitor: V) -> Result<V::Value> |
| where |
| V: serde::de::Visitor<'de>, |
| { |
| visitor.visit_u64(self.parse_number()?) |
| } |
| |
| fn deserialize_f32<V>(self, _visitor: V) -> Result<V::Value> |
| where |
| V: serde::de::Visitor<'de>, |
| { |
| unimplemented!() |
| } |
| |
| fn deserialize_f64<V>(self, _visitor: V) -> Result<V::Value> |
| where |
| V: serde::de::Visitor<'de>, |
| { |
| unimplemented!() |
| } |
| |
| fn deserialize_char<V>(self, visitor: V) -> Result<V::Value> |
| where |
| V: serde::de::Visitor<'de>, |
| { |
| visitor.visit_char( |
| self.next_char() |
| .ok_or_else(|| self.error_here(ErrorKind::Eof))?, |
| ) |
| } |
| |
| fn deserialize_str<V>(self, visitor: V) -> Result<V::Value> |
| where |
| V: serde::de::Visitor<'de>, |
| { |
| match self.parse_string()? { |
| Cow::Borrowed(s) => visitor.visit_borrowed_str(s), |
| Cow::Owned(s) => visitor.visit_string(s), |
| } |
| } |
| |
| fn deserialize_string<V>(self, visitor: V) -> Result<V::Value> |
| where |
| V: serde::de::Visitor<'de>, |
| { |
| self.deserialize_str(visitor) |
| } |
| |
| fn deserialize_bytes<V>(self, _visitor: V) -> Result<V::Value> |
| where |
| V: serde::de::Visitor<'de>, |
| { |
| unimplemented!() |
| } |
| |
| fn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value> |
| where |
| V: serde::de::Visitor<'de>, |
| { |
| self.deserialize_bytes(visitor) |
| } |
| |
| fn deserialize_option<V>(self, visitor: V) -> Result<V::Value> |
| where |
| V: serde::de::Visitor<'de>, |
| { |
| // The fact that an option is specified implies that is exists, hence we always visit |
| // Some() here. |
| visitor.visit_some(self) |
| } |
| |
| fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value> |
| where |
| V: serde::de::Visitor<'de>, |
| { |
| visitor.visit_unit() |
| } |
| |
| fn deserialize_unit_struct<V>(self, _name: &'static str, visitor: V) -> Result<V::Value> |
| where |
| V: serde::de::Visitor<'de>, |
| { |
| self.deserialize_unit(visitor) |
| } |
| |
| fn deserialize_newtype_struct<V>(self, _name: &'static str, visitor: V) -> Result<V::Value> |
| where |
| V: serde::de::Visitor<'de>, |
| { |
| visitor.visit_newtype_struct(self) |
| } |
| |
| fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value> |
| where |
| V: serde::de::Visitor<'de>, |
| { |
| if self.peek_char() == Some('[') { |
| self.next_char(); |
| let val = visitor.visit_seq(&mut *self)?; |
| |
| if self.peek_char() != Some(']') { |
| Err(self.error_here(ErrorKind::ExpectedCloseBracket)) |
| } else { |
| self.next_char(); |
| Ok(val) |
| } |
| } else { |
| // The `EmptyMapAccess` failing to parse means that this sequence must take arguments, |
| // i.e. that an opening bracket is expected. |
| visitor |
| .visit_map(EmptyMapAccess) |
| .map_err(|_| self.error_here(ErrorKind::ExpectedOpenBracket)) |
| } |
| } |
| |
| fn deserialize_tuple<V>(self, _len: usize, visitor: V) -> Result<V::Value> |
| where |
| V: serde::de::Visitor<'de>, |
| { |
| self.deserialize_seq(visitor) |
| } |
| |
| fn deserialize_tuple_struct<V>( |
| self, |
| _name: &'static str, |
| _len: usize, |
| _visitor: V, |
| ) -> Result<V::Value> |
| where |
| V: serde::de::Visitor<'de>, |
| { |
| unimplemented!() |
| } |
| |
| fn deserialize_map<V>(self, visitor: V) -> Result<V::Value> |
| where |
| V: serde::de::Visitor<'de>, |
| { |
| // The top structure (i.e. the first structure that we will ever parse) does not need to be |
| // enclosed in braces, but inner structures do. |
| // |
| // We need to do this here as well as in `deserialize_struct` because the top-element of |
| // flattened structs will be a map, not a struct. |
| self.top_struct_parsed = true; |
| |
| visitor.visit_map(self) |
| } |
| |
| fn deserialize_struct<V>( |
| self, |
| _name: &'static str, |
| fields: &'static [&'static str], |
| visitor: V, |
| ) -> Result<V::Value> |
| where |
| V: serde::de::Visitor<'de>, |
| { |
| // The top structure (i.e. the first structure that we will ever parse) does not need to be |
| // enclosed in braces, but inner structures do. |
| let top_struct_parsed = std::mem::replace(&mut self.top_struct_parsed, true); |
| |
| if top_struct_parsed { |
| if self.peek_char() == Some('[') { |
| self.next_char(); |
| } else { |
| // The `EmptyMapAccess` failing to parse means that this struct must take |
| // arguments, i.e. that an opening bracket is expected. |
| return visitor |
| .visit_map(EmptyMapAccess) |
| .map_err(|_| self.error_here(ErrorKind::ExpectedOpenBracket)); |
| } |
| } |
| |
| // The name of the first field of a struct can be omitted (see documentation of |
| // `next_identifier` for details). |
| // |
| // To detect this, peek the next identifier, and check if the character following is '='. If |
| // it is not, then we may have a value in first position, unless the value is identical to |
| // one of the field's name - in this case, assume this is a boolean using the flag syntax. |
| self.next_identifier = match any_identifier(self.input) { |
| Ok((_, s)) => match self.input.chars().nth(s.chars().count()) { |
| Some('=') => None, |
| _ => { |
| if fields.contains(&s) { |
| None |
| } else { |
| fields.first().copied() |
| } |
| } |
| }, |
| // Not an identifier, probably means this is a value for the first field then. |
| Err(_) => fields.first().copied(), |
| }; |
| |
| let ret = visitor.visit_map(&mut *self)?; |
| |
| if top_struct_parsed { |
| if self.peek_char() == Some(']') { |
| self.next_char(); |
| } else { |
| return Err(self.error_here(ErrorKind::ExpectedCloseBracket)); |
| } |
| } |
| |
| Ok(ret) |
| } |
| |
| fn deserialize_enum<V>( |
| self, |
| _name: &'static str, |
| _variants: &'static [&'static str], |
| visitor: V, |
| ) -> Result<V::Value> |
| where |
| V: serde::de::Visitor<'de>, |
| { |
| visitor.visit_enum(self) |
| } |
| |
| fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value> |
| where |
| V: serde::de::Visitor<'de>, |
| { |
| let identifier = self |
| .next_identifier |
| .take() |
| .map_or_else(|| self.parse_identifier(), Ok)?; |
| |
| visitor.visit_borrowed_str(identifier) |
| } |
| |
| fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value> |
| where |
| V: serde::de::Visitor<'de>, |
| { |
| self.deserialize_any(visitor) |
| } |
| } |
| |
| /// Attempts to deserialize `T` from the key-values string `input`. |
| pub fn from_key_values<'a, T>(input: &'a str) -> Result<T> |
| where |
| T: Deserialize<'a>, |
| { |
| let mut deserializer = KeyValueDeserializer::from(input); |
| let ret = T::deserialize(&mut deserializer)?; |
| deserializer.finish()?; |
| |
| Ok(ret) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use std::collections::BTreeSet; |
| use std::path::PathBuf; |
| |
| use super::*; |
| |
| #[derive(Deserialize, PartialEq, Debug)] |
| struct SingleStruct<T> { |
| m: T, |
| } |
| |
| #[test] |
| fn nom_any_separator() { |
| let test_str = ",foo"; |
| assert_eq!(any_separator(test_str), Ok((&test_str[1..], Some(',')))); |
| let test_str = "]bar"; |
| assert_eq!(any_separator(test_str), Ok((&test_str[1..], Some(']')))); |
| let test_str = ""; |
| assert_eq!(any_separator(test_str), Ok((test_str, None))); |
| |
| let test_str = "something,anything"; |
| assert_eq!( |
| any_separator(test_str), |
| Err(nom::Err::Error(nom::error::Error::new( |
| test_str, |
| nom::error::ErrorKind::Char |
| ))) |
| ); |
| } |
| |
| #[test] |
| fn deserialize_number() { |
| let res = from_key_values::<SingleStruct<usize>>("m=54").unwrap(); |
| assert_eq!(res.m, 54); |
| |
| let res = from_key_values::<SingleStruct<isize>>("m=-54").unwrap(); |
| assert_eq!(res.m, -54); |
| |
| // Parsing a signed into an unsigned? |
| let res = from_key_values::<SingleStruct<u32>>("m=-54").unwrap_err(); |
| assert_eq!( |
| res, |
| ParseError { |
| kind: ErrorKind::InvalidNumber, |
| pos: 2 |
| } |
| ); |
| |
| // Value too big for a signed? |
| let val = i32::MAX as u32 + 1; |
| let res = from_key_values::<SingleStruct<i32>>(&format!("m={val}")).unwrap_err(); |
| assert_eq!( |
| res, |
| ParseError { |
| kind: ErrorKind::InvalidNumber, |
| pos: 2 |
| } |
| ); |
| |
| // Not a number. |
| let res = from_key_values::<SingleStruct<usize>>("m=test").unwrap_err(); |
| assert_eq!( |
| res, |
| ParseError { |
| kind: ErrorKind::InvalidNumber, |
| pos: 2, |
| } |
| ); |
| |
| // Parsing hex values |
| let res: SingleStruct<usize> = |
| from_key_values::<SingleStruct<usize>>("m=0x1234abcd").unwrap(); |
| assert_eq!(res.m, 0x1234abcd); |
| let res: SingleStruct<isize> = |
| from_key_values::<SingleStruct<isize>>("m=-0x1234abcd").unwrap(); |
| assert_eq!(res.m, -0x1234abcd); |
| |
| // Hex value outside range |
| let res: ParseError = from_key_values::<SingleStruct<usize>>("m=0xg").unwrap_err(); |
| assert_eq!( |
| res, |
| ParseError { |
| kind: ErrorKind::InvalidNumber, |
| pos: 2, |
| } |
| ); |
| |
| // Parsing octal values |
| let res: SingleStruct<usize> = from_key_values::<SingleStruct<usize>>("m=0o755").unwrap(); |
| assert_eq!(res.m, 0o755); |
| let res: SingleStruct<isize> = from_key_values::<SingleStruct<isize>>("m=-0o755").unwrap(); |
| assert_eq!(res.m, -0o755); |
| |
| // Octal value outside range |
| let res: ParseError = from_key_values::<SingleStruct<usize>>("m=0o8").unwrap_err(); |
| assert_eq!( |
| res, |
| ParseError { |
| kind: ErrorKind::InvalidNumber, |
| pos: 2, |
| } |
| ); |
| |
| // Parsing binary values |
| let res: SingleStruct<usize> = from_key_values::<SingleStruct<usize>>("m=0b1100").unwrap(); |
| assert_eq!(res.m, 0b1100); |
| let res: SingleStruct<isize> = from_key_values::<SingleStruct<isize>>("m=-0b1100").unwrap(); |
| assert_eq!(res.m, -0b1100); |
| |
| // Binary value outside range |
| let res: ParseError = from_key_values::<SingleStruct<usize>>("m=0b2").unwrap_err(); |
| assert_eq!( |
| res, |
| ParseError { |
| kind: ErrorKind::InvalidNumber, |
| pos: 2, |
| } |
| ); |
| } |
| |
| #[test] |
| fn deserialize_string() { |
| let kv = "m=John"; |
| let res = from_key_values::<SingleStruct<String>>(kv).unwrap(); |
| assert_eq!(res.m, "John".to_string()); |
| |
| // Spaces are valid (but not recommended) in unquoted strings. |
| let kv = "m=John Doe"; |
| let res = from_key_values::<SingleStruct<String>>(kv).unwrap(); |
| assert_eq!(res.m, "John Doe".to_string()); |
| |
| // Empty string is not valid if unquoted |
| let kv = "m="; |
| let err = from_key_values::<SingleStruct<String>>(kv).unwrap_err(); |
| assert_eq!( |
| err, |
| ParseError { |
| kind: ErrorKind::ExpectedString, |
| pos: 2 |
| } |
| ); |
| |
| // Quoted strings. |
| let kv = r#"m="John Doe""#; |
| let res = from_key_values::<SingleStruct<String>>(kv).unwrap(); |
| assert_eq!(res.m, "John Doe".to_string()); |
| let kv = r#"m='John Doe'"#; |
| let res = from_key_values::<SingleStruct<String>>(kv).unwrap(); |
| assert_eq!(res.m, "John Doe".to_string()); |
| |
| // Empty quoted strings. |
| let kv = r#"m="""#; |
| let res = from_key_values::<SingleStruct<String>>(kv).unwrap(); |
| assert_eq!(res.m, "".to_string()); |
| let kv = r#"m=''"#; |
| let res = from_key_values::<SingleStruct<String>>(kv).unwrap(); |
| assert_eq!(res.m, "".to_string()); |
| |
| // "=", ",", "[", "]" and "'" in quote. |
| let kv = r#"m="val = [10, 20, 'a']""#; |
| let res = from_key_values::<SingleStruct<String>>(kv).unwrap(); |
| assert_eq!(res.m, r#"val = [10, 20, 'a']"#.to_string()); |
| |
| // Quotes in unquoted strings are forbidden. |
| let kv = r#"m=val="a""#; |
| let err = from_key_values::<SingleStruct<String>>(kv).unwrap_err(); |
| assert_eq!( |
| err, |
| ParseError { |
| kind: ErrorKind::InvalidCharInString, |
| pos: 6 |
| } |
| ); |
| let kv = r#"m=val='a'"#; |
| let err = from_key_values::<SingleStruct<String>>(kv).unwrap_err(); |
| assert_eq!( |
| err, |
| ParseError { |
| kind: ErrorKind::InvalidCharInString, |
| pos: 6 |
| } |
| ); |
| |
| // Brackets in unquoted strings are forbidden. |
| let kv = r#"m=val=[a]"#; |
| let err = from_key_values::<SingleStruct<String>>(kv).unwrap_err(); |
| assert_eq!( |
| err, |
| ParseError { |
| kind: ErrorKind::InvalidCharInString, |
| pos: 6 |
| } |
| ); |
| |
| // Numbers and booleans are technically valid strings. |
| let kv = "m=10"; |
| let res = from_key_values::<SingleStruct<String>>(kv).unwrap(); |
| assert_eq!(res.m, "10".to_string()); |
| let kv = "m=false"; |
| let res = from_key_values::<SingleStruct<String>>(kv).unwrap(); |
| assert_eq!(res.m, "false".to_string()); |
| |
| // Escaped quote. |
| let kv = r#"m="Escaped \" quote""#; |
| let res = from_key_values::<SingleStruct<String>>(kv).unwrap(); |
| assert_eq!(res.m, r#"Escaped " quote"#.to_string()); |
| |
| // Escaped slash at end of string. |
| let kv = r#"m="Escaped slash\\""#; |
| let res = from_key_values::<SingleStruct<String>>(kv).unwrap(); |
| assert_eq!(res.m, r"Escaped slash\".to_string()); |
| |
| // Characters within single quotes should not be escaped. |
| let kv = r#"m='Escaped \" quote'"#; |
| let res = from_key_values::<SingleStruct<String>>(kv).unwrap(); |
| assert_eq!(res.m, r#"Escaped \" quote"#.to_string()); |
| let kv = r"m='Escaped slash\\'"; |
| let res = from_key_values::<SingleStruct<String>>(kv).unwrap(); |
| assert_eq!(res.m, r"Escaped slash\\".to_string()); |
| } |
| |
| #[test] |
| fn deserialize_unit() { |
| from_key_values::<SingleStruct<()>>("m").unwrap(); |
| from_key_values::<SingleStruct<()>>("m=").unwrap(); |
| |
| from_key_values::<SingleStruct<()>>("").unwrap_err(); |
| from_key_values::<SingleStruct<()>>("p").unwrap_err(); |
| from_key_values::<SingleStruct<()>>("m=10").unwrap_err(); |
| } |
| |
| #[test] |
| fn deserialize_bool() { |
| let res = from_key_values::<SingleStruct<bool>>("m=true").unwrap(); |
| assert!(res.m); |
| |
| let res = from_key_values::<SingleStruct<bool>>("m=false").unwrap(); |
| assert!(!res.m); |
| |
| let res = from_key_values::<SingleStruct<bool>>("m").unwrap(); |
| assert!(res.m); |
| |
| let res = from_key_values::<SingleStruct<bool>>("m=10").unwrap_err(); |
| assert_eq!( |
| res, |
| ParseError { |
| kind: ErrorKind::ExpectedBoolean, |
| pos: 2, |
| } |
| ); |
| |
| let res = from_key_values::<SingleStruct<bool>>("m=").unwrap_err(); |
| assert_eq!( |
| res, |
| ParseError { |
| kind: ErrorKind::ExpectedBoolean, |
| pos: 2, |
| } |
| ); |
| } |
| |
| #[test] |
| fn deserialize_complex_struct() { |
| #[derive(Deserialize, PartialEq, Debug)] |
| struct TestStruct { |
| num: usize, |
| path: PathBuf, |
| enable: bool, |
| } |
| let kv = "num=54,path=/dev/foomatic,enable=false"; |
| let res = from_key_values::<TestStruct>(kv).unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| num: 54, |
| path: "/dev/foomatic".into(), |
| enable: false, |
| } |
| ); |
| |
| let kv = "num=0x54,path=/dev/foomatic,enable=false"; |
| let res = from_key_values::<TestStruct>(kv).unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| num: 0x54, |
| path: "/dev/foomatic".into(), |
| enable: false, |
| } |
| ); |
| |
| let kv = "enable,path=/usr/lib/libossom.so.1,num=12"; |
| let res = from_key_values::<TestStruct>(kv).unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| num: 12, |
| path: "/usr/lib/libossom.so.1".into(), |
| enable: true, |
| } |
| ); |
| |
| // Braces specified at top-level. |
| let kv = "[enable,path=/usr/lib/libossom.so.1,num=12]"; |
| assert!(from_key_values::<TestStruct>(kv).is_err()); |
| } |
| |
| #[test] |
| fn deserialize_unknown_field() { |
| #[derive(Deserialize, PartialEq, Debug)] |
| #[serde(deny_unknown_fields)] |
| struct TestStruct { |
| num: usize, |
| path: PathBuf, |
| enable: bool, |
| } |
| |
| let kv = "enable,path=/usr/lib/libossom.so.1,num=12,foo=bar"; |
| assert!(from_key_values::<TestStruct>(kv).is_err()); |
| } |
| |
| #[test] |
| fn deserialize_option() { |
| #[derive(Deserialize, PartialEq, Debug)] |
| struct TestStruct { |
| num: u32, |
| opt: Option<u32>, |
| } |
| let kv = "num=16,opt=12"; |
| let res: TestStruct = from_key_values(kv).unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| num: 16, |
| opt: Some(12), |
| } |
| ); |
| |
| let kv = "num=16"; |
| let res: TestStruct = from_key_values(kv).unwrap(); |
| assert_eq!(res, TestStruct { num: 16, opt: None }); |
| |
| let kv = ""; |
| assert!(from_key_values::<TestStruct>(kv).is_err()); |
| } |
| |
| #[test] |
| fn deserialize_optional_struct_with_default() { |
| #[derive(Deserialize, PartialEq, Debug)] |
| struct DefaultStruct { |
| #[serde(default)] |
| param: u32, |
| } |
| |
| #[derive(Deserialize, PartialEq, Debug)] |
| struct TestStruct { |
| flag: Option<DefaultStruct>, |
| } |
| |
| // Specify member explicitly |
| let kv = "flag=[param=12]"; |
| let res: TestStruct = from_key_values(kv).unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| flag: Some(DefaultStruct { param: 12 }) |
| } |
| ); |
| |
| // No member specified, braces present. |
| let kv = "flag=[]"; |
| let res: TestStruct = from_key_values(kv).unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| flag: Some(DefaultStruct { param: 0 }) |
| } |
| ); |
| |
| // No member specified, no braces. |
| let kv = "flag="; |
| let res: TestStruct = from_key_values(kv).unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| flag: Some(DefaultStruct { param: 0 }) |
| } |
| ); |
| |
| // No member specified, no braces, no equal sign. |
| let kv = "flag"; |
| let res: TestStruct = from_key_values(kv).unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| flag: Some(DefaultStruct { param: 0 }) |
| } |
| ); |
| |
| // No closing brace. |
| let kv = "flag=["; |
| assert!(from_key_values::<TestStruct>(kv).is_err()); |
| |
| // No opening brace. |
| let kv = "flag=]"; |
| assert!(from_key_values::<TestStruct>(kv).is_err()); |
| } |
| |
| #[test] |
| fn deserialize_optional_struct_within_flattened() { |
| #[derive(Deserialize, PartialEq, Debug)] |
| struct FlatStruct { |
| a: u32, |
| #[serde(default)] |
| b: String, |
| } |
| |
| #[derive(Deserialize, PartialEq, Debug)] |
| struct DefaultStruct { |
| #[serde(default)] |
| param: u32, |
| } |
| |
| #[derive(Deserialize, PartialEq, Debug)] |
| struct TestStruct { |
| #[serde(flatten)] |
| flat: FlatStruct, |
| flag: Option<DefaultStruct>, |
| } |
| |
| // Everything specified. |
| let kv = "a=10,b=foomatic,flag=[param=24]"; |
| let res: TestStruct = from_key_values(kv).unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| flat: FlatStruct { |
| a: 10, |
| b: "foomatic".into(), |
| }, |
| flag: Some(DefaultStruct { param: 24 }) |
| } |
| ); |
| |
| // Flag left to default value. |
| let kv = "a=10,b=foomatic,flag"; |
| let res: TestStruct = from_key_values(kv).unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| flat: FlatStruct { |
| a: 10, |
| b: "foomatic".into(), |
| }, |
| flag: Some(DefaultStruct { param: 0 }) |
| } |
| ); |
| |
| // Flattened default value unspecified. |
| let kv = "a=10,flag=[param=24]"; |
| let res: TestStruct = from_key_values(kv).unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| flat: FlatStruct { |
| a: 10, |
| b: Default::default(), |
| }, |
| flag: Some(DefaultStruct { param: 24 }) |
| } |
| ); |
| |
| // No optional, no default value. |
| let kv = "a=10"; |
| let res: TestStruct = from_key_values(kv).unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| flat: FlatStruct { |
| a: 10, |
| b: Default::default(), |
| }, |
| flag: None, |
| } |
| ); |
| |
| // Required member unspecified. |
| let kv = "b=foomatic,flag=[param=24]"; |
| assert!(from_key_values::<TestStruct>(kv).is_err()); |
| |
| // Braces specified at top-level. |
| let kv = "[a=10,b=foomatic,flag=[param=24]]"; |
| assert!(from_key_values::<TestStruct>(kv).is_err()); |
| } |
| |
| #[test] |
| fn deserialize_enum() { |
| #[derive(Deserialize, PartialEq, Debug)] |
| enum TestEnum { |
| #[serde(rename = "first")] |
| FirstVariant, |
| #[serde(rename = "second")] |
| SecondVariant, |
| } |
| let res: TestEnum = from_key_values("first").unwrap(); |
| assert_eq!(res, TestEnum::FirstVariant,); |
| |
| let res: TestEnum = from_key_values("second").unwrap(); |
| assert_eq!(res, TestEnum::SecondVariant,); |
| |
| from_key_values::<TestEnum>("third").unwrap_err(); |
| } |
| |
| #[test] |
| fn deserialize_embedded_enum() { |
| #[derive(Deserialize, PartialEq, Debug)] |
| enum TestEnum { |
| #[serde(rename = "first")] |
| FirstVariant, |
| #[serde(rename = "second")] |
| SecondVariant, |
| } |
| #[derive(Deserialize, PartialEq, Debug)] |
| struct TestStruct { |
| variant: TestEnum, |
| #[serde(default)] |
| active: bool, |
| } |
| let res: TestStruct = from_key_values("variant=first").unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| variant: TestEnum::FirstVariant, |
| active: false, |
| } |
| ); |
| let res: TestStruct = from_key_values("variant=second,active=true").unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| variant: TestEnum::SecondVariant, |
| active: true, |
| } |
| ); |
| let res: TestStruct = from_key_values("active=true,variant=second").unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| variant: TestEnum::SecondVariant, |
| active: true, |
| } |
| ); |
| let res: TestStruct = from_key_values("active,variant=second").unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| variant: TestEnum::SecondVariant, |
| active: true, |
| } |
| ); |
| let res: TestStruct = from_key_values("active=false,variant=second").unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| variant: TestEnum::SecondVariant, |
| active: false, |
| } |
| ); |
| } |
| |
| #[test] |
| fn deserialize_untagged_enum() { |
| #[derive(Deserialize, PartialEq, Debug)] |
| #[serde(untagged)] |
| enum TestEnum { |
| FirstVariant { first: u32 }, |
| SecondVariant { second: bool }, |
| } |
| |
| #[derive(Deserialize, PartialEq, Debug)] |
| struct TestStruct { |
| #[serde(flatten)] |
| variant: TestEnum, |
| } |
| |
| let res: TestStruct = from_key_values("first=10").unwrap(); |
| assert_eq!(res.variant, TestEnum::FirstVariant { first: 10 }); |
| |
| let res: TestStruct = from_key_values("second=false").unwrap(); |
| assert_eq!(res.variant, TestEnum::SecondVariant { second: false },); |
| |
| let res: TestStruct = from_key_values("second").unwrap(); |
| assert_eq!(res.variant, TestEnum::SecondVariant { second: true },); |
| |
| from_key_values::<TestStruct>("third=10").unwrap_err(); |
| from_key_values::<TestStruct>("first=some_string").unwrap_err(); |
| from_key_values::<TestStruct>("second=10").unwrap_err(); |
| } |
| |
| #[test] |
| fn deserialize_first_arg_string() { |
| #[derive(Deserialize, PartialEq, Debug)] |
| struct TestStruct { |
| name: String, |
| num: u8, |
| } |
| let res: TestStruct = from_key_values("name=foo,num=12").unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| name: "foo".into(), |
| num: 12, |
| } |
| ); |
| |
| let res: TestStruct = from_key_values("foo,num=12").unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| name: "foo".into(), |
| num: 12, |
| } |
| ); |
| } |
| |
| #[test] |
| fn deserialize_first_arg_int() { |
| #[derive(Deserialize, PartialEq, Debug)] |
| struct TestStruct { |
| num: u8, |
| name: String, |
| } |
| let res: TestStruct = from_key_values("name=foo,num=12").unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| num: 12, |
| name: "foo".into(), |
| } |
| ); |
| |
| let res: TestStruct = from_key_values("12,name=foo").unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| num: 12, |
| name: "foo".into(), |
| } |
| ); |
| } |
| |
| #[test] |
| fn deserialize_tuple() { |
| #[derive(Deserialize, PartialEq, Debug)] |
| struct TestStruct { |
| size: (u32, u32), |
| } |
| |
| let res: TestStruct = from_key_values("size=[320,200]").unwrap(); |
| assert_eq!(res, TestStruct { size: (320, 200) }); |
| |
| // Unterminated tuple. |
| let err = from_key_values::<TestStruct>("size=[320]").unwrap_err(); |
| assert_eq!( |
| err, |
| ParseError { |
| kind: ErrorKind::SerdeError("invalid length 1, expected a tuple of size 2".into()), |
| pos: 0, |
| } |
| ); |
| |
| // Too many elements in tuple. |
| let err = from_key_values::<TestStruct>("size=[320,200,255]").unwrap_err(); |
| assert_eq!( |
| err, |
| ParseError { |
| kind: ErrorKind::ExpectedCloseBracket, |
| pos: 14, |
| } |
| ); |
| |
| // Non-closed sequence is invalid. |
| let err = from_key_values::<TestStruct>("size=[320,200").unwrap_err(); |
| assert_eq!( |
| err, |
| ParseError { |
| kind: ErrorKind::ExpectedCloseBracket, |
| pos: 13, |
| } |
| ); |
| } |
| |
| #[test] |
| fn deserialize_vector() { |
| #[derive(Deserialize, PartialEq, Debug)] |
| struct TestStruct { |
| numbers: Vec<u32>, |
| } |
| |
| let res: TestStruct = from_key_values("numbers=[1,2,4,8,16,32,64]").unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| numbers: vec![1, 2, 4, 8, 16, 32, 64], |
| } |
| ); |
| } |
| |
| #[test] |
| fn deserialize_vector_of_strings() { |
| #[derive(Deserialize, PartialEq, Debug)] |
| struct TestStruct { |
| strs: Vec<String>, |
| } |
| |
| // Unquoted strings |
| let res: TestStruct = |
| from_key_values(r#"strs=[singleword,camel_cased,kebab-cased]"#).unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| strs: vec![ |
| "singleword".into(), |
| "camel_cased".into(), |
| "kebab-cased".into() |
| ], |
| } |
| ); |
| |
| // All quoted strings |
| let res: TestStruct = |
| from_key_values(r#"strs=["first string","second string","third string"]"#).unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| strs: vec![ |
| "first string".into(), |
| "second string".into(), |
| "third string".into() |
| ], |
| } |
| ); |
| |
| // Mix |
| let res: TestStruct = |
| from_key_values(r#"strs=[unquoted,"quoted string",'quoted with escape "']"#).unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| strs: vec![ |
| "unquoted".into(), |
| "quoted string".into(), |
| "quoted with escape \"".into() |
| ], |
| } |
| ); |
| } |
| |
| #[test] |
| fn deserialize_vector_of_structs() { |
| #[derive(Deserialize, PartialEq, Debug)] |
| #[serde(deny_unknown_fields)] |
| struct Display { |
| size: (u32, u32), |
| #[serde(default)] |
| disabled: bool, |
| } |
| |
| #[derive(Deserialize, PartialEq, Debug)] |
| #[serde(deny_unknown_fields)] |
| struct TestStruct { |
| displays: Vec<Display>, |
| hostname: Option<String>, |
| } |
| |
| let res: TestStruct = from_key_values("displays=[[size=[640,480]]]").unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| displays: vec![Display { |
| size: (640, 480), |
| disabled: false, |
| }], |
| hostname: None, |
| } |
| ); |
| |
| let res: TestStruct = |
| from_key_values("hostname=crosmatic,displays=[[size=[800,600],disabled]]").unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| displays: vec![Display { |
| size: (800, 600), |
| disabled: true, |
| }], |
| hostname: Some("crosmatic".to_string()), |
| } |
| ); |
| |
| // First field of a struct does not need to be named even if it is not the top-level struct. |
| let res: TestStruct = |
| from_key_values("displays=[[[640,480]],[[800,600],disabled]]").unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| displays: vec![ |
| Display { |
| size: (640, 480), |
| disabled: false, |
| }, |
| Display { |
| size: (800, 600), |
| disabled: true, |
| } |
| ], |
| hostname: None, |
| } |
| ); |
| |
| let res: TestStruct = |
| from_key_values("displays=[[[1024,768]],[size=[800,600],disabled]],hostname=crosmatic") |
| .unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| displays: vec![ |
| Display { |
| size: (1024, 768), |
| disabled: false, |
| }, |
| Display { |
| size: (800, 600), |
| disabled: true, |
| } |
| ], |
| hostname: Some("crosmatic".to_string()), |
| } |
| ); |
| } |
| |
| #[test] |
| fn deserialize_set() { |
| #[derive(Deserialize, PartialEq, Eq, Debug, PartialOrd, Ord)] |
| #[serde(rename_all = "kebab-case")] |
| enum Flags { |
| Awesome, |
| Fluffy, |
| Transparent, |
| } |
| #[derive(Deserialize, PartialEq, Debug)] |
| struct TestStruct { |
| flags: BTreeSet<Flags>, |
| } |
| |
| let res: TestStruct = from_key_values("flags=[awesome,fluffy]").unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| flags: BTreeSet::from([Flags::Awesome, Flags::Fluffy]), |
| } |
| ); |
| |
| // Unknown enum variant? |
| let err = from_key_values::<TestStruct>("flags=[awesome,spiky]").unwrap_err(); |
| assert_eq!( |
| err, |
| ParseError { |
| kind: ErrorKind::SerdeError( |
| "unknown variant `spiky`, expected one of `awesome`, `fluffy`, `transparent`" |
| .into() |
| ), |
| pos: 0, |
| } |
| ); |
| } |
| |
| #[test] |
| fn deserialize_struct_and_tuple_enum() { |
| #[derive(Deserialize, PartialEq, Debug)] |
| #[serde(rename_all = "kebab-case")] |
| enum VideoMode { |
| Fullscreen, |
| WindowAsTuple(u32, u32), |
| WindowAsStruct { width: u32, height: u32 }, |
| } |
| |
| #[derive(Deserialize, PartialEq, Debug)] |
| struct TestStruct { |
| mode: VideoMode, |
| } |
| |
| let res: TestStruct = from_key_values("mode=fullscreen").unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| mode: VideoMode::Fullscreen |
| } |
| ); |
| |
| let res: TestStruct = from_key_values("mode=window-as-tuple[640,480]").unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| mode: VideoMode::WindowAsTuple(640, 480), |
| } |
| ); |
| |
| // Missing values |
| let err = from_key_values::<TestStruct>("mode=window-as-tuple").unwrap_err(); |
| assert_eq!( |
| err, |
| ParseError { |
| kind: ErrorKind::ExpectedOpenBracket, |
| pos: 20, |
| } |
| ); |
| |
| let res: TestStruct = |
| from_key_values("mode=window-as-struct[width=800,height=600]").unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| mode: VideoMode::WindowAsStruct { |
| width: 800, |
| height: 600, |
| } |
| } |
| ); |
| |
| // Missing values. |
| let err = from_key_values::<TestStruct>("mode=window-as-struct").unwrap_err(); |
| assert_eq!( |
| err, |
| ParseError { |
| kind: ErrorKind::ExpectedOpenBracket, |
| pos: 21, |
| } |
| ); |
| } |
| |
| #[test] |
| fn deserialize_struct_enum_with_default() { |
| #[derive(Deserialize, PartialEq, Debug)] |
| #[serde(rename_all = "kebab-case")] |
| enum FlipMode { |
| Inactive, |
| Active { |
| #[serde(default)] |
| switch1: bool, |
| #[serde(default)] |
| switch2: bool, |
| }, |
| } |
| |
| #[derive(Deserialize, PartialEq, Debug)] |
| struct TestStruct { |
| mode: FlipMode, |
| } |
| |
| // Only specify one member and expect the other to be default. |
| let res: TestStruct = from_key_values("mode=active[switch1=true]").unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| mode: FlipMode::Active { |
| switch1: true, |
| switch2: false |
| } |
| } |
| ); |
| |
| // Specify boolean members without explicit value. |
| let res: TestStruct = from_key_values("mode=active[switch1,switch2]").unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| mode: FlipMode::Active { |
| switch1: true, |
| switch2: true |
| } |
| } |
| ); |
| |
| // No member specified, braces present. |
| let res: TestStruct = from_key_values("mode=active[]").unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| mode: FlipMode::Active { |
| switch1: false, |
| switch2: false |
| } |
| } |
| ); |
| |
| // No member specified and no braces. |
| let res: TestStruct = from_key_values("mode=active").unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| mode: FlipMode::Active { |
| switch1: false, |
| switch2: false |
| } |
| } |
| ); |
| |
| // Non-struct variant should be recognized without braces. |
| let res: TestStruct = from_key_values("mode=inactive").unwrap(); |
| assert_eq!( |
| res, |
| TestStruct { |
| mode: FlipMode::Inactive, |
| } |
| ); |
| |
| // Non-struct variant should not accept braces. |
| let err = from_key_values::<TestStruct>("mode=inactive[]").unwrap_err(); |
| assert_eq!( |
| err, |
| ParseError { |
| kind: ErrorKind::ExpectedComma, |
| pos: 13, |
| } |
| ); |
| } |
| } |