hecate/codegen/input_schema/
quantity.rs

1#![allow(unused)]
2use super::RawRepr;
3use super::unit::{FormatUnitError, format_unit};
4use crate::StdError;
5use const_format::{concatcp, formatcp};
6use derive_more::{Deref, DerefMut};
7use dyn_clone::DynClone;
8use itertools::Itertools;
9use lazy_static::lazy_static;
10use regex::Regex;
11use schemars::{JsonSchema, Schema, json_schema};
12use serde::de::Visitor;
13use serde::{Deserialize, Serialize, de::Error};
14use serde_yaml::Value;
15use std::borrow::Cow;
16use std::collections::HashMap;
17use std::fmt::{Debug, Display};
18use std::ops::Deref;
19use std::str::FromStr;
20use std::sync::LazyLock;
21use thiserror::Error;
22use ucfirst::ucfirst;
23
24#[derive(Debug, PartialEq, PartialOrd, Clone, Deref, DerefMut)]
25pub struct WithRawRepr<L> {
26    raw: String,
27    #[deref_mut]
28    #[deref]
29    parsed: L,
30}
31
32pub trait SchemaId {
33    fn schema_id() -> Cow<'static, str>;
34}
35
36impl<T: JsonSchema> JsonSchema for WithRawRepr<T> {
37    fn schema_name() -> Cow<'static, str> {
38        T::schema_name()
39    }
40
41    fn schema_id() -> Cow<'static, str> {
42        T::schema_id()
43    }
44
45    fn json_schema(r#gen: &mut schemars::SchemaGenerator) -> Schema {
46        T::json_schema(r#gen)
47    }
48}
49
50impl<T> Display for WithRawRepr<T> {
51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        write!(f, "{}", self.raw)
53    }
54}
55
56impl<L> RawRepr for WithRawRepr<L> {
57    fn raw(&self) -> &str {
58        &self.raw
59    }
60}
61
62impl<T: FromStr> FromStr for WithRawRepr<T> {
63    type Err = <T as FromStr>::Err;
64
65    fn from_str(s: &str) -> Result<Self, Self::Err> {
66        let parsed: T = s.parse()?;
67        Ok(WithRawRepr {
68            raw: s.trim().to_string(),
69            parsed,
70        })
71    }
72}
73
74impl<T> Serialize for WithRawRepr<T> {
75    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
76    where
77        S: serde::Serializer,
78    {
79        // Only serialize the `raw` field as "value"
80        serializer.serialize_str(&self.raw)
81    }
82}
83
84struct WithRawReprVisitor<T> {
85    _marker: std::marker::PhantomData<T>,
86}
87
88impl<'de, T: FromStr> Visitor<'de> for WithRawReprVisitor<T>
89where
90    <T as FromStr>::Err: std::fmt::Display,
91{
92    type Value = WithRawRepr<T>;
93
94    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
95        formatter.write_str("a properly formatted quantity with 'value [unit]'")
96    }
97
98    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
99    where
100        E: Error,
101    {
102        Ok(v.parse::<WithRawRepr<T>>().map_err(|e| E::custom(e))?)
103    }
104}
105
106impl<'de, T> Deserialize<'de> for WithRawRepr<T>
107where
108    T: FromStr,
109    <T as FromStr>::Err: std::fmt::Display,
110{
111    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
112    where
113        D: serde::Deserializer<'de>,
114    {
115        deserializer.deserialize_str(WithRawReprVisitor {
116            _marker: std::marker::PhantomData,
117        })
118    }
119}
120
121// #[typetag::serde(tag = "type")]
122// pub trait DynQuantity: DynClone + Debug {
123//     fn si_value(&self) -> f64;
124// }
125// dyn_clone::clone_trait_object!(DynQuantity);
126
127// pub fn parse_quantity(
128//     raw: &str,
129//     r#type: &str,
130// ) -> Result<WithRawRepr<Box<dyn QuantityTrait>>, ParseQuantityError> {
131//     match r#type {
132//         "speed" => raw.parse()
133//     }
134// }
135//
136// impl JsonSchema for dyn QuantityTrait {
137//     fn schema_name() -> Cow<'static, str> {
138//         "Quantity".into()
139//     }
140//
141//     fn json_schema(generator: &mut schemars::SchemaGenerator) -> Schema {
142//         json_schema!({
143//             "title": "Quantity",
144//             "description": "A physical quantity with a unit. If no unit is specified, the default unit is used.",
145//             "oneOf": [
146//                 {
147//                     "type": "string",
148//                     "pattern": QUANTITY_PATTERN
149//                 },
150//                 {
151//                     "type": "number"
152//                 }
153//             ]
154//         })
155//     }
156// }
157
158pub trait QuantityTrait: Clone + Debug + FromStr {
159    // fn name(&self) -> Cow<'static, str> {
160    // }
161    // fn default_unit(&self) -> Cow<'static, str>;
162    fn description() -> Cow<'static, str> {
163        format!("A {}.", Self::name().to_lowercase()).into()
164    }
165
166    fn name() -> Cow<'static, str> {
167        Self::type_id().split('_').map(ucfirst).join(" ").into()
168    }
169
170    fn type_id() -> Cow<'static, str>;
171
172    fn si_value(&self) -> f64;
173}
174
175const PARTIAL_QUANTITY_PATTERN: &str =
176    r"\s*([+-]?[\d_ ]*\.?[\d_ ]+?(?:e(?:\+|-)?[.\d]+)?)[ \t]*([^\d\s.](?:.*?[^.])?)?\s*";
177
178pub const NO_REF_QUANTITY_PATTERN: &str = formatcp!("^{PARTIAL_QUANTITY_PATTERN}$");
179
180const PARTIAL_REFERENCE_PATTERN: &str = concatcp!(r"\s*(reference|ref)?", PARTIAL_QUANTITY_PATTERN);
181pub const QUANTITY_PATTERN: &str = formatcp!("^{PARTIAL_REFERENCE_PATTERN}$");
182
183pub const RANGE_PATTERN: &str =
184    formatcp!(r"^{PARTIAL_QUANTITY_PATTERN}\s*..\s*{PARTIAL_QUANTITY_PATTERN}$");
185
186lazy_static! {
187    pub static ref QUANTITY_RE: Regex = Regex::new(QUANTITY_PATTERN).unwrap();
188}
189
190pub fn get_unit(quantity: &str) -> Option<&str> {
191    Some(QUANTITY_RE.captures(quantity)?.get(3)?.as_str())
192}
193
194#[derive(Debug, Error)]
195pub enum ParseQuantityError {
196    #[error("invalid quantity format : '{0}', should be 'value [unit]'")]
197    InvalidFormat(String),
198    #[error("this quantity can't be a reference, please remove the 'ref' or 'reference' keyword")]
199    NoReference,
200    #[error("invalid unit format: {0}")]
201    InvalidUnitFormat(#[from] FormatUnitError),
202    #[error("quantity not recognized: '{0}'")]
203    Unrecognized(String),
204}
205
206#[derive(Debug, DerefMut, Deref, Clone, PartialEq, PartialOrd)]
207pub struct Quantity<T: QuantityTrait>(T);
208
209pub trait QuantitySchema {
210    fn type_id() -> Cow<'static, str>;
211
212    fn description() -> Cow<'static, str> {
213        format!("A {}.", Self::type_id()).into()
214    }
215}
216
217impl<T: QuantityTrait> JsonSchema for Quantity<T> {
218    fn schema_name() -> Cow<'static, str> {
219        T::type_id()
220    }
221
222    fn json_schema(generator: &mut schemars::SchemaGenerator) -> Schema {
223        json_schema!({
224            "title": ucfirst(&T::name()),
225            "description": T::description(),
226            "oneOf": [
227                {
228                    "type": "string",
229                    "pattern": QUANTITY_PATTERN
230                },
231                {
232                    "type": "number"
233                }
234            ]
235        })
236    }
237}
238
239impl<T: QuantityTrait + PartialEq> PartialEq<T> for Quantity<T> {
240    fn eq(&self, other: &T) -> bool {
241        self.0 == *other
242    }
243}
244
245static UNITS: LazyLock<HashMap<Cow<'static, str>, Cow<'static, str>>> =
246    LazyLock::new(|| [("length".into(), "m".into())].into());
247
248impl<T: QuantityTrait> FromStr for Quantity<T>
249where
250    <T as FromStr>::Err: std::fmt::Display,
251{
252    type Err = ParseQuantityError;
253    fn from_str(raw: &str) -> Result<Self, Self::Err> {
254        if let Some(captures) = QUANTITY_RE.captures(raw) {
255            if captures.get(1).is_some() {
256                return Err(ParseQuantityError::NoReference);
257            }
258            let mut unit: String = UNITS
259                .get(&T::type_id())
260                .cloned()
261                .unwrap_or_else(|| "".into())
262                .to_string();
263            if let Some(u) = captures.get(3) {
264                unit = format_unit(u.as_str())?;
265            }
266
267            let value = &captures[2];
268            let mut pretty_value = String::with_capacity(value.len());
269            let mut prepped_value = String::with_capacity(value.len());
270
271            for c in value.chars() {
272                match c {
273                    ' ' => pretty_value.push(' '),
274                    '_' => pretty_value.push(' '),
275                    _ => {
276                        pretty_value.push(c);
277                        prepped_value.push(c);
278                    }
279                }
280            }
281
282            let prepped_raw = format!("{} {}", prepped_value, &unit);
283
284            Ok(Quantity(prepped_raw.parse().map_err(
285                |e: <T as FromStr>::Err| ParseQuantityError::Unrecognized(e.to_string()),
286            )?))
287            //     raw: format!(
288            //         "{}{}{}",
289            //         pretty_value,
290            //         if unit.len() > 0 { " " } else { "" },
291            //         &unit
292            //     ),
293            // })
294        } else {
295            Err(ParseQuantityError::InvalidFormat(raw.to_string()))
296        }
297    }
298}
299
300// impl<T: QuantityTrait> QuantityTrait for Quantity<T> {
301//     const DEFAULT_UNIT: &str = T::DEFAULT_UNIT;
302//
303//     const NAME: &str = T::DEFAULT_UNIT;
304//
305//     fn si_value(&self) -> f64 {
306//         self.0.si_value()
307//     }
308// }
309
310// impl<T: QuantityTrait> JsonSchema for Quantity<T> {
311//     fn schema_name() -> Cow<'static, str> {
312//         T::schema_name()
313//     }
314//
315//     fn json_schema(generator: &mut schemars::SchemaGenerator) -> Schema {
316//         T::json_schema(generator)
317//     }
318// }
319
320// impl<T> WithRawRepr<T>
321// where
322//     T: FromStr<Err = UomParseError> + QuantityTrait,
323// {
324//     // Constructor to create a new ParsedValue
325//     pub fn new(raw: &str) -> Result<Self, ParseQuantityError> {
326//         if let Some(captures) = QUANTITY_RE.captures(raw) {
327//             if captures.get(1).is_some() {
328//                 return Err(ParseQuantityError::NoReference);
329//             }
330//             let mut unit: String = T::DEFAULT_UNIT.to_string();
331//             if let Some(u) = captures.get(3) {
332//                 unit = format_unit(u.as_str())?;
333//             }
334//
335//             let value = &captures[2];
336//             let mut pretty_value = String::with_capacity(value.len());
337//             let mut prepped_value = String::with_capacity(value.len());
338//
339//             for c in value.chars() {
340//                 match c {
341//                     ' ' => pretty_value.push(' '),
342//                     '_' => pretty_value.push(' '),
343//                     _ => {
344//                         pretty_value.push(c);
345//                         prepped_value.push(c);
346//                     }
347//                 }
348//             }
349//
350//             let prepped_raw = format!("{} {}", prepped_value, &unit);
351//
352//             Ok(WithRawRepr {
353//                 parsed: prepped_raw.parse()?,
354//                 raw: format!(
355//                     "{}{}{}",
356//                     pretty_value,
357//                     if unit.len() > 0 { " " } else { "" },
358//                     &unit
359//                 ),
360//             })
361//         } else {
362//             Err(ParseQuantityError::InvalidFormat(raw.to_string()))
363//         }
364//     }
365//
366//     /// Getter for the parsed quantity
367//     pub fn parsed(&self) -> &T {
368//         &self.parsed
369//     }
370//
371//     /// Raw representation of the quantity as written by the user
372//     pub fn raw(&self) -> &str {
373//         &self.raw
374//     }
375// }
376
377use uom::si::f64 as si;
378
379pub trait DefaultUnit {
380    const DEFAULT_UNIT: &str;
381}
382
383pub trait QuantityId {
384    const QUANTITY_ID: &str;
385}
386
387/// Ratio (unit less value resulting from calculating the ratio of two quantities)
388pub type Ratio = WithRawRepr<si::Ratio>;
389
390impl DefaultUnit for si::Ratio {
391    const DEFAULT_UNIT: &str = "";
392}
393
394/// Area (default: km²)
395pub type Area = WithRawRepr<Quantity<si::Area>>;
396
397impl QuantitySchema for si::Area {
398    fn type_id() -> Cow<'static, str> {
399        "area".into()
400    }
401}
402
403impl QuantityTrait for si::Area {
404    fn type_id() -> Cow<'static, str> {
405        "area".into()
406    }
407    fn si_value(&self) -> f64 {
408        self.get::<uom::si::area::square_meter>()
409    }
410}
411
412/// # CustomQuantity
413/// This allows defining custom quantities based on
414/// the seven base quantities of the international system.
415#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
416pub struct CustomQuantity {
417    #[serde(default)]
418    pub length: isize,
419    #[serde(default)]
420    pub time: isize,
421    #[serde(default)]
422    pub mass: isize,
423    #[serde(default)]
424    pub current: isize,
425    #[serde(default)]
426    pub temperature: isize,
427    #[serde(default)]
428    pub amount: isize,
429    #[serde(default)]
430    pub luminous_intensity: isize,
431    #[serde(default)]
432    pub value: f64,
433}
434
435/// # Quantity
436#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
437#[serde(tag = "type", content = "value")]
438#[serde(rename_all = "snake_case")]
439pub enum QuantityEnum {
440    Speed(Speed),
441    Length(Length),
442    Area(Area),
443    Volume(Volume),
444    Mass(Mass),
445    Temperature(Temperature),
446    DiffusionCoefficient(DiffusionCoefficient),
447    Custom(CustomQuantity),
448}
449
450impl QuantityEnum {
451    pub fn si_value(&self) -> f64 {
452        match self {
453            QuantityEnum::Speed(q) => q.value,
454            QuantityEnum::Length(q) => q.value,
455            QuantityEnum::DiffusionCoefficient(q) => q.value,
456            QuantityEnum::Area(q) => q.value,
457            QuantityEnum::Mass(q) => q.value,
458            QuantityEnum::Volume(q) => q.value,
459            QuantityEnum::Temperature(q) => q.value,
460            QuantityEnum::Custom(q) => q.value,
461        }
462    }
463}
464//
465// impl Deref for QuantityEnum {
466//     type Target = dyn QuantityTrait;
467//
468//     fn deref(&self) -> &Self::Target {
469//         todo!()
470//     }
471//
472// }
473
474// impl QuantityEnum {
475//     pub fn si_value(&self) -> f64 {
476//         match self {
477//             QuantityEnum::Speed(v) => v.si_value(),
478//         }
479//     }
480// }
481
482pub type DiffusionCoefficient = WithRawRepr<Quantity<si::DiffusionCoefficient>>;
483// impl DynQuantity for si::DiffusionCoefficient {
484//     fn si_value(&self) -> f64 {
485//         self.get::<uom::si::diffusion_coefficient::square_meter_per_second>()
486//     }
487// }
488impl QuantityTrait for si::DiffusionCoefficient {
489    fn type_id() -> Cow<'static, str> {
490        "diffusion_coefficient".into()
491    }
492    fn si_value(&self) -> f64 {
493        self.get::<uom::si::diffusion_coefficient::square_meter_per_second>()
494    }
495}
496
497// /// Compressibility (default: Pa⁻¹)
498// pub type Compressibility = Quantity<si::Compressibility>;
499//
500// impl DefaultUnit for si::Compressibility {
501//     const DEFAULT_UNIT: &str = "Pa⁻¹";
502// }
503
504// /// HydraulicPermeability (default: darcy)
505// pub type HydraulicPermeability = Quantity<si::HydraulicPermeability>;
506//
507// impl DefaultUnit for si::HydraulicPermeability {
508//     const DEFAULT_UNIT: &str = "mD";
509// }
510
511/// Length (default: kilometers, since distances in geoscience are often measured in km)
512pub type Length = WithRawRepr<Quantity<si::Length>>;
513
514// impl DefaultUnit for si::Length {
515//     const DEFAULT_UNIT: &str = "km";
516// }
517impl QuantityTrait for si::Length {
518    fn type_id() -> Cow<'static, str> {
519        "length".into()
520    }
521    fn si_value(&self) -> f64 {
522        self.get::<uom::si::length::meter>()
523    }
524}
525
526impl QuantitySchema for si::Length {
527    fn type_id() -> Cow<'static, str> {
528        "length".into()
529    }
530}
531
532impl Quantity<si::Length> {
533    pub fn meters(&self) -> f64 {
534        self.get::<uom::si::length::meter>()
535    }
536}
537
538pub type Speed = WithRawRepr<Quantity<si::Velocity>>;
539
540// impl DefaultUnit for si::Velocity {
541//     const DEFAULT_UNIT: &str = "m/s";
542// }
543// impl<T: QuantityTrait> SchemaId for T {
544//     fn schema_id() -> Cow<'static, str> {
545//         T::NAME.into()
546//     }
547// }
548
549impl<T: SchemaId> SchemaId for WithRawRepr<T> {
550    fn schema_id() -> Cow<'static, str> {
551        T::schema_id()
552    }
553}
554
555impl QuantityTrait for si::Velocity {
556    fn si_value(&self) -> f64 {
557        self.get::<uom::si::velocity::meter_per_second>()
558    }
559
560    fn type_id() -> Cow<'static, str> {
561        "speed".into()
562    }
563}
564
565impl QuantitySchema for si::Velocity {
566    fn type_id() -> Cow<'static, str> {
567        "speed".into()
568    }
569}
570
571/// Mass (default: grams, since small mass quantities in geoscience, especially in analysis, use grams)
572pub type Mass = WithRawRepr<Quantity<si::Mass>>;
573
574impl QuantityTrait for si::Mass {
575    fn type_id() -> Cow<'static, str> {
576        "mass".into()
577    }
578    fn si_value(&self) -> f64 {
579        self.get::<uom::si::mass::gram>()
580    }
581}
582
583impl DefaultUnit for si::Mass {
584    const DEFAULT_UNIT: &str = "g";
585}
586
587/// Time (default: seconds)
588pub type Time = WithRawRepr<Quantity<si::Time>>;
589impl Quantity<si::Time> {
590    pub fn seconds(&self) -> f64 {
591        self.si_value()
592    }
593}
594impl QuantitySchema for si::Time {
595    fn type_id() -> Cow<'static, str> {
596        "time".into()
597    }
598}
599impl QuantityTrait for si::Time {
600    fn type_id() -> Cow<'static, str> {
601        "time".into()
602    }
603    fn si_value(&self) -> f64 {
604        self.get::<uom::si::time::second>()
605    }
606}
607
608/// Temperature (default: Celsius, as temperature is often measured in Celsius in geoscience contexts)
609pub type Temperature = WithRawRepr<Quantity<si::ThermodynamicTemperature>>;
610
611impl DefaultUnit for si::ThermodynamicTemperature {
612    const DEFAULT_UNIT: &'static str = "°C";
613}
614
615impl QuantityTrait for si::ThermodynamicTemperature {
616    fn type_id() -> Cow<'static, str> {
617        "temperature".into()
618    }
619    fn si_value(&self) -> f64 {
620        self.get::<uom::si::thermodynamic_temperature::kelvin>()
621    }
622}
623
624/// Pressure (default: pascal, as pressure is often measured in pascal in scientific contexts)
625pub type Pressure = WithRawRepr<Quantity<si::Pressure>>;
626
627impl DefaultUnit for si::Pressure {
628    const DEFAULT_UNIT: &'static str = "Pa";
629}
630
631impl QuantityTrait for si::Pressure {
632    fn type_id() -> Cow<'static, str> {
633        "pressure".into()
634    }
635    fn si_value(&self) -> f64 {
636        self.get::<uom::si::pressure::pascal>()
637    }
638}
639
640/// Volume (default: cubic meters, which is the SI unit for volume)
641pub type Volume = WithRawRepr<Quantity<si::Volume>>;
642
643impl DefaultUnit for si::Volume {
644    const DEFAULT_UNIT: &'static str = "m³";
645}
646
647impl QuantityTrait for si::Volume {
648    fn type_id() -> Cow<'static, str> {
649        "volume".into()
650    }
651    fn si_value(&self) -> f64 {
652        self.get::<uom::si::volume::cubic_meter>()
653    }
654}
655
656/// Molar Mass (default: grams per mole, as it's commonly used in geoscience)
657pub type MolarMass = WithRawRepr<Quantity<si::MolarMass>>;
658
659impl DefaultUnit for si::MolarMass {
660    const DEFAULT_UNIT: &'static str = "g/mol";
661}
662
663impl QuantityTrait for si::MolarMass {
664    fn type_id() -> Cow<'static, str> {
665        "molar_mass".into()
666    }
667    fn si_value(&self) -> f64 {
668        self.get::<uom::si::molar_mass::gram_per_mole>()
669    }
670}
671
672#[cfg(test)]
673mod tests {
674    use super::*;
675    use std::str::FromStr;
676
677    use crate::codegen::input_schema::quantity::Time;
678
679    use super::{DefaultUnit, Pressure, QUANTITY_RE, WithRawRepr};
680    use std::fmt::Debug;
681    use uom::si::{f64::Length, length::*};
682
683    fn make_parsed<L>(raw: &str, parsed: L) -> WithRawRepr<L>
684    where
685        L: FromStr,
686    {
687        WithRawRepr {
688            raw: raw.to_string(),
689            parsed,
690        }
691    }
692
693    #[test]
694    fn parse_length_with_valid_input() {
695        fn make_length(raw: &str) -> super::Length {
696            raw.parse().unwrap()
697        }
698        assert_eq!(
699            make_length("10 m"),
700            make_parsed("10 m", Quantity(Length::new::<meter>(10.)))
701        );
702        // assert_eq!(
703        //     make_length("10m"),
704        //     make_parsed("10 m", Quantity(Length::new::<meter>(10.)))
705        // );
706        // assert_eq!(
707        //     make_length("10     m"),
708        //     make_parsed("10 m", Quantity(Length::new::<meter>(10.)))
709        // );
710        // assert_eq!(
711        //     make_length("10"),
712        //     make_parsed("10 km", Quantity(Length::new::<kilometer>(10.)))
713        // );
714        assert_eq!(
715            make_length("100 000 m"),
716            make_parsed("100 000 m", Quantity(Length::new::<kilometer>(100.)))
717        );
718        assert_eq!(
719            make_length("1 meter"),
720            make_parsed("1 meter", Quantity(Length::new::<meter>(1.)))
721        );
722        assert_eq!(
723            make_length("2 meters"),
724            make_parsed("2 meters", Quantity(Length::new::<meter>(2.)))
725        );
726        assert_eq!(
727            make_length("-1"),
728            make_parsed("-1", Quantity(Length::new::<meter>(-1.)))
729        );
730        // assert_eq!(
731        //     super::Compressibility::new("1e-09 Pa-1").expect("Valid quantity should be parsed."),
732        //     Quantity {
733        //         parsed: uom::si::f64::Compressibility::new::<uom::si::compressibility::pascal>(
734        //             1e-09
735        //         ),
736        //         raw: "1e-09 Pa⁻¹".to_string()
737        //     }
738        // );
739    }
740    #[test]
741    fn parse_length_with_invalid_input() {
742        fn attempt_length_parse(raw: &str) {
743            let result = raw.parse::<super::Length>();
744            assert!(result.is_err(), "Expected error for input '{}'", raw);
745        }
746
747        attempt_length_parse("ten m"); // Invalid number format
748        attempt_length_parse("10 xyz"); // Unrecognized unit
749        attempt_length_parse(""); // Empty string
750        //attempt_length_parse("10 10 m"); // Invalid format
751        attempt_length_parse("reference m"); // Missing number
752    }
753
754    #[test]
755    fn parse_reference_should_err() {
756        let raw = "reference 5 000 000";
757        assert!(raw.parse::<Pressure>().is_err());
758    }
759
760    #[test]
761    fn parse_real_number() {
762        let raw = "0.1 s";
763        let time: Time = raw.parse().unwrap();
764        assert_eq!(time.parsed.get::<uom::si::time::second>(), 0.1)
765    }
766
767    #[test]
768    fn test_re() {
769        assert!(QUANTITY_RE.captures("0.1 s").is_some())
770    }
771}