Skip to main content

qubit_config/from/
from_config.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10
11use std::collections::HashMap;
12use std::time::Duration;
13
14use bigdecimal::BigDecimal;
15use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Utc};
16use num_bigint::BigInt;
17use qubit_datatype::{DataConvertTo, DataConverter};
18use qubit_value::{MultiValues, Value as QubitValue};
19use serde_json::Value as JsonValue;
20use url::Url;
21
22use crate::{ConfigResult, Property, utils};
23
24use super::config_parse_context::ConfigParseContext;
25use super::helpers::first_scalar_string;
26
27/// Parses a configuration [`Property`] into a target Rust type.
28///
29pub trait FromConfig: Sized {
30    /// Parses `property` using `ctx`.
31    ///
32    /// # Parameters
33    ///
34    /// * `property` - Non-empty property selected by the reader.
35    /// * `ctx` - Key, options, and substitution context.
36    ///
37    /// # Returns
38    ///
39    /// Parsed value, or a [`crate::ConfigError`] with key context.
40    fn from_config(property: &Property, ctx: &ConfigParseContext<'_>) -> ConfigResult<Self>;
41}
42
43/// Converts the first scalar string value of a property to a target type.
44///
45/// # Parameters
46///
47/// * `property` - The property to convert.
48/// * `ctx` - The parsing context.
49///
50/// # Returns
51///
52/// The converted value, or a [`ConfigError`] with key context.
53///
54fn convert_first<T>(property: &Property, ctx: &ConfigParseContext<'_>) -> ConfigResult<T>
55where
56    for<'a> DataConverter<'a>: DataConvertTo<T>,
57{
58    if let Some(value) = first_scalar_string(property) {
59        let value = ctx.substitute_string(value)?;
60        QubitValue::String(value)
61            .to_with::<T>(ctx.options().conversion_options())
62            .map_err(|e| utils::map_value_error(ctx.key(), e))
63    } else {
64        property
65            .value()
66            .to_with::<T>(ctx.options().conversion_options())
67            .map_err(|e| utils::map_value_error(ctx.key(), e))
68    }
69}
70
71/// Builds a conversion source with string leaves after variable substitution.
72///
73/// # Parameters
74///
75/// * `property` - Property whose value is used as the conversion source.
76/// * `ctx` - Parsing context that supplies variable substitution.
77///
78/// # Returns
79///
80/// A [`MultiValues`] value with string entries substituted; non-string entries
81/// are cloned unchanged.
82///
83/// # Errors
84///
85/// Returns a substitution error if any string entry cannot be resolved.
86fn substituted_values(
87    property: &Property,
88    ctx: &ConfigParseContext<'_>,
89) -> ConfigResult<MultiValues> {
90    match property.value() {
91        MultiValues::String(values) => values
92            .iter()
93            .map(|value| ctx.substitute_string(value))
94            .collect::<ConfigResult<Vec<_>>>()
95            .map(MultiValues::String),
96        values => Ok(values.clone()),
97    }
98}
99
100/// Implements the `FromConfig` trait for a list of types.
101///
102/// # Parameters
103///
104/// * `($($ty:ty),+ $(,)?)` - The list of types to implement the trait for.
105///
106macro_rules! impl_from_config_via_value {
107    ($($ty:ty),+ $(,)?) => {
108        $(
109            impl FromConfig for $ty {
110                fn from_config(
111                    property: &Property,
112                    ctx: &ConfigParseContext<'_>,
113                ) -> ConfigResult<Self> {
114                    convert_first::<$ty>(property, ctx)
115                }
116            }
117        )+
118    };
119}
120
121impl_from_config_via_value!(
122    bool,
123    i8,
124    i16,
125    i32,
126    i64,
127    i128,
128    isize,
129    u8,
130    u16,
131    u32,
132    u64,
133    u128,
134    usize,
135    f32,
136    f64,
137    char,
138    NaiveDate,
139    NaiveTime,
140    NaiveDateTime,
141    DateTime<Utc>,
142    Duration,
143    Url,
144    BigInt,
145    BigDecimal,
146    JsonValue,
147    HashMap<String, String>,
148);
149
150impl FromConfig for String {
151    /// Parses `property` using `ctx`.
152    ///
153    /// # Parameters
154    ///
155    /// * `property` - Non-empty property selected by the reader.
156    /// * `ctx` - Key, options, and substitution context.
157    ///
158    /// # Returns
159    ///
160    /// Parsed value, or a [`crate::ConfigError`] with key context.
161    fn from_config(property: &Property, ctx: &ConfigParseContext<'_>) -> ConfigResult<Self> {
162        if let Some(value) = first_scalar_string(property) {
163            let value = ctx.substitute_string(value)?;
164            QubitValue::String(value)
165                .to_with::<String>(ctx.options().conversion_options())
166                .map_err(|e| utils::map_value_error(ctx.key(), e))
167        } else {
168            property
169                .value()
170                .to_with::<String>(ctx.options().conversion_options())
171                .map_err(|e| utils::map_value_error(ctx.key(), e))
172        }
173    }
174}
175
176impl<T> FromConfig for Vec<T>
177where
178    T: FromConfig,
179{
180    /// Parses `property` using `ctx`.
181    ///
182    /// # Parameters
183    ///
184    /// * `property` - Non-empty property selected by the reader.
185    /// * `ctx` - Key, options, and substitution context.
186    ///
187    /// # Returns
188    ///
189    /// Parsed value, or a [`crate::ConfigError`] with key context.
190    fn from_config(property: &Property, ctx: &ConfigParseContext<'_>) -> ConfigResult<Self> {
191        let values = substituted_values(property, ctx)?
192            .to_list_with::<String>(ctx.options().conversion_options())
193            .map_err(|e| utils::map_value_error(ctx.key(), e))?;
194
195        let mut result = Vec::new();
196        for item in values {
197            let item_property =
198                Property::with_value(ctx.key().to_string(), MultiValues::String(vec![item]));
199            result.push(T::from_config(&item_property, ctx)?);
200        }
201        Ok(result)
202    }
203}