Skip to main content

eml_nl/utils/
string_value.rs

1use std::{borrow::Cow, convert::Infallible, num::NonZeroU64};
2
3use thiserror::Error;
4
5use crate::{EMLError, EMLValueResultExt as _};
6
7/// Trait for data types that can be used with [`StringValue`], defines how to parse and serialize the value.
8pub trait StringValueData: Clone {
9    /// The error type returned when parsing the value from a string fails.
10    type Error: std::error::Error + Send + Sync + 'static;
11
12    /// Parse the value from a string.
13    fn parse_from_str(s: &str) -> Result<Self, Self::Error>
14    where
15        Self: Sized;
16
17    /// Convert the value to its raw string representation.
18    fn to_raw_value(&self) -> String;
19}
20
21/// A string value that can either be stored as a raw unparsed string or as a parsed value of type `T`.
22///
23/// The type `T` must implement the [`StringValueData`] trait, which defines how to parse and
24/// serialize the value. This type is used whenever an EML_NL document element or attribute
25/// contains a string value that could be parsed, but where strict parsing is not always desired.
26///
27/// Depending on the parsing mode used when parsing a document, [`StringValue`]
28/// instances may either contain the raw string or the parsed value. When
29/// [`EMLParsingMode::Strict`](crate::io::EMLParsingMode::Strict) is used, all
30/// [`StringValue`] instances will contain the parsed value. When
31/// [`EMLParsingMode::StrictFallback`](crate::io::EMLParsingMode::StrictFallback)
32/// is used, the library will attempt to parse values but will fall back to
33/// storing the raw string if parsing fails. When
34/// [`EMLParsingMode::Loose`](crate::io::EMLParsingMode::Loose) is used, the
35/// library will not even attempt to parse values and will store all values as
36/// raw strings.
37///
38/// In most cases you will want to use the parsed value by using one of the
39/// value retrieving methods:
40/// - [`StringValue::value`]: Get a [`Cow`] containing either a borrowed
41///   reference to the parsed value or an owned parsed value if the StringValue
42///   contains a raw string. If the StringValue contains a raw string and
43///   parsing fails, the error from the parsing attempt will be returned as an
44///   error.
45/// - [`StringValue::copied_value`]: Same as `value`, but only available for
46///   types that implement [`Copy`] and returns a copy of the value instead.
47/// - [`StringValue::cloned_value`]: Same as `value`, but returns a cloned copy
48///   of the value instead of a reference. Only available for types that
49///   implement [`Clone`].
50#[derive(Debug, Clone, PartialEq, Eq, Hash)]
51pub enum StringValue<T: StringValueData> {
52    /// A raw unparsed string value that potentially can be parsed into a value of type `T`.
53    Raw(String),
54    /// Parsed value of type `T`.
55    Parsed(T),
56}
57
58impl<T: StringValueData> StringValue<T> {
59    /// Try to create a [`StringValue`] from the given raw string by parsing it.
60    pub fn from_raw_parsed(s: impl AsRef<str>) -> Result<Self, T::Error> {
61        let v = T::parse_from_str(s.as_ref())?;
62        Ok(StringValue::Parsed(v))
63    }
64
65    /// Create a [`StringValue`] from a raw string.
66    pub fn from_raw(s: impl Into<String>) -> Self {
67        StringValue::Raw(s.into())
68    }
69
70    /// Create a [`StringValue`] from a parsed value.
71    pub fn from_value(v: T) -> Self {
72        StringValue::Parsed(v)
73    }
74
75    /// Get the raw string value.
76    pub fn raw(&self) -> Cow<'_, str> {
77        match self {
78            StringValue::Raw(s) => Cow::Borrowed(s),
79            StringValue::Parsed(v) => Cow::Owned(v.to_raw_value()),
80        }
81    }
82
83    /// Get the parsed value, returning any possible parsing errors.
84    pub fn value_err(&self) -> Result<Cow<'_, T>, T::Error> {
85        match self {
86            StringValue::Raw(s) => {
87                let v = T::parse_from_str(s)?;
88                Ok(Cow::Owned(v))
89            }
90            StringValue::Parsed(v) => Ok(Cow::Borrowed(v)),
91        }
92    }
93
94    /// Get the parsed value, returning a cloned copy of it.
95    ///
96    /// If there is an error, the original parsing error will be returned.
97    pub fn cloned_value_err(&self) -> Result<T, T::Error> {
98        self.value_err().map(|v| v.into_owned())
99    }
100
101    /// Get the parsed value, returning a cloned copy of it.
102    ///
103    /// If there is an error, it will be wrapped in an [`EMLError`].
104    pub fn cloned_value(&self) -> Result<T, EMLError> {
105        self.cloned_value_err().wrap_value_error()
106    }
107
108    /// Get the parsed value, returning any possible parsing errors as an [`EMLError`].
109    ///
110    /// The `element_name` and `span` parameters are used to provide context in the error
111    /// if parsing fails.
112    pub fn value(&self) -> Result<Cow<'_, T>, EMLError> {
113        self.value_err().wrap_value_error()
114    }
115}
116
117impl<T: StringValueData + Copy> StringValue<T> {
118    /// Get the parsed value, returning a copy of it.
119    ///
120    /// This is only available for types that implement [`Copy`].
121    pub fn copied_value_err(&self) -> Result<T, T::Error> {
122        Ok(*self.value_err()?.as_ref())
123    }
124
125    /// Get the parsed value, returning a copy of it.
126    ///
127    /// If there is an error, it will be wrapped in an [`EMLError`].
128    /// This is only available for types that implement [`Copy`].
129    pub fn copied_value(&self) -> Result<T, EMLError> {
130        self.copied_value_err().wrap_value_error()
131    }
132}
133
134impl StringValueData for String {
135    type Error = Infallible;
136
137    fn parse_from_str(s: &str) -> Result<Self, Self::Error>
138    where
139        Self: Sized,
140    {
141        Ok(s.to_string())
142    }
143
144    fn to_raw_value(&self) -> String {
145        self.clone()
146    }
147}
148
149impl StringValueData for u64 {
150    type Error = std::num::ParseIntError;
151
152    fn parse_from_str(s: &str) -> Result<Self, Self::Error>
153    where
154        Self: Sized,
155    {
156        s.parse::<u64>()
157    }
158
159    fn to_raw_value(&self) -> String {
160        self.to_string()
161    }
162}
163
164impl StringValueData for NonZeroU64 {
165    type Error = std::num::ParseIntError;
166
167    fn parse_from_str(s: &str) -> Result<Self, Self::Error>
168    where
169        Self: Sized,
170    {
171        s.parse()
172    }
173
174    fn to_raw_value(&self) -> String {
175        self.get().to_string()
176    }
177}
178
179/// Error returned when parsing a boolean value fails.
180#[derive(Debug, Clone, PartialEq, Eq, Error)]
181#[error("Failed to parse boolean value")]
182pub struct ParseBoolError;
183
184impl StringValueData for bool {
185    type Error = ParseBoolError;
186
187    fn parse_from_str(s: &str) -> Result<Self, Self::Error>
188    where
189        Self: Sized,
190    {
191        Ok(match s {
192            "0" | "false" => false,
193            "1" | "true" => true,
194            _ => return Err(ParseBoolError),
195        })
196    }
197
198    fn to_raw_value(&self) -> String {
199        // Note: We use "true" and "false" as the raw string representation for
200        // boolean values, as this is more human-readable than "1" and "0".
201        self.to_string()
202    }
203}