Skip to main content

mzdata_param/
param.rs

1use std::borrow::Cow;
2use std::fmt::Display;
3use std::hash::Hash;
4use std::str::{self, FromStr};
5
6
7use crate::{
8    AccessionIntCode, CURIE, ControlledVocabulary, ParamCow, ParamLike, ParamValue, ParamValueParseError, Unit, Value, ValueRef
9};
10
11
12/// A controlled vocabulary or user parameter
13#[derive(Debug, Clone, Default, PartialEq, Eq)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15pub struct Param {
16    /// The human-readable name of the parameter.
17    pub name: String,
18    /// A type generic value associated with the parameter
19    pub value: Value,
20    /// The numeric component of the accession code in the identifier for the parameter's
21    /// definition in [`Self::controlled_vocabulary`], if it is defined by one.
22    pub accession: Option<AccessionIntCode>,
23    /// The controlled vocabulary this parameter is defined in, if any.
24    pub controlled_vocabulary: Option<ControlledVocabulary>,
25    /// The unit the parameter's value is expressed in, if any.
26    pub unit: Unit,
27}
28
29impl AsRef<Value> for Param {
30    fn as_ref(&self) -> &Value {
31        &self.value
32    }
33}
34
35impl ParamValue for Param {
36    fn is_empty(&self) -> bool {
37        <Value as ParamValue>::is_empty(&self.value)
38    }
39
40    fn is_i64(&self) -> bool {
41        <Value as ParamValue>::is_i64(&self.value)
42    }
43
44    fn is_f64(&self) -> bool {
45        <Value as ParamValue>::is_f64(&self.value)
46    }
47
48    fn is_buffer(&self) -> bool {
49        <Value as ParamValue>::is_buffer(&self.value)
50    }
51
52    fn is_str(&self) -> bool {
53        <Value as ParamValue>::is_str(&self.value)
54    }
55
56    fn to_f64(&self) -> Result<f64, ParamValueParseError> {
57        <Value as ParamValue>::to_f64(&self.value)
58    }
59
60    fn to_i64(&self) -> Result<i64, ParamValueParseError> {
61        <Value as ParamValue>::to_i64(&self.value)
62    }
63
64    fn to_str(&self) -> Cow<'_, str> {
65        <Value as ParamValue>::to_str(&self.value)
66    }
67
68    fn to_buffer(&self) -> Result<Cow<'_, [u8]>, ParamValueParseError> {
69        <Value as ParamValue>::to_buffer(&self.value)
70    }
71
72    fn parse<T: FromStr>(&self) -> Result<T, T::Err> {
73        <Value as ParamValue>::parse(&self.value)
74    }
75
76    fn as_bytes(&self) -> Cow<'_, [u8]> {
77        <Value as ParamValue>::as_bytes(&self.value)
78    }
79
80    fn as_ref(&self) -> ValueRef<'_> {
81        <Value as ParamValue>::as_ref(&self.value)
82    }
83
84    fn data_len(&self) -> usize {
85        <Value as ParamValue>::data_len(&self.value)
86    }
87
88    fn is_boolean(&self) -> bool {
89        <Value as ParamValue>::is_boolean(&self.value)
90    }
91
92    fn to_bool(&self) -> Result<bool, ParamValueParseError> {
93        <Value as ParamValue>::to_bool(&self.value)
94    }
95
96    fn is_list(&self) -> bool {
97        <Value as ParamValue>::is_list(&self.value)
98    }
99
100    fn as_slice(&self) -> Cow<'_, [Value]> {
101        <Value as ParamValue>::as_slice(&self.value)
102    }
103}
104
105impl Display for Param {
106    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107        let mut body = if self.is_controlled() {
108            format!(
109                "{}:{}|{}={}",
110                String::from_utf8_lossy(self.controlled_vocabulary.unwrap().as_bytes()),
111                self.accession.unwrap(),
112                self.name,
113                self.value
114            )
115        } else {
116            format!("{}={}", self.name, self.value)
117        };
118        if self.unit != Unit::Unknown {
119            body.extend(format!(" {}", self.unit).chars());
120        };
121        f.write_str(body.as_str())
122    }
123}
124
125/// Incrementally build up a [`Param`]
126#[derive(Default, Debug, Clone)]
127pub struct ParamBuilder {
128    name: String,
129    value: Value,
130    accession: Option<AccessionIntCode>,
131    controlled_vocabulary: Option<ControlledVocabulary>,
132    unit: Unit,
133}
134
135impl ParamBuilder {
136    /// Set the parameter's name.
137    pub fn name<S: ToString>(mut self, name: S) -> Self {
138        self.name = name.to_string();
139        self
140    }
141
142    /// Set the parameter's value.
143    pub fn value<V: Into<Value>>(mut self, value: V) -> Self {
144        self.value = value.into();
145        self
146    }
147
148    /// Set the parameter's controlled vocabulary namespace.
149    ///
150    /// # See also
151    /// - [`ParamBuilder::curie`], to set the namespace and accession together
152    pub fn controlled_vocabulary(mut self, cv: ControlledVocabulary) -> Self {
153        self.controlled_vocabulary = Some(cv);
154        self
155    }
156
157    /// Set the parameter's accession code.
158    ///
159    /// # See also
160    /// - [`ParamBuilder::curie`], to set the namespace and accession together
161    pub fn accession(mut self, accession: AccessionIntCode) -> Self {
162        self.accession = Some(accession);
163        self
164    }
165
166    /// A convenience method that configures the controlled vocabulary and accession number
167    /// from a [`CURIE`]
168    pub fn curie(mut self, curie: CURIE) -> Self {
169        self.controlled_vocabulary = Some(curie.controlled_vocabulary);
170        self.accession = Some(curie.accession);
171        self
172    }
173
174    /// Set the parameter's unit of measure.
175    pub fn unit(mut self, unit: Unit) -> Self {
176        self.unit = unit;
177        self
178    }
179
180    /// Consume the builder to produce a [`Param`]
181    pub fn build(self) -> Param {
182        let mut this = Param::new();
183        this.name = self.name;
184        this.value = self.value;
185        this.controlled_vocabulary = self.controlled_vocabulary;
186        this.accession = self.accession;
187        this.unit = self.unit;
188        this
189    }
190}
191
192impl Param {
193    /// Create a new, empty [`Param`].
194    ///
195    /// # See also
196    /// - [`Param::new_key_value`]
197    /// - [`Param::builder`]
198    /// - [`ControlledVocabulary::param`]
199    /// - [`ControlledVocabulary::param_val`]
200    pub fn new() -> Param {
201        Param {
202            ..Default::default()
203        }
204    }
205
206    /// Create a new [`ParamBuilder`] to make creating a new [`Param`] more convenient than
207    /// setting fields on a bare [`Param`] directly.
208    pub fn builder() -> ParamBuilder {
209        ParamBuilder::default()
210    }
211
212    /// A construction method for [`Param`] that sets [`Param::name`] and [`Param::value`]
213    /// but leaves all other attributes as default.
214    pub fn new_key_value<K: Into<String>, V: Into<Value>>(name: K, value: V) -> Param {
215        let mut inst = Self::new();
216        inst.name = name.into();
217        inst.value = value.into();
218        inst
219    }
220
221    /// Attempt to parse the value of this parameter into `T`.
222    ///
223    /// See [`Value::parse`]
224    pub fn parse<T: str::FromStr>(&self) -> Result<T, T::Err> {
225        self.value.parse::<T>()
226    }
227
228    /// Check if this parameter is defined in a controlled vocabulary
229    pub const fn is_controlled(&self) -> bool {
230        self.accession.is_some()
231    }
232
233    /// Create a [`CURIE`] from [`Param::controlled_vocabulary`] and [`Param::accession`]
234    pub const fn curie(&self) -> Option<CURIE> {
235        match (self.controlled_vocabulary, self.accession) {
236            (Some(cv), Some(acc)) => Some(CURIE::new(cv, acc)),
237            _ => None,
238        }
239    }
240
241    /// Format the [`Param::curie`] as a string, if it exists
242    pub fn curie_str(&self) -> Option<String> {
243        self.curie().map(|c| c.to_string())
244    }
245
246    /// Update [`Param::unit`] inferred from `accession`, failing that, `name`
247    ///
248    /// # See also
249    /// - [`Param::with_unit_t`], to infer the unit from a resolved [`Unit`]
250    pub fn with_unit<S: AsRef<str>, A: AsRef<str>>(mut self, accession: S, name: A) -> Param {
251        self.unit = Unit::from_accession(accession.as_ref());
252        if matches!(self.unit, Unit::Unknown) {
253            self.unit = Unit::from_name(name.as_ref());
254        }
255        self
256    }
257
258    /// Set [`Param::unit`] directly from `unit`.
259    ///
260    /// # See also
261    /// - [`Param::with_unit`], to infer the unit from an accession or name instead
262    pub fn with_unit_t(mut self, unit: &Unit) -> Param {
263        self.unit = *unit;
264        self
265    }
266}
267
268impl ParamLike for Param {
269    fn name(&self) -> &str {
270        &self.name
271    }
272
273    fn value(&self) -> ValueRef<'_> {
274        self.value.as_ref()
275    }
276
277    fn accession(&self) -> Option<AccessionIntCode> {
278        self.accession
279    }
280
281    fn controlled_vocabulary(&self) -> Option<ControlledVocabulary> {
282        self.controlled_vocabulary
283    }
284
285    fn unit(&self) -> Unit {
286        self.unit
287    }
288}
289
290impl PartialEq<CURIE> for Param {
291    fn eq(&self, other: &CURIE) -> bool {
292        other.eq(self)
293    }
294}
295
296impl<'a> PartialEq<ParamCow<'a>> for Param {
297    fn eq(&self, other: &ParamCow<'a>) -> bool {
298        self.controlled_vocabulary == other.controlled_vocabulary
299            && self.accession == other.accession
300            && self.name == other.name
301            && self.value == other.value
302            && self.unit == other.unit
303    }
304}
305
306impl PartialEq<Param> for ParamCow<'_> {
307    fn eq(&self, other: &Param) -> bool {
308        self.controlled_vocabulary == other.controlled_vocabulary
309            && self.accession == other.accession
310            && self.name == other.name
311            && self.value == other.value
312            && self.unit == other.unit
313    }
314}
315
316impl Hash for Param {
317    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
318        self.name.hash(state);
319        self.value.hash(state);
320        self.accession.hash(state);
321        self.controlled_vocabulary.hash(state);
322        self.unit.hash(state);
323    }
324}