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}