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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
use gaia_stub::tco_tmiv::{tco_param, Tco, TcoParam};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub enum DataType {
    INTEGER,
    DOUBLE,
    BYTES,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Schema {
    pub name: String,
    pub params: Vec<ParamSchema>,
}

impl Schema {
    fn validate(&self, normalized_tco: &Tco) -> Result<(), String> {
        if self.params.len() != normalized_tco.params.len() {
            return Err(format!(
                "Mismatched the number of params: expected: {}, actual: {}",
                self.params.len(),
                normalized_tco.params.len()
            ));
        }
        for (idx, (param_schema, value)) in self
            .params
            .iter()
            .zip(normalized_tco.params.iter())
            .enumerate()
        {
            param_schema
                .validate(value)
                .map_err(|e| format!("params[{idx}]: {e}"))?;
        }
        Ok(())
    }

    fn normalize(&mut self) {
        self.params.sort_by(|a, b| a.name.cmp(&b.name));
    }
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct ParamSchema {
    pub name: String,
    pub data_type: DataType,
}

impl ParamSchema {
    fn validate_name(&self, name: &str) -> Result<(), String> {
        if self.name != *name {
            return Err(format!(
                "Mismatched param name: expected: {}, actual: {}",
                &self.name, name
            ));
        }
        Ok(())
    }

    fn validate_value(&self, value: &tco_param::Value) -> Result<(), String> {
        match (&self.data_type, value) {
            (DataType::INTEGER, tco_param::Value::Integer(_))
            | (DataType::DOUBLE, tco_param::Value::Double(_))
            | (DataType::BYTES, tco_param::Value::Bytes(_)) => Ok(()),
            _ => Err("type mismatched".to_string()),
        }
    }

    fn validate(&self, TcoParam { name, value }: &TcoParam) -> Result<(), String> {
        self.validate_name(name)?;
        let value = value.as_ref().ok_or("no value")?;
        self.validate_value(value)
            .map_err(|e| format!("{name} is {e}"))?;
        Ok(())
    }
}

#[derive(Debug)]
pub struct SchemaSet {
    schemata: Vec<Schema>,
}

impl SchemaSet {
    pub fn new(mut schemata: Vec<Schema>) -> Self {
        schemata.sort_by(|a, b| a.name.cmp(&b.name));
        for schema in &mut schemata {
            schema.normalize();
        }
        Self::new_unchecked(schemata)
    }

    pub fn new_unchecked(schemata: Vec<Schema>) -> Self {
        Self { schemata }
    }

    pub fn validate(&self, normalized_tco: &Tco) -> Result<(), String> {
        let schema = self
            .find_schema_by_name(&normalized_tco.name)
            .ok_or_else(|| format!("No matched schema for command {}", &normalized_tco.name))?;
        schema.validate(normalized_tco)?;
        Ok(())
    }

    pub fn sanitize(&self, tco: &Tco) -> Result<Tco, String> {
        let mut normalized_tco = tco.clone();
        normalize_tco(&mut normalized_tco);
        self.validate(&normalized_tco)?;
        Ok(normalized_tco)
    }

    fn find_schema_by_name(&self, param_name: &str) -> Option<&Schema> {
        self.schemata
            .iter()
            .find(|schema| param_name == schema.name)
    }
}

fn normalize_tco(tco: &mut Tco) {
    tco.params.sort_by(|a, b| a.name.cmp(&b.name))
}