Skip to main content

bo4e_core/bo/
person.rs

1//! Person (Person) business object.
2
3use chrono::NaiveDate;
4use serde::{Deserialize, Serialize};
5
6use crate::com::{Address, ContactMethod};
7use crate::enums::{Salutation, Title};
8use crate::traits::{Bo4eMeta, Bo4eObject};
9
10/// A natural person.
11///
12/// German: Person
13///
14/// # Example
15///
16/// ```rust
17/// use bo4e_core::bo::Person;
18/// use bo4e_core::enums::Salutation;
19///
20/// let person = Person {
21///     salutation: Some(Salutation::Mr),
22///     first_name: Some("Max".to_string()),
23///     last_name: Some("Mustermann".to_string()),
24///     ..Default::default()
25/// };
26/// ```
27#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
28#[cfg_attr(feature = "json-schema", derive(schemars::JsonSchema))]
29#[cfg_attr(feature = "json-schema", schemars(rename = "Person"))]
30#[serde(rename_all = "camelCase")]
31pub struct Person {
32    /// BO4E metadata
33    #[serde(flatten)]
34    pub meta: Bo4eMeta,
35
36    /// Salutation (Anrede)
37    #[serde(skip_serializing_if = "Option::is_none")]
38    #[cfg_attr(feature = "json-schema", schemars(rename = "anrede"))]
39    pub salutation: Option<Salutation>,
40
41    /// Title (Titel)
42    #[serde(skip_serializing_if = "Option::is_none")]
43    #[cfg_attr(feature = "json-schema", schemars(rename = "titel"))]
44    pub title: Option<Title>,
45
46    /// First name (Vorname)
47    #[serde(skip_serializing_if = "Option::is_none")]
48    #[cfg_attr(feature = "json-schema", schemars(rename = "vorname"))]
49    pub first_name: Option<String>,
50
51    /// Last name (Nachname)
52    #[serde(skip_serializing_if = "Option::is_none")]
53    #[cfg_attr(feature = "json-schema", schemars(rename = "nachname"))]
54    pub last_name: Option<String>,
55
56    /// Name suffix (Namenszusatz)
57    #[serde(skip_serializing_if = "Option::is_none")]
58    #[cfg_attr(feature = "json-schema", schemars(rename = "namenszusatz"))]
59    pub name_suffix: Option<String>,
60
61    /// Name prefix (Namenspraefix)
62    #[serde(skip_serializing_if = "Option::is_none")]
63    #[cfg_attr(feature = "json-schema", schemars(rename = "namenspraefix"))]
64    pub name_prefix: Option<String>,
65
66    /// Company name if representing a company (Firma)
67    #[serde(skip_serializing_if = "Option::is_none")]
68    #[cfg_attr(feature = "json-schema", schemars(rename = "firma"))]
69    pub company_name: Option<String>,
70
71    /// Birth date (Geburtsdatum)
72    #[serde(skip_serializing_if = "Option::is_none")]
73    #[cfg_attr(feature = "json-schema", schemars(rename = "geburtsdatum"))]
74    pub birth_date: Option<NaiveDate>,
75
76    /// Primary address (Adresse)
77    #[serde(skip_serializing_if = "Option::is_none")]
78    #[cfg_attr(feature = "json-schema", schemars(rename = "adresse"))]
79    pub address: Option<Address>,
80
81    /// Contact methods (Kontaktwege)
82    #[serde(default, skip_serializing_if = "Vec::is_empty")]
83    #[cfg_attr(feature = "json-schema", schemars(rename = "kontaktwege"))]
84    pub contact_methods: Vec<ContactMethod>,
85}
86
87impl Bo4eObject for Person {
88    fn type_name_german() -> &'static str {
89        "Person"
90    }
91
92    fn type_name_english() -> &'static str {
93        "Person"
94    }
95
96    fn meta(&self) -> &Bo4eMeta {
97        &self.meta
98    }
99
100    fn meta_mut(&mut self) -> &mut Bo4eMeta {
101        &mut self.meta
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn test_person_creation() {
111        let person = Person {
112            salutation: Some(Salutation::Mr),
113            first_name: Some("Max".to_string()),
114            last_name: Some("Mustermann".to_string()),
115            ..Default::default()
116        };
117
118        assert_eq!(person.first_name, Some("Max".to_string()));
119        assert_eq!(person.last_name, Some("Mustermann".to_string()));
120    }
121
122    #[test]
123    fn test_person_with_title() {
124        let person = Person {
125            salutation: Some(Salutation::Mr),
126            title: Some(Title::Dr),
127            first_name: Some("Hans".to_string()),
128            last_name: Some("Mueller".to_string()),
129            ..Default::default()
130        };
131
132        assert_eq!(person.title, Some(Title::Dr));
133    }
134
135    #[test]
136    fn test_serialize() {
137        let person = Person {
138            meta: Bo4eMeta::with_type("Person"),
139            salutation: Some(Salutation::Ms),
140            first_name: Some("Erika".to_string()),
141            last_name: Some("Musterfrau".to_string()),
142            ..Default::default()
143        };
144
145        let json = serde_json::to_string(&person).unwrap();
146        assert!(json.contains(r#""firstName":"Erika""#));
147        assert!(json.contains(r#""lastName":"Musterfrau""#));
148    }
149
150    #[test]
151    fn test_roundtrip() {
152        let person = Person {
153            meta: Bo4eMeta::with_type("Person"),
154            salutation: Some(Salutation::Mr),
155            title: Some(Title::ProfDr),
156            first_name: Some("Klaus".to_string()),
157            last_name: Some("Schmidt".to_string()),
158            name_suffix: Some("Jr.".to_string()),
159            company_name: Some("Schmidt GmbH".to_string()),
160            birth_date: Some(NaiveDate::from_ymd_opt(1970, 5, 15).unwrap()),
161            ..Default::default()
162        };
163
164        let json = serde_json::to_string(&person).unwrap();
165        let parsed: Person = serde_json::from_str(&json).unwrap();
166        assert_eq!(person, parsed);
167    }
168
169    #[test]
170    fn test_bo4e_object_impl() {
171        assert_eq!(Person::type_name_german(), "Person");
172        assert_eq!(Person::type_name_english(), "Person");
173    }
174}