1use chrono::{DateTime, Utc};
2use schemars::JsonSchema;
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6use crate::{
7 Element, Vector3,
8 date_time::utc_now,
9 geometry::zero_origin,
10 validate::{Validate, Validator},
11};
12
13#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]
18pub struct Project {
19 #[serde(default, skip_serializing_if = "String::is_empty")]
21 pub name: String,
22 #[serde(default, skip_serializing_if = "String::is_empty")]
24 pub description: String,
25 #[serde(default, skip_serializing_if = "String::is_empty")]
30 pub coordinate_reference_system: String,
31 #[serde(default, skip_serializing_if = "String::is_empty")]
36 pub units: String,
37 #[serde(default, skip_serializing_if = "zero_origin")]
42 pub origin: Vector3,
43 #[serde(default, skip_serializing_if = "String::is_empty")]
45 pub author: String,
46 #[serde(default, skip_serializing_if = "String::is_empty")]
48 pub application: String,
49 pub date: DateTime<Utc>,
51 #[serde(default, skip_serializing_if = "serde_json::Map::is_empty")]
57 pub metadata: serde_json::Map<String, Value>,
58 #[serde(default)]
60 pub elements: Vec<Element>,
61}
62
63impl Project {
64 pub fn new(name: impl Into<String>) -> Self {
66 Self {
67 name: name.into(),
68 ..Default::default()
69 }
70 }
71}
72
73impl Default for Project {
74 fn default() -> Self {
75 Self {
76 name: Default::default(),
77 description: Default::default(),
78 coordinate_reference_system: Default::default(),
79 units: Default::default(),
80 origin: Default::default(),
81 author: Default::default(),
82 application: Default::default(),
83 date: utc_now(),
84 metadata: Default::default(),
85 elements: Default::default(),
86 }
87 }
88}
89
90impl Validate for Project {
91 fn validate_inner(&mut self, val: &mut Validator) {
92 val.enter("Project")
93 .name(&self.name)
94 .finite_seq(self.origin, "origin")
95 .objs(&mut self.elements)
96 .unique(
97 self.elements.iter().map(|e| &e.name),
98 "elements[..]::name",
99 false,
100 );
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use std::str::FromStr;
107
108 use serde_json::Value;
109
110 use super::*;
111
112 #[test]
113 fn serde_empty_project() {
114 let mut p = Project::new("Test");
115 p.name = "Foo".to_owned();
116 p.units = "meters".to_owned();
117 p.origin = [1e6, 0.0, 0.0];
118 p.date = chrono::DateTime::from_str("2022-10-31T09:00:00.594Z").unwrap();
119 p.metadata.insert("other".to_owned(), Value::Bool(true));
120 let s = serde_json::to_string(&p).unwrap();
121 let q = serde_json::from_str(&s).unwrap();
122 assert_eq!(p, q);
123 assert_eq!(
124 s,
125 concat!(
126 r#"{"name":"Foo","units":"meters","origin":[1000000.0,0.0,0.0],"#,
127 r#""date":"2022-10-31T09:00:00.594Z","metadata":{"other":true},"#,
128 r#""elements":[]}"#
129 )
130 );
131 }
132}