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}