Skip to main content

oca_sdk_rs/data_validator/
mod.rs

1use oca_ast::ast::{AttributeType, NestedAttrType};
2use oca_bundle::state::{attribute::Attribute, oca_bundle::OCABundleModel};
3use serde_json::Value;
4
5/// Represents the validation status of the data.
6///
7/// This enum is used to indicate whether the provided data is valid
8/// or contains validation errors.
9///
10/// # Variants
11/// * `Valid` - Indicates that the data is valid and meets all validation criteria.
12/// * `Invalid(Vec<String>)` - Indicates that the data is invalid. Contains a vector
13///   of error messages describing the validation issues.
14pub enum DataValidationStatus {
15    Valid,
16    Invalid(Vec<String>),
17}
18
19/// Validates the provided data against the schema defined in the `OCABundle`.
20///
21/// This function checks if the structure and attributes of the input `data` conform
22/// to the semantics specified in the `OCABundle`. It performs validations
23/// for each attribute and aggregates any errors found.
24///
25/// # Arguments
26/// * `oca` - A reference to an `OCABundle` that contains the schema for validation.
27/// * `data` - A reference to a `serde_json::Value` representing the data to be validated.
28///   The `data` must be a JSON object.
29///
30/// # Returns
31/// * `Ok(DataValidationStatus)` - Indicates whether the data is valid or invalid,
32///   along with any associated error messages.
33/// * `Err(String)` - Indicates that an error occurred during validation, such as
34///   failure to parse the input data.
35///
36/// # Errors
37/// * Returns `Err` if the provided `data` cannot be parsed as a JSON object.
38/// * Returns `Ok(DataValidationStatus::Invalid)` if validation fails, with a
39///   vector of detailed error messages.
40///
41pub fn validate_data(
42    oca: &mut OCABundleModel,
43    data: &Value,
44) -> Result<DataValidationStatus, String> {
45    let mut errors = vec![];
46
47    if !data.is_object() {
48        return Err("Data is not an object".to_string());
49    }
50    oca.fill_attributes();
51
52    for attr in oca.attributes.as_ref().unwrap().values() {
53        let value = data.get(attr.name.clone());
54        let attribute_errors = validate_attribute(attr, value)?;
55
56        if !attribute_errors.is_empty() {
57            errors.extend(attribute_errors);
58        }
59    }
60
61    if errors.is_empty() {
62        Ok(DataValidationStatus::Valid)
63    } else {
64        Ok(DataValidationStatus::Invalid(errors))
65    }
66}
67
68fn validate_attribute(
69    attribute: &Attribute,
70    value: Option<&serde_json::Value>,
71) -> Result<Vec<String>, String> {
72    let mut errors = vec![];
73
74    let is_required = false; // TODO Replace with overlay_Def attribute.conformance == Some("M".to_string());
75
76    let v = match value {
77        Some(value) => value,
78        None => {
79            if is_required {
80                errors.push(format!(
81                    "Attribute \"{}\" value is mandatory",
82                    attribute.name
83                ));
84            }
85            return Ok(errors);
86        }
87    };
88
89    if v.is_array() || v.is_object() {
90        return Ok(errors);
91    }
92
93    if let Some(nested_attribute_type) = &attribute.attribute_type {
94        match nested_attribute_type {
95            NestedAttrType::Value(attribute_type) => match attribute_type {
96                AttributeType::Text => {
97                    if !v.is_string() {
98                        errors.push(format!(
99                            "Attribute \"{}\" value ({}) is not a string",
100                            attribute.name, v
101                        ));
102                    }
103                }
104                AttributeType::Numeric => {
105                    if !v.is_number() {
106                        errors.push(format!(
107                            "Attribute \"{}\" value ({}) is not a number",
108                            attribute.name, v
109                        ));
110                    }
111                }
112                AttributeType::DateTime => {
113                    if !v.is_string() {
114                        errors.push(format!(
115                            "Attribute \"{}\" value ({}) is not a string",
116                            attribute.name, v
117                        ));
118                    }
119                }
120                AttributeType::Boolean => {
121                    if !v.is_boolean() {
122                        errors.push(format!(
123                            "Attribute \"{}\" value ({}) is not a boolean",
124                            attribute.name, v
125                        ));
126                    }
127                }
128                AttributeType::Binary => {
129                    if !v.is_string() {
130                        errors.push(format!(
131                            "Attribute \"{}\" value ({}) is not a string",
132                            attribute.name, v
133                        ));
134                    }
135                }
136            },
137            NestedAttrType::Array(_) => {
138                if !v.is_array() {
139                    errors.push(format!(
140                        "Attribute \"{}\" value ({}) is not an array",
141                        attribute.name, v
142                    ));
143                }
144            }
145            NestedAttrType::Null => {}
146            _ => {}
147        }
148    }
149
150    // TODO: repace with overlay_Def
151    // if let Some(entry_codes) = &attribute.entry_codes {
152    //     match entry_codes {
153    //         EntryCodes::Array(codes) => {
154    //             if !codes.contains(&v.as_str().unwrap().to_string()) {
155    //                 errors.push(format!(
156    //                     "Attribute \"{}\" value ({}) is not in entry codes",
157    //                     attribute.name, v
158    //                 ));
159    //             }
160    //         }
161    //         EntryCodes::Object(codes) => {
162    //             if !codes
163    //                 .values()
164    //                 .any(|c| c.contains(&v.as_str().unwrap().to_string()))
165    //             {
166    //                 errors.push(format!(
167    //                     "Attribute \"{}\" value ({}) is not in entry codes",
168    //                     attribute.name, v
169    //                 ));
170    //             }
171    //         }
172    //         _ => {}
173    //     }
174    // }
175
176    Ok(errors)
177}