bo4e_core/
traits.rs

1//! Core traits and types for BO4E objects.
2
3use crate::AdditionalAttribute;
4use serde::{Deserialize, Serialize};
5
6/// Metadata common to all BO4E objects.
7///
8/// This struct holds the standard BO4E metadata fields:
9/// - `_typ`: Type discriminator
10/// - `_version`: BO4E schema version
11/// - `_id`: External system ID
12/// - `zusatzAttribute`: Additional attributes for extensibility
13///
14/// # Example
15///
16/// ```rust
17/// use bo4e_core::Bo4eMeta;
18///
19/// let meta = Bo4eMeta {
20///     typ: Some("Zaehler".to_string()),
21///     version: Some("202401.0.1".to_string()),
22///     ..Default::default()
23/// };
24/// ```
25#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
26#[serde(rename_all = "camelCase")]
27pub struct Bo4eMeta {
28    /// Type discriminator (maps to `_typ` in JSON)
29    #[serde(rename = "_typ", skip_serializing_if = "Option::is_none")]
30    pub typ: Option<String>,
31
32    /// BO4E schema version (maps to `_version` in JSON)
33    #[serde(rename = "_version", skip_serializing_if = "Option::is_none")]
34    pub version: Option<String>,
35
36    /// External system ID (maps to `_id` in JSON)
37    #[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
38    pub id: Option<String>,
39
40    /// Additional attributes for extensibility
41    #[serde(default, skip_serializing_if = "Vec::is_empty")]
42    pub zusatz_attribute: Vec<AdditionalAttribute>,
43}
44
45impl Bo4eMeta {
46    /// Create metadata with type name.
47    pub fn with_type(typ: impl Into<String>) -> Self {
48        Self {
49            typ: Some(typ.into()),
50            ..Default::default()
51        }
52    }
53
54    /// Set the version.
55    pub fn version(mut self, version: impl Into<String>) -> Self {
56        self.version = Some(version.into());
57        self
58    }
59
60    /// Set the external ID.
61    pub fn id(mut self, id: impl Into<String>) -> Self {
62        self.id = Some(id.into());
63        self
64    }
65
66    /// Add an additional attribute.
67    pub fn with_attribute(mut self, attr: AdditionalAttribute) -> Self {
68        self.zusatz_attribute.push(attr);
69        self
70    }
71}
72
73/// Trait implemented by all BO4E types.
74///
75/// This trait provides a common interface for accessing type metadata
76/// and enables generic programming over BO4E types.
77pub trait Bo4eObject {
78    /// Returns the German type name as used in the `_typ` field.
79    ///
80    /// Example: `"Zaehler"` for Meter, `"Marktlokation"` for MarketLocation
81    fn type_name_german() -> &'static str;
82
83    /// Returns the English type name.
84    ///
85    /// Example: `"Meter"`, `"MarketLocation"`
86    fn type_name_english() -> &'static str;
87
88    /// Returns a reference to the metadata.
89    fn meta(&self) -> &Bo4eMeta;
90
91    /// Returns a mutable reference to the metadata.
92    fn meta_mut(&mut self) -> &mut Bo4eMeta;
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn test_meta_default() {
101        let meta = Bo4eMeta::default();
102        assert!(meta.typ.is_none());
103        assert!(meta.version.is_none());
104        assert!(meta.id.is_none());
105        assert!(meta.zusatz_attribute.is_empty());
106    }
107
108    #[test]
109    fn test_meta_builder() {
110        let meta = Bo4eMeta::with_type("Zaehler")
111            .version("202401.0.1")
112            .id("ext-123");
113
114        assert_eq!(meta.typ, Some("Zaehler".to_string()));
115        assert_eq!(meta.version, Some("202401.0.1".to_string()));
116        assert_eq!(meta.id, Some("ext-123".to_string()));
117    }
118
119    #[test]
120    fn test_meta_serialize() {
121        let meta = Bo4eMeta::with_type("Zaehler").version("202401.0.1");
122        let json = serde_json::to_string(&meta).unwrap();
123
124        assert!(json.contains(r#""_typ":"Zaehler""#));
125        assert!(json.contains(r#""_version":"202401.0.1""#));
126        assert!(!json.contains("zusatzAttribute")); // Empty vec skipped
127    }
128
129    #[test]
130    fn test_meta_deserialize() {
131        let json = r#"{"_typ":"Zaehler","_version":"202401.0.1","_id":"123"}"#;
132        let meta: Bo4eMeta = serde_json::from_str(json).unwrap();
133
134        assert_eq!(meta.typ, Some("Zaehler".to_string()));
135        assert_eq!(meta.version, Some("202401.0.1".to_string()));
136        assert_eq!(meta.id, Some("123".to_string()));
137    }
138
139    #[test]
140    fn test_meta_with_zusatz_attribute() {
141        let meta = Bo4eMeta::with_type("Zaehler")
142            .with_attribute(crate::AdditionalAttribute::string("sap_id", "SAP001"));
143
144        assert_eq!(meta.zusatz_attribute.len(), 1);
145        assert_eq!(meta.zusatz_attribute[0].name, "sap_id");
146    }
147}