Skip to main content

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