Skip to main content

cu29_runtime/config/
value.rs

1//! Scalar and component configuration values.
2
3use super::BTreeMap;
4#[cfg(not(feature = "std"))]
5use super::imp::*;
6#[cfg(not(feature = "std"))]
7use alloc::boxed::Box;
8#[cfg(not(feature = "std"))]
9use alloc::vec;
10use core::any::type_name;
11use core::fmt;
12use core::fmt::Display;
13use cu29_traits::CuError;
14use cu29_value::Value as CuValue;
15use hashbrown::HashMap;
16use ron::Number;
17use ron::value::Value as RonValue;
18use serde::de::DeserializeOwned;
19use serde::{Deserialize, Deserializer, Serialize};
20
21/// This is the configuration of a component (like a task config or a monitoring config):w
22/// It is a map of key-value pairs.
23/// It is given to the new method of the task implementation.
24#[derive(Serialize, Deserialize, Debug, Clone, Default)]
25pub struct ComponentConfig(
26    #[serde(serialize_with = "super::serialize_ordered_map")] pub HashMap<String, Value>,
27);
28
29/// Mapping between resource binding names and bundle-scoped resource ids.
30#[allow(dead_code)]
31impl Display for ComponentConfig {
32    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33        let mut first = true;
34        let ComponentConfig(config) = self;
35        write!(f, "{{")?;
36        for (key, value) in config.iter() {
37            if !first {
38                write!(f, ", ")?;
39            }
40            write!(f, "{key}: {value}")?;
41            first = false;
42        }
43        write!(f, "}}")
44    }
45}
46
47// forward map interface
48impl ComponentConfig {
49    #[allow(dead_code)]
50    pub fn new() -> Self {
51        ComponentConfig(HashMap::new())
52    }
53
54    #[allow(dead_code)]
55    pub fn get<T>(&self, key: &str) -> Result<Option<T>, ConfigError>
56    where
57        T: for<'a> TryFrom<&'a Value, Error = ConfigError>,
58    {
59        let ComponentConfig(config) = self;
60        match config.get(key) {
61            Some(value) => T::try_from(value).map(Some),
62            None => Ok(None),
63        }
64    }
65
66    #[allow(dead_code)]
67    /// Retrieve a structured config value by deserializing it with cu29-value.
68    ///
69    /// Example RON:
70    /// `{ "calibration": { "matrix": [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]], "enabled": true } }`
71    ///
72    /// ```rust,ignore
73    /// #[derive(serde::Deserialize)]
74    /// struct CalibrationCfg {
75    ///     matrix: [[f32; 3]; 3],
76    ///     enabled: bool,
77    /// }
78    /// let cfg: CalibrationCfg = config.get_value("calibration")?.unwrap();
79    /// ```
80    pub fn get_value<T>(&self, key: &str) -> Result<Option<T>, ConfigError>
81    where
82        T: DeserializeOwned,
83    {
84        let ComponentConfig(config) = self;
85        let Some(value) = config.get(key) else {
86            return Ok(None);
87        };
88        let cu_value = ron_value_to_cu_value(&value.0).map_err(|err| err.with_key(key))?;
89        cu_value
90            .deserialize_into::<T>()
91            .map(Some)
92            .map_err(|err| ConfigError {
93                message: format!(
94                    "Config key '{key}' failed to deserialize as {}: {err}",
95                    type_name::<T>()
96                ),
97            })
98    }
99
100    #[allow(dead_code)]
101    pub fn deserialize_into<T>(&self) -> Result<T, ConfigError>
102    where
103        T: DeserializeOwned,
104    {
105        let mut map = BTreeMap::new();
106        for (key, value) in &self.0 {
107            let mapped_value = ron_value_to_cu_value(&value.0).map_err(|err| err.with_key(key))?;
108            map.insert(CuValue::String(key.clone()), mapped_value);
109        }
110
111        CuValue::Map(map)
112            .deserialize_into::<T>()
113            .map_err(|err| ConfigError {
114                message: format!(
115                    "Config failed to deserialize as {}: {err}",
116                    type_name::<T>()
117                ),
118            })
119    }
120
121    #[allow(dead_code)]
122    pub fn set<T: Into<Value>>(&mut self, key: &str, value: T) {
123        let ComponentConfig(config) = self;
124        config.insert(key.to_string(), value.into());
125    }
126
127    #[allow(dead_code)]
128    pub fn merge_from(&mut self, other: &ComponentConfig) {
129        let ComponentConfig(config) = self;
130        for (key, value) in &other.0 {
131            config.insert(key.clone(), value.clone());
132        }
133    }
134}
135
136fn ron_value_to_cu_value(value: &RonValue) -> Result<CuValue, ConfigError> {
137    match value {
138        RonValue::Bool(v) => Ok(CuValue::Bool(*v)),
139        RonValue::Char(v) => Ok(CuValue::Char(*v)),
140        RonValue::String(v) => Ok(CuValue::String(v.clone())),
141        RonValue::Bytes(v) => Ok(CuValue::Bytes(v.clone())),
142        RonValue::Unit => Ok(CuValue::Unit),
143        RonValue::Option(v) => {
144            let mapped = match v {
145                Some(inner) => Some(Box::new(ron_value_to_cu_value(inner)?)),
146                None => None,
147            };
148            Ok(CuValue::Option(mapped))
149        }
150        RonValue::Seq(seq) => {
151            let mut mapped = Vec::with_capacity(seq.len());
152            for item in seq {
153                mapped.push(ron_value_to_cu_value(item)?);
154            }
155            Ok(CuValue::Seq(mapped))
156        }
157        RonValue::Map(map) => {
158            let mut mapped = BTreeMap::new();
159            for (key, value) in map.iter() {
160                let mapped_key = ron_value_to_cu_value(key)?;
161                let mapped_value = ron_value_to_cu_value(value)?;
162                mapped.insert(mapped_key, mapped_value);
163            }
164            Ok(CuValue::Map(mapped))
165        }
166        RonValue::Number(num) => match num {
167            Number::I8(v) => Ok(CuValue::I8(*v)),
168            Number::I16(v) => Ok(CuValue::I16(*v)),
169            Number::I32(v) => Ok(CuValue::I32(*v)),
170            Number::I64(v) => Ok(CuValue::I64(*v)),
171            Number::U8(v) => Ok(CuValue::U8(*v)),
172            Number::U16(v) => Ok(CuValue::U16(*v)),
173            Number::U32(v) => Ok(CuValue::U32(*v)),
174            Number::U64(v) => Ok(CuValue::U64(*v)),
175            Number::F32(v) => Ok(CuValue::F32(v.0)),
176            Number::F64(v) => Ok(CuValue::F64(v.0)),
177            _ => Err(ConfigError {
178                message: "Unsupported RON number variant".to_string(),
179            }),
180        },
181    }
182}
183
184// The configuration Serialization format is as follows:
185// (
186//   tasks : [ (id: "toto", type: "zorglub::MyType", config: {...}),
187//             (id: "titi", type: "zorglub::MyType2", config: {...})]
188//   cnx : [ (src: "toto", dst: "titi", msg: "zorglub::MyMsgType"),...]
189// )
190
191/// Wrapper around the ron::Value to allow for custom serialization.
192#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
193pub struct Value(pub(super) RonValue);
194
195/// Scalar representation used by compile-time constants after RON parsing.
196#[doc(hidden)]
197#[derive(Debug, Clone, Copy, PartialEq)]
198pub enum ConstantNumber {
199    Signed(i64),
200    Unsigned(u64),
201    Float(f64),
202}
203
204impl ConstantNumber {
205    pub fn as_f64(self) -> f64 {
206        match self {
207            Self::Signed(value) => value as f64,
208            Self::Unsigned(value) => value as f64,
209            Self::Float(value) => value,
210        }
211    }
212}
213
214/// Rust scalar storage selected for a compile-time constant.
215#[doc(hidden)]
216#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, PartialEq, Eq)]
217#[serde(rename_all = "lowercase")]
218pub enum ConstantStorage {
219    I8,
220    I16,
221    I32,
222    I64,
223    Isize,
224    U8,
225    U16,
226    U32,
227    U64,
228    Usize,
229    #[default]
230    F32,
231    F64,
232}
233
234impl ConstantStorage {
235    pub const fn rust_type(self) -> &'static str {
236        match self {
237            Self::I8 => "i8",
238            Self::I16 => "i16",
239            Self::I32 => "i32",
240            Self::I64 => "i64",
241            Self::Isize => "isize",
242            Self::U8 => "u8",
243            Self::U16 => "u16",
244            Self::U32 => "u32",
245            Self::U64 => "u64",
246            Self::Usize => "usize",
247            Self::F32 => "f32",
248            Self::F64 => "f64",
249        }
250    }
251
252    pub const fn supports_quantity(self) -> bool {
253        matches!(self, Self::F32 | Self::F64)
254    }
255}
256
257/// One top-level `constants:` declaration.
258#[doc(hidden)]
259#[derive(Serialize, Deserialize, Debug, Clone)]
260pub struct ConstantConfig {
261    pub(super) id: String,
262    #[serde(default, deserialize_with = "deserialize_constant_module")]
263    module: Option<String>,
264    #[serde(default)]
265    pub(super) storage: Option<ConstantStorage>,
266    pub(super) quantity: Option<cu29_units::constant::Quantity>,
267    pub(super) unit: Option<cu29_units::constant::Unit>,
268    pub(super) value: Option<Value>,
269    #[serde(rename = "type")]
270    pub(super) rust_type: Option<String>,
271    pub(super) expression: Option<String>,
272}
273
274fn deserialize_constant_module<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
275where
276    D: Deserializer<'de>,
277{
278    Option::<String>::deserialize(deserializer).map(|module| {
279        module.map(|module| {
280            module
281                .chars()
282                .filter(|character| !character.is_whitespace())
283                .collect()
284        })
285    })
286}
287
288impl ConstantConfig {
289    pub const DEFAULT_MODULE: &'static str = "constants";
290
291    pub fn id(&self) -> &str {
292        &self.id
293    }
294
295    pub fn module_path(&self) -> &str {
296        self.module.as_deref().unwrap_or(Self::DEFAULT_MODULE)
297    }
298
299    pub fn qualified_id(&self) -> String {
300        format!("{}::{}", self.module_path(), self.id)
301    }
302
303    pub const fn storage(&self) -> ConstantStorage {
304        match self.storage {
305            Some(storage) => storage,
306            None => ConstantStorage::F32,
307        }
308    }
309
310    pub const fn quantity(&self) -> Option<cu29_units::constant::Quantity> {
311        self.quantity
312    }
313
314    pub const fn explicit_unit(&self) -> Option<cu29_units::constant::Unit> {
315        self.unit
316    }
317
318    pub fn expression_definition(&self) -> Option<(&str, &str)> {
319        self.rust_type.as_deref().zip(self.expression.as_deref())
320    }
321
322    pub fn resolved_unit(&self) -> Result<Option<cu29_units::constant::Unit>, String> {
323        let Some(quantity) = self.quantity else {
324            return Ok(None);
325        };
326        if let Some(unit) = self.unit {
327            return Ok(Some(unit));
328        }
329        let definition = cu29_units::constant::definition(quantity).ok_or_else(|| {
330            format!(
331                "Constant '{}' uses quantity '{}' which is missing from the unit catalogue",
332                self.id,
333                quantity.name()
334            )
335        })?;
336        cu29_units::constant::Unit::from_name(definition.coherent_unit)
337            .map(Some)
338            .ok_or_else(|| {
339                format!(
340                    "Constant '{}' quantity '{}' has invalid coherent unit metadata '{}'",
341                    self.id,
342                    quantity.name(),
343                    definition.coherent_unit
344                )
345            })
346    }
347
348    pub fn numbers(&self) -> Result<(bool, Vec<ConstantNumber>), String> {
349        fn number(value: &RonValue) -> Result<ConstantNumber, String> {
350            match value {
351                RonValue::Number(number) => match number {
352                    Number::I8(value) => Ok(ConstantNumber::Signed(i64::from(*value))),
353                    Number::I16(value) => Ok(ConstantNumber::Signed(i64::from(*value))),
354                    Number::I32(value) => Ok(ConstantNumber::Signed(i64::from(*value))),
355                    Number::I64(value) => Ok(ConstantNumber::Signed(*value)),
356                    Number::U8(value) => Ok(ConstantNumber::Unsigned(u64::from(*value))),
357                    Number::U16(value) => Ok(ConstantNumber::Unsigned(u64::from(*value))),
358                    Number::U32(value) => Ok(ConstantNumber::Unsigned(u64::from(*value))),
359                    Number::U64(value) => Ok(ConstantNumber::Unsigned(*value)),
360                    Number::F32(value) => Ok(ConstantNumber::Float(f64::from(value.0))),
361                    Number::F64(value) => Ok(ConstantNumber::Float(value.0)),
362                    _ => Err("unsupported numeric representation".to_string()),
363                },
364                _ => Err("expected a number".to_string()),
365            }
366        }
367
368        let value = self
369            .value
370            .as_ref()
371            .ok_or_else(|| format!("Constant '{}' does not declare a numeric value", self.id))?;
372        match &value.0 {
373            RonValue::Seq(values) => values
374                .iter()
375                .map(number)
376                .collect::<Result<Vec<_>, _>>()
377                .map(|values| (true, values)),
378            value => number(value).map(|value| (false, vec![value])),
379        }
380        .map_err(|error| format!("Constant '{}': {error}", self.id))
381    }
382
383    pub fn normalized_f32(&self) -> Result<(bool, Vec<f32>), String> {
384        let quantity = self.quantity.ok_or_else(|| {
385            format!(
386                "Constant '{}' does not declare a physical quantity",
387                self.id
388            )
389        })?;
390        let unit = self
391            .resolved_unit()?
392            .ok_or_else(|| format!("Constant '{}' has no resolved unit", self.id))?;
393        let (is_array, numbers) = self.numbers()?;
394        numbers
395            .into_iter()
396            .map(|number| {
397                let value = number.as_f64() as f32;
398                if !value.is_finite() {
399                    return Err(format!("Constant '{}' values must be finite", self.id));
400                }
401                cu29_units::constant::normalize_f32(quantity, unit, value).ok_or_else(|| {
402                    format!(
403                        "Constant '{}' unit '{}' is not compatible with quantity '{}'",
404                        self.id,
405                        unit.name(),
406                        quantity.name()
407                    )
408                })
409            })
410            .collect::<Result<Vec<_>, _>>()
411            .map(|values| (is_array, values))
412    }
413
414    pub fn normalized_f64(&self) -> Result<(bool, Vec<f64>), String> {
415        let quantity = self.quantity.ok_or_else(|| {
416            format!(
417                "Constant '{}' does not declare a physical quantity",
418                self.id
419            )
420        })?;
421        let unit = self
422            .resolved_unit()?
423            .ok_or_else(|| format!("Constant '{}' has no resolved unit", self.id))?;
424        let (is_array, numbers) = self.numbers()?;
425        numbers
426            .into_iter()
427            .map(|number| {
428                let value = number.as_f64();
429                if !value.is_finite() {
430                    return Err(format!("Constant '{}' values must be finite", self.id));
431                }
432                cu29_units::constant::normalize_f64(quantity, unit, value).ok_or_else(|| {
433                    format!(
434                        "Constant '{}' unit '{}' is not compatible with quantity '{}'",
435                        self.id,
436                        unit.name(),
437                        quantity.name()
438                    )
439                })
440            })
441            .collect::<Result<Vec<_>, _>>()
442            .map(|values| (is_array, values))
443    }
444
445    /// Stable comparison key for detecting runtime attempts to change a baked constant.
446    #[allow(dead_code)]
447    pub fn semantic_fingerprint(&self) -> Result<u64, String> {
448        const OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
449        const PRIME: u64 = 0x0000_0100_0000_01b3;
450
451        fn hash_bytes(hash: &mut u64, bytes: &[u8]) {
452            for byte in bytes {
453                *hash ^= u64::from(*byte);
454                *hash = hash.wrapping_mul(PRIME);
455            }
456        }
457
458        let mut hash = OFFSET;
459        if let Some((rust_type, expression)) = self.expression_definition() {
460            hash_bytes(&mut hash, b"expression");
461            hash_bytes(&mut hash, rust_type.as_bytes());
462            hash_bytes(&mut hash, &[0]);
463            hash_bytes(&mut hash, expression.as_bytes());
464            return Ok(hash);
465        }
466
467        let storage = self.storage();
468        hash_bytes(&mut hash, b"numeric");
469        hash_bytes(&mut hash, storage.rust_type().as_bytes());
470        hash_bytes(
471            &mut hash,
472            self.quantity
473                .map_or("primitive", |quantity| quantity.name())
474                .as_bytes(),
475        );
476
477        if self.quantity.is_some() {
478            match storage {
479                ConstantStorage::F32 => {
480                    let (is_array, values) = self.normalized_f32()?;
481                    hash_bytes(&mut hash, &[u8::from(is_array)]);
482                    for value in values {
483                        hash_bytes(&mut hash, &value.to_bits().to_le_bytes());
484                    }
485                }
486                ConstantStorage::F64 => {
487                    let (is_array, values) = self.normalized_f64()?;
488                    hash_bytes(&mut hash, &[u8::from(is_array)]);
489                    for value in values {
490                        hash_bytes(&mut hash, &value.to_bits().to_le_bytes());
491                    }
492                }
493                _ => {
494                    return Err(format!(
495                        "Constant '{}' quantity storage must be f32 or f64",
496                        self.id
497                    ));
498                }
499            }
500            return Ok(hash);
501        }
502
503        let (is_array, numbers) = self.numbers()?;
504        hash_bytes(&mut hash, &[u8::from(is_array)]);
505        for number in numbers {
506            match (storage, number) {
507                (ConstantStorage::F32, number) => {
508                    hash_bytes(&mut hash, &(number.as_f64() as f32).to_bits().to_le_bytes())
509                }
510                (ConstantStorage::F64, number) => {
511                    hash_bytes(&mut hash, &number.as_f64().to_bits().to_le_bytes())
512                }
513                (_, ConstantNumber::Signed(value)) => hash_bytes(&mut hash, &value.to_le_bytes()),
514                (_, ConstantNumber::Unsigned(value)) => hash_bytes(&mut hash, &value.to_le_bytes()),
515                (_, ConstantNumber::Float(value)) => {
516                    hash_bytes(&mut hash, &value.to_bits().to_le_bytes())
517                }
518            }
519        }
520        Ok(hash)
521    }
522}
523
524#[derive(Debug, Clone, PartialEq)]
525pub struct ConfigError {
526    message: String,
527}
528
529impl ConfigError {
530    fn type_mismatch(expected: &'static str, value: &Value) -> Self {
531        ConfigError {
532            message: format!("Expected {expected} but got {value:?}"),
533        }
534    }
535
536    fn with_key(self, key: &str) -> Self {
537        ConfigError {
538            message: format!("Config key '{key}': {}", self.message),
539        }
540    }
541}
542
543impl Display for ConfigError {
544    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
545        write!(f, "{}", self.message)
546    }
547}
548
549#[cfg(feature = "std")]
550impl std::error::Error for ConfigError {}
551
552#[cfg(not(feature = "std"))]
553impl core::error::Error for ConfigError {}
554
555impl From<ConfigError> for CuError {
556    fn from(err: ConfigError) -> Self {
557        CuError::from(err.to_string())
558    }
559}
560
561// Macro for implementing From<T> for Value where T is a numeric type
562macro_rules! impl_from_numeric_for_value {
563    ($($source:ty),* $(,)?) => {
564        $(impl From<$source> for Value {
565            fn from(value: $source) -> Self {
566                Value(RonValue::Number(value.into()))
567            }
568        })*
569    };
570}
571
572// Implement From for common numeric types
573impl_from_numeric_for_value!(i8, i16, i32, i64, u8, u16, u32, u64, f32, f64);
574
575impl TryFrom<&Value> for bool {
576    type Error = ConfigError;
577
578    fn try_from(value: &Value) -> Result<Self, Self::Error> {
579        if let Value(RonValue::Bool(v)) = value {
580            Ok(*v)
581        } else {
582            Err(ConfigError::type_mismatch("bool", value))
583        }
584    }
585}
586
587impl From<Value> for bool {
588    fn from(value: Value) -> Self {
589        if let Value(RonValue::Bool(v)) = value {
590            v
591        } else {
592            panic!("Expected a Boolean variant but got {value:?}")
593        }
594    }
595}
596macro_rules! impl_from_value_for_int {
597    ($($target:ty),* $(,)?) => {
598        $(
599            impl From<Value> for $target {
600                fn from(value: Value) -> Self {
601                    if let Value(RonValue::Number(num)) = value {
602                        match num {
603                            Number::I8(n) => n as $target,
604                            Number::I16(n) => n as $target,
605                            Number::I32(n) => n as $target,
606                            Number::I64(n) => n as $target,
607                            Number::U8(n) => n as $target,
608                            Number::U16(n) => n as $target,
609                            Number::U32(n) => n as $target,
610                            Number::U64(n) => n as $target,
611                            Number::F32(_) | Number::F64(_) => {
612                                panic!("Expected an integer Number variant but got {num:?}")
613                            }
614                            _ => {
615                                panic!("Expected an integer Number variant but got {num:?}")
616                            }
617                        }
618                    } else {
619                        panic!("Expected a Number variant but got {value:?}")
620                    }
621                }
622            }
623        )*
624    };
625}
626
627impl_from_value_for_int!(u8, i8, u16, i16, u32, i32, u64, i64);
628
629macro_rules! impl_try_from_value_for_int {
630    ($($target:ty),* $(,)?) => {
631        $(
632            impl TryFrom<&Value> for $target {
633                type Error = ConfigError;
634
635                fn try_from(value: &Value) -> Result<Self, Self::Error> {
636                    if let Value(RonValue::Number(num)) = value {
637                        match num {
638                            Number::I8(n) => Ok(*n as $target),
639                            Number::I16(n) => Ok(*n as $target),
640                            Number::I32(n) => Ok(*n as $target),
641                            Number::I64(n) => Ok(*n as $target),
642                            Number::U8(n) => Ok(*n as $target),
643                            Number::U16(n) => Ok(*n as $target),
644                            Number::U32(n) => Ok(*n as $target),
645                            Number::U64(n) => Ok(*n as $target),
646                            Number::F32(_) | Number::F64(_) => {
647                                Err(ConfigError::type_mismatch("integer", value))
648                            }
649                            _ => {
650                                Err(ConfigError::type_mismatch("integer", value))
651                            }
652                        }
653                    } else {
654                        Err(ConfigError::type_mismatch("integer", value))
655                    }
656                }
657            }
658        )*
659    };
660}
661
662impl_try_from_value_for_int!(u8, i8, u16, i16, u32, i32, u64, i64);
663
664impl TryFrom<&Value> for f64 {
665    type Error = ConfigError;
666
667    fn try_from(value: &Value) -> Result<Self, Self::Error> {
668        if let Value(RonValue::Number(num)) = value {
669            let number = match num {
670                Number::I8(n) => *n as f64,
671                Number::I16(n) => *n as f64,
672                Number::I32(n) => *n as f64,
673                Number::I64(n) => *n as f64,
674                Number::U8(n) => *n as f64,
675                Number::U16(n) => *n as f64,
676                Number::U32(n) => *n as f64,
677                Number::U64(n) => *n as f64,
678                Number::F32(n) => n.0 as f64,
679                Number::F64(n) => n.0,
680                _ => {
681                    return Err(ConfigError::type_mismatch("number", value));
682                }
683            };
684            Ok(number)
685        } else {
686            Err(ConfigError::type_mismatch("number", value))
687        }
688    }
689}
690
691impl From<Value> for f64 {
692    fn from(value: Value) -> Self {
693        if let Value(RonValue::Number(num)) = value {
694            num.into_f64()
695        } else {
696            panic!("Expected a Number variant but got {value:?}")
697        }
698    }
699}
700
701//Basically just a copy of the From<Value> for f64.
702impl TryFrom<&Value> for f32 {
703    type Error = ConfigError;
704
705    fn try_from(value: &Value) -> Result<Self, Self::Error> {
706        if let Value(RonValue::Number(num)) = value {
707            let number = match num {
708                Number::I8(n) => *n as f32,
709                Number::I16(n) => *n as f32,
710                Number::I32(n) => *n as f32,
711                Number::I64(n) => *n as f32,
712                Number::U8(n) => *n as f32,
713                Number::U16(n) => *n as f32,
714                Number::U32(n) => *n as f32,
715                Number::U64(n) => *n as f32,
716                Number::F32(n) => n.0,
717                Number::F64(n) => n.0 as f32,
718                _ => {
719                    return Err(ConfigError::type_mismatch("number", value));
720                }
721            };
722            Ok(number)
723        } else {
724            Err(ConfigError::type_mismatch("number", value))
725        }
726    }
727}
728
729impl From<Value> for f32 {
730    fn from(value: Value) -> Self {
731        if let Value(RonValue::Number(num)) = value {
732            num.into_f64() as f32
733        } else {
734            panic!("Expected a Number variant but got {value:?}")
735        }
736    }
737}
738
739impl From<String> for Value {
740    fn from(value: String) -> Self {
741        Value(RonValue::String(value))
742    }
743}
744
745impl TryFrom<&Value> for String {
746    type Error = ConfigError;
747
748    fn try_from(value: &Value) -> Result<Self, Self::Error> {
749        if let Value(RonValue::String(s)) = value {
750            Ok(s.clone())
751        } else {
752            Err(ConfigError::type_mismatch("string", value))
753        }
754    }
755}
756
757impl From<Value> for String {
758    fn from(value: Value) -> Self {
759        if let Value(RonValue::String(s)) = value {
760            s
761        } else {
762            panic!("Expected a String variant")
763        }
764    }
765}
766
767impl Display for Value {
768    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
769        let Value(value) = self;
770        match value {
771            RonValue::Number(n) => {
772                let s = match n {
773                    Number::I8(n) => n.to_string(),
774                    Number::I16(n) => n.to_string(),
775                    Number::I32(n) => n.to_string(),
776                    Number::I64(n) => n.to_string(),
777                    Number::U8(n) => n.to_string(),
778                    Number::U16(n) => n.to_string(),
779                    Number::U32(n) => n.to_string(),
780                    Number::U64(n) => n.to_string(),
781                    Number::F32(n) => n.0.to_string(),
782                    Number::F64(n) => n.0.to_string(),
783                    _ => panic!("Expected a Number variant but got {value:?}"),
784                };
785                write!(f, "{s}")
786            }
787            RonValue::String(s) => write!(f, "{s}"),
788            RonValue::Bool(b) => write!(f, "{b}"),
789            RonValue::Map(m) => write!(f, "{m:?}"),
790            RonValue::Char(c) => write!(f, "{c:?}"),
791            RonValue::Unit => write!(f, "unit"),
792            RonValue::Option(o) => write!(f, "{o:?}"),
793            RonValue::Seq(s) => write!(f, "{s:?}"),
794            RonValue::Bytes(bytes) => write!(f, "{bytes:?}"),
795        }
796    }
797}