bo4e_core/
additional_attribute.rs

1//! Additional attributes for extensibility.
2
3use serde::{Deserialize, Serialize};
4
5/// Value type for additional attributes.
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
7#[serde(untagged)]
8pub enum AttributeValue {
9    /// String value
10    String(String),
11    /// Numeric value (integer or float)
12    Number(f64),
13    /// Boolean value
14    Boolean(bool),
15    /// Nested object with more attributes
16    Object(Vec<AdditionalAttribute>),
17    /// Array of values
18    Array(Vec<AttributeValue>),
19    /// Null value
20    Null,
21}
22
23/// Additional attribute for external system IDs and custom metadata.
24///
25/// This enables interoperability with external systems that need to attach
26/// their own identifiers or metadata to BO4E objects.
27///
28/// # Example
29///
30/// ```rust
31/// use bo4e_core::AdditionalAttribute;
32/// use bo4e_core::additional_attribute::AttributeValue;
33///
34/// let attr = AdditionalAttribute {
35///     name: "sap_id".to_string(),
36///     value: Some(AttributeValue::String("SAP123".to_string())),
37/// };
38/// ```
39#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
40#[serde(rename_all = "camelCase")]
41pub struct AdditionalAttribute {
42    /// Name/key of the attribute
43    pub name: String,
44    /// Value of the attribute (optional)
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub value: Option<AttributeValue>,
47}
48
49impl AdditionalAttribute {
50    /// Create a new additional attribute with a string value.
51    pub fn string(name: impl Into<String>, value: impl Into<String>) -> Self {
52        Self {
53            name: name.into(),
54            value: Some(AttributeValue::String(value.into())),
55        }
56    }
57
58    /// Create a new additional attribute with a numeric value.
59    pub fn number(name: impl Into<String>, value: f64) -> Self {
60        Self {
61            name: name.into(),
62            value: Some(AttributeValue::Number(value)),
63        }
64    }
65
66    /// Create a new additional attribute with a boolean value.
67    pub fn boolean(name: impl Into<String>, value: bool) -> Self {
68        Self {
69            name: name.into(),
70            value: Some(AttributeValue::Boolean(value)),
71        }
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn test_string_attribute() {
81        let attr = AdditionalAttribute::string("system_id", "ABC123");
82        assert_eq!(attr.name, "system_id");
83        assert_eq!(
84            attr.value,
85            Some(AttributeValue::String("ABC123".to_string()))
86        );
87    }
88
89    #[test]
90    fn test_number_attribute() {
91        let attr = AdditionalAttribute::number("priority", 42.0);
92        assert_eq!(attr.name, "priority");
93        assert_eq!(attr.value, Some(AttributeValue::Number(42.0)));
94    }
95
96    #[test]
97    fn test_serialize_attribute() {
98        let attr = AdditionalAttribute::string("key", "value");
99        let json = serde_json::to_string(&attr).unwrap();
100        assert_eq!(json, r#"{"name":"key","value":"value"}"#);
101    }
102
103    #[test]
104    fn test_deserialize_attribute() {
105        let json = r#"{"name":"key","value":"value"}"#;
106        let attr: AdditionalAttribute = serde_json::from_str(json).unwrap();
107        assert_eq!(attr.name, "key");
108        assert_eq!(
109            attr.value,
110            Some(AttributeValue::String("value".to_string()))
111        );
112    }
113
114    #[test]
115    fn test_deserialize_nested_attribute() {
116        let json = r#"{"name":"parent","value":[{"name":"child","value":123}]}"#;
117        let attr: AdditionalAttribute = serde_json::from_str(json).unwrap();
118        assert_eq!(attr.name, "parent");
119        match attr.value {
120            Some(AttributeValue::Object(children)) => {
121                assert_eq!(children.len(), 1);
122                assert_eq!(children[0].name, "child");
123            }
124            _ => panic!("Expected nested object"),
125        }
126    }
127}