sz_configtool_lib/
attributes.rs1use crate::error::{Result, SzConfigError};
2use crate::helpers;
3use serde_json::{Value, json};
4
5#[derive(Debug, Clone)]
11pub struct AddAttributeParams<'a> {
12 pub attribute: &'a str,
13 pub feature: &'a str,
14 pub element: &'a str,
15 pub class: &'a str,
16 pub default_value: Option<&'a str>,
17 pub internal: Option<&'a str>,
18 pub required: Option<&'a str>,
19}
20
21impl<'a> TryFrom<&'a Value> for AddAttributeParams<'a> {
22 type Error = SzConfigError;
23
24 fn try_from(json: &'a Value) -> Result<Self> {
25 Ok(Self {
26 attribute: json
27 .get("attribute")
28 .and_then(|v| v.as_str())
29 .ok_or_else(|| SzConfigError::MissingField("attribute".to_string()))?,
30 feature: json
31 .get("feature")
32 .and_then(|v| v.as_str())
33 .ok_or_else(|| SzConfigError::MissingField("feature".to_string()))?,
34 element: json
35 .get("element")
36 .and_then(|v| v.as_str())
37 .ok_or_else(|| SzConfigError::MissingField("element".to_string()))?,
38 class: json
39 .get("class")
40 .and_then(|v| v.as_str())
41 .ok_or_else(|| SzConfigError::MissingField("class".to_string()))?,
42 default_value: json.get("default").and_then(|v| v.as_str()),
43 internal: json.get("internal").and_then(|v| v.as_str()),
44 required: json.get("required").and_then(|v| v.as_str()),
45 })
46 }
47}
48
49#[derive(Debug, Clone, Default)]
51pub struct SetAttributeParams<'a> {
52 pub attribute: &'a str,
53 pub internal: Option<&'a str>,
54 pub required: Option<&'a str>,
55 pub default_value: Option<&'a str>,
56}
57
58impl<'a> TryFrom<&'a Value> for SetAttributeParams<'a> {
59 type Error = SzConfigError;
60
61 fn try_from(json: &'a Value) -> Result<Self> {
62 Ok(Self {
63 attribute: json
64 .get("attribute")
65 .and_then(|v| v.as_str())
66 .ok_or_else(|| SzConfigError::MissingField("attribute".to_string()))?,
67 internal: json.get("internal").and_then(|v| v.as_str()),
68 required: json.get("required").and_then(|v| v.as_str()),
69 default_value: json.get("default").and_then(|v| v.as_str()),
70 })
71 }
72}
73
74pub fn add_attribute(config_json: &str, params: AddAttributeParams) -> Result<(String, Value)> {
90 let config: Value =
91 serde_json::from_str(config_json).map_err(|e| SzConfigError::JsonParse(e.to_string()))?;
92
93 let valid_classes = [
95 "NAME",
96 "ATTRIBUTE",
97 "IDENTIFIER",
98 "ADDRESS",
99 "PHONE",
100 "RELATIONSHIP",
101 "OTHER",
102 ];
103 if !valid_classes.contains(¶ms.class) {
104 return Err(SzConfigError::InvalidInput(format!(
105 "Invalid attribute class '{}'. Must be one of: {}",
106 params.class,
107 valid_classes.join(", ")
108 )));
109 }
110
111 let attribute_upper = params.attribute.to_uppercase();
112 let feature_upper = params.feature.to_uppercase();
113 let element_upper = params.element.to_uppercase();
114
115 let attrs = config
117 .get("G2_CONFIG")
118 .and_then(|g| g.get("CFG_ATTR"))
119 .and_then(|v| v.as_array())
120 .ok_or_else(|| SzConfigError::MissingSection("CFG_ATTR".to_string()))?;
121
122 if attrs
123 .iter()
124 .any(|attr| attr["ATTR_CODE"].as_str() == Some(&attribute_upper))
125 {
126 return Err(SzConfigError::AlreadyExists(format!(
127 "Attribute: {attribute_upper}"
128 )));
129 }
130
131 let _ftype_id = helpers::lookup_feature_id(config_json, &feature_upper)?;
133
134 let _felem_id = helpers::lookup_element_id(config_json, &element_upper)?;
136
137 let required = if let Some(req) = params.required {
139 let req_upper = req.to_uppercase();
140 match req_upper.as_str() {
141 "YES" => "Yes",
142 "NO" => "No",
143 "ANY" => "Any",
144 "DESIRED" => "Desired",
145 _ => {
146 return Err(SzConfigError::InvalidInput(format!(
147 "Invalid REQUIRED value '{req}'. Must be one of: Yes, No, Any, Desired"
148 )));
149 }
150 }
151 } else {
152 "No"
153 };
154
155 let internal = if let Some(int) = params.internal {
157 let int_upper = int.to_uppercase();
158 match int_upper.as_str() {
159 "YES" => "Yes",
160 "NO" => "No",
161 _ => {
162 return Err(SzConfigError::InvalidInput(format!(
163 "Invalid INTERNAL value '{int}'. Must be 'Yes' or 'No'"
164 )));
165 }
166 }
167 } else {
168 "No"
169 };
170
171 let next_attr_id = helpers::get_next_id_from_array(attrs, "ATTR_ID")?;
173
174 let new_attribute = json!({
176 "ATTR_ID": next_attr_id,
177 "ATTR_CODE": attribute_upper.clone(),
178 "ATTR_CLASS": params.class,
179 "FTYPE_CODE": feature_upper, "FELEM_CODE": element_upper, "FELEM_REQ": required,
182 "DEFAULT_VALUE": params.default_value.map(|v| json!(v)).unwrap_or(Value::Null),
183 "INTERNAL": internal
184 });
185
186 let modified_json =
188 helpers::add_to_config_array(config_json, "CFG_ATTR", new_attribute.clone())?;
189
190 Ok((modified_json, new_attribute))
191}
192
193pub fn delete_attribute(config_json: &str, code: &str) -> Result<String> {
207 helpers::delete_from_config_array(config_json, "CFG_ATTR", "ATTR_CODE", &code.to_uppercase())
208}
209
210pub fn get_attribute(config_json: &str, code: &str) -> Result<Value> {
224 let config: Value =
225 serde_json::from_str(config_json).map_err(|e| SzConfigError::JsonParse(e.to_string()))?;
226
227 let code_upper = code.to_uppercase();
228 config
229 .get("G2_CONFIG")
230 .and_then(|g| g.get("CFG_ATTR"))
231 .and_then(|v| v.as_array())
232 .ok_or_else(|| SzConfigError::MissingSection("CFG_ATTR".to_string()))?
233 .iter()
234 .find(|attr| attr["ATTR_CODE"].as_str() == Some(&code_upper))
235 .cloned()
236 .ok_or_else(|| SzConfigError::NotFound(format!("Attribute not found: {code_upper}")))
237}
238
239pub fn list_attributes(config_json: &str) -> Result<Vec<Value>> {
251 let config: Value =
252 serde_json::from_str(config_json).map_err(|e| SzConfigError::JsonParse(e.to_string()))?;
253
254 let attrs = config
255 .get("G2_CONFIG")
256 .and_then(|g| g.get("CFG_ATTR"))
257 .and_then(|v| v.as_array())
258 .ok_or_else(|| SzConfigError::MissingSection("CFG_ATTR".to_string()))?;
259
260 Ok(attrs
261 .iter()
262 .map(|item| {
263 json!({
264 "id": item.get("ATTR_ID").and_then(|v| v.as_i64()).unwrap_or(0),
265 "attribute": item.get("ATTR_CODE").and_then(|v| v.as_str()).unwrap_or(""),
266 "class": item.get("ATTR_CLASS").and_then(|v| v.as_str()).unwrap_or(""),
267 "feature": item.get("FTYPE_CODE").cloned().unwrap_or(Value::Null),
268 "element": item.get("FELEM_CODE").cloned().unwrap_or(Value::Null),
269 "required": item.get("FELEM_REQ").and_then(|v| v.as_str()).unwrap_or(""),
270 "default": item.get("DEFAULT_VALUE").cloned().unwrap_or(Value::Null),
271 "internal": item.get("INTERNAL").and_then(|v| v.as_str()).unwrap_or("")
272 })
273 })
274 .collect())
275}
276
277pub fn set_attribute(config_json: &str, params: SetAttributeParams) -> Result<String> {
292 let mut config: Value =
293 serde_json::from_str(config_json).map_err(|e| SzConfigError::JsonParse(e.to_string()))?;
294
295 let code_upper = params.attribute.to_uppercase();
296 let attrs = config
297 .get_mut("G2_CONFIG")
298 .and_then(|g| g.get_mut("CFG_ATTR"))
299 .and_then(|v| v.as_array_mut())
300 .ok_or_else(|| SzConfigError::MissingSection("CFG_ATTR".to_string()))?;
301
302 let attr = attrs
303 .iter_mut()
304 .find(|a| a["ATTR_CODE"].as_str() == Some(&code_upper))
305 .ok_or_else(|| SzConfigError::NotFound(format!("Attribute not found: {code_upper}")))?;
306
307 if let Some(val) = params.internal {
309 let val_upper = val.to_uppercase();
311 let validated = match val_upper.as_str() {
312 "YES" => "Yes",
313 "NO" => "No",
314 _ => {
315 return Err(SzConfigError::InvalidInput(format!(
316 "Invalid INTERNAL value '{val}'. Must be 'Yes' or 'No'"
317 )));
318 }
319 };
320 attr["INTERNAL"] = json!(validated);
321 }
322 if let Some(val) = params.required {
323 let val_upper = val.to_uppercase();
325 let validated = match val_upper.as_str() {
326 "YES" => "Yes",
327 "NO" => "No",
328 "ANY" => "Any",
329 "DESIRED" => "Desired",
330 _ => {
331 return Err(SzConfigError::InvalidInput(format!(
332 "Invalid REQUIRED value '{val}'. Must be one of: Yes, No, Any, Desired"
333 )));
334 }
335 };
336 attr["FELEM_REQ"] = json!(validated);
337 }
338 if let Some(val) = params.default_value {
339 attr["DEFAULT_VALUE"] = json!(val);
340 }
341
342 serde_json::to_string(&config).map_err(|e| SzConfigError::JsonParse(e.to_string()))
343}