1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
use cel_interpreter::{CelExpression, CelType, CelValue};
use derive_builder::Builder;
use serde::{Deserialize, Serialize};

/// Contains the parameters used to create a new `TxTemplate`
#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
#[builder(build_fn(validate = "Self::validate"))]
pub struct ParamDefinition {
    #[builder(setter(into))]
    pub(super) name: String,
    pub(super) r#type: ParamDataType,
    #[builder(setter(strip_option, name = "default_expr", into), default)]
    pub(super) default: Option<String>,
    #[builder(setter(strip_option, into), default)]
    pub(super) description: Option<String>,
}

impl ParamDefinition {
    pub fn builder() -> ParamDefinitionBuilder {
        ParamDefinitionBuilder::default()
    }

    pub fn default_expr(&self) -> Option<CelExpression> {
        self.default
            .as_ref()
            .map(|v| v.parse().expect("Couldn't create default_expr"))
    }
}

impl ParamDefinitionBuilder {
    fn validate(&self) -> Result<(), String> {
        if let Some(Some(expr)) = self.default.as_ref() {
            let expr = CelExpression::try_from(expr.as_str()).map_err(|e| e.to_string())?;
            let param_type = ParamDataType::try_from(
                &expr
                    .evaluate(&super::cel_context::initialize())
                    .map_err(|e| format!("{e}"))?,
            )?;
            let specified_type = self.r#type.as_ref().unwrap();
            if &param_type != specified_type {
                return Err(format!(
                    "Default expression type {param_type:?} does not match parameter type {specified_type:?}"
                ));
            }
        }
        Ok(())
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub enum ParamDataType {
    STRING,
    INTEGER,
    DECIMAL,
    BOOLEAN,
    UUID,
    DATE,
    TIMESTAMP,
    JSON,
}

impl TryFrom<&CelValue> for ParamDataType {
    type Error = String;

    fn try_from(value: &CelValue) -> Result<Self, Self::Error> {
        use cel_interpreter::CelType::*;
        match CelType::from(value) {
            Int => Ok(ParamDataType::INTEGER),
            String => Ok(ParamDataType::STRING),
            Map => Ok(ParamDataType::JSON),
            Date => Ok(ParamDataType::DATE),
            Uuid => Ok(ParamDataType::UUID),
            Decimal => Ok(ParamDataType::DECIMAL),
            Bool => Ok(ParamDataType::BOOLEAN),
            _ => Err(format!("Unsupported type: {value:?}")),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn build_param_definition() {
        let definition = ParamDefinition::builder()
            .name("name")
            .r#type(ParamDataType::JSON)
            .default_expr("{'key': 'value'}")
            .build()
            .unwrap();
        assert_eq!(definition.name, "name");
    }
}