sdsforge_core/schema/
mod.rs1mod generated;
2
3pub use generated::*;
4
5pub mod serde_flex {
7 use serde::de::{self, SeqAccess, Visitor};
8 use serde::Deserializer;
9 use std::fmt;
10
11 pub fn flex_string_opt<'de, D>(d: D) -> Result<Option<String>, D::Error>
18 where
19 D: Deserializer<'de>,
20 {
21 struct FlexStringVisitor;
22
23 impl<'de> Visitor<'de> for FlexStringVisitor {
24 type Value = Option<String>;
25
26 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
27 write!(f, "a string, an array of strings, or null")
28 }
29
30 fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
31 if v.is_empty() { Ok(None) } else { Ok(Some(v.to_string())) }
32 }
33
34 fn visit_string<E: de::Error>(self, v: String) -> Result<Self::Value, E> {
35 if v.is_empty() { Ok(None) } else { Ok(Some(v)) }
36 }
37
38 fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
39 let mut parts = Vec::new();
40 while let Some(s) = seq.next_element::<String>()? {
41 if !s.is_empty() {
42 parts.push(s);
43 }
44 }
45 if parts.is_empty() { Ok(None) } else { Ok(Some(parts.join("\n"))) }
46 }
47
48 fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> { Ok(None) }
49 fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> { Ok(None) }
50 }
51
52 d.deserialize_any(FlexStringVisitor)
53 }
54
55 pub fn flex_vec_string_opt<'de, D>(d: D) -> Result<Option<Vec<String>>, D::Error>
60 where
61 D: Deserializer<'de>,
62 {
63 struct FlexVecVisitor;
64
65 impl<'de> Visitor<'de> for FlexVecVisitor {
66 type Value = Option<Vec<String>>;
67
68 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
69 write!(f, "a string, an array of strings, or null")
70 }
71
72 fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
73 if v.is_empty() {
74 Ok(None)
75 } else {
76 Ok(Some(vec![v.to_string()]))
77 }
78 }
79
80 fn visit_string<E: de::Error>(self, v: String) -> Result<Self::Value, E> {
81 if v.is_empty() {
82 Ok(None)
83 } else {
84 Ok(Some(vec![v]))
85 }
86 }
87
88 fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
89 let mut items = Vec::new();
90 while let Some(s) = seq.next_element::<String>()? {
91 if !s.is_empty() {
92 items.push(s);
93 }
94 }
95 if items.is_empty() {
96 Ok(None)
97 } else {
98 Ok(Some(items))
99 }
100 }
101
102 fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
103 Ok(None)
104 }
105
106 fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
107 Ok(None)
108 }
109 }
110
111 d.deserialize_any(FlexVecVisitor)
112 }
113
114 pub fn flex_vec_string_or_obj_opt<'de, D>(d: D) -> Result<Option<Vec<String>>, D::Error>
123 where
124 D: Deserializer<'de>,
125 {
126 use serde::Deserialize;
127
128 let v = Option::<serde_json::Value>::deserialize(d)
129 .map_err(de::Error::custom)?;
130
131 fn obj_to_string(obj: serde_json::Value) -> String {
132 if let serde_json::Value::Object(ref map) = obj {
133 if let Some(ai) = map.get("AdditionalInfo") {
135 if let Some(ft) = ai.get("FullText") {
136 let parts: Vec<String> = match ft {
137 serde_json::Value::Array(arr) => arr
138 .iter()
139 .filter_map(|v| v.as_str().map(str::to_string))
140 .collect(),
141 serde_json::Value::String(s) => vec![s.clone()],
142 _ => vec![],
143 };
144 if !parts.is_empty() {
145 return parts.join(" / ");
146 }
147 }
148 }
149 if let Some(serde_json::Value::String(name)) = map.get("ItemName") {
151 if !name.is_empty() {
152 return name.clone();
153 }
154 }
155 }
156 serde_json::to_string(&obj).unwrap_or_default()
158 }
159
160 match v {
161 None => Ok(None),
162 Some(serde_json::Value::String(s)) => {
163 if s.is_empty() { Ok(None) } else { Ok(Some(vec![s])) }
164 }
165 Some(serde_json::Value::Array(arr)) => {
166 let items: Vec<String> = arr
167 .into_iter()
168 .filter_map(|elem| match elem {
169 serde_json::Value::String(s) if !s.is_empty() => Some(s),
170 serde_json::Value::Object(_) => {
171 let s = obj_to_string(elem);
172 if s.is_empty() { None } else { Some(s) }
173 }
174 _ => None,
175 })
176 .collect();
177 if items.is_empty() { Ok(None) } else { Ok(Some(items)) }
178 }
179 _ => Ok(None),
180 }
181 }
182
183 macro_rules! flex_single_or_vec_opt {
188 ($fn_name:ident, $T:ty) => {
189 pub fn $fn_name<'de, D>(d: D) -> Result<Option<Vec<$T>>, D::Error>
190 where
191 D: serde::Deserializer<'de>,
192 {
193 use serde::Deserialize;
194 let v = Option::<serde_json::Value>::deserialize(d)
195 .map_err(serde::de::Error::custom)?;
196 match v {
197 None => Ok(None),
198 Some(serde_json::Value::Array(arr)) => {
199 let items: Result<Vec<$T>, _> = arr
200 .into_iter()
201 .map(|val| {
202 serde_json::from_value(val).map_err(serde::de::Error::custom)
203 })
204 .collect();
205 Ok(Some(items?))
206 }
207 Some(obj) => {
208 let item = serde_json::from_value::<$T>(obj)
209 .map_err(serde::de::Error::custom)?;
210 Ok(Some(vec![item]))
211 }
212 }
213 }
214 };
215 }
216
217 use crate::schema::{
218 HandlingAndStorageStorageConditionsForSafeStorage,
219 HazardIdentificationClassificationHealthEffectSpecificTargetOrganRE,
220 HazardIdentificationClassificationHealthEffectSpecificTargetOrganSE,
221 };
222
223 flex_single_or_vec_opt!(
224 flex_stoase_opt,
225 HazardIdentificationClassificationHealthEffectSpecificTargetOrganSE
226 );
227 flex_single_or_vec_opt!(
228 flex_stoare_opt,
229 HazardIdentificationClassificationHealthEffectSpecificTargetOrganRE
230 );
231
232 pub fn flex_storage_conditions_opt<'de, D>(
239 d: D,
240 ) -> Result<Option<HandlingAndStorageStorageConditionsForSafeStorage>, D::Error>
241 where
242 D: serde::Deserializer<'de>,
243 {
244 use serde::Deserialize;
245 let v = Option::<serde_json::Value>::deserialize(d)
246 .map_err(serde::de::Error::custom)?;
247 match v {
248 None => Ok(None),
249 Some(serde_json::Value::String(s)) if s.is_empty() => Ok(None),
250 Some(serde_json::Value::String(s)) => {
251 Ok(Some(HandlingAndStorageStorageConditionsForSafeStorage {
252 technical_measures_and_storage_conditions: Some(s),
253 ..Default::default()
254 }))
255 }
256 Some(obj) => serde_json::from_value(obj)
257 .map_err(serde::de::Error::custom),
258 }
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265
266 #[test]
270 fn fire_fighting_full_text_accepts_array() {
271 let json = r#"{
272 "FireFightingMeasures": {
273 "FireAndExplosionHazards": {
274 "FullText": ["有害燃焼副産物:ケイ素酸化物", "炭素酸化物"]
275 },
276 "FireFightingProcedures": {
277 "FullText": "現場の状況に応じて適切な消火手段を用いる。"
278 }
279 }
280 }"#;
281 let sds: SdsRoot = serde_json::from_str(json)
282 .expect("FireFightingMeasures with array FullText should deserialize");
283 let ffm = sds.fire_fighting_measures.as_ref().expect("FireFightingMeasures must be Some");
284 let hazards = ffm.fire_and_explosion_hazards.as_ref().expect("FireAndExplosionHazards must be Some");
285 let full_text = hazards.full_text.as_deref().expect("FullText must be Some");
286 assert!(full_text.contains("有害燃焼副産物"), "FullText content lost: {full_text}");
288 assert!(full_text.contains("炭素酸化物"), "second element lost: {full_text}");
289 }
290
291 #[test]
295 fn stability_reactivity_substance_accepts_array() {
296 let json = r#"{
297 "StabilityReactivity": {
298 "HazardousDecompositionProducts": {
299 "Substance": ["二酸化炭素(CO2)", "アンモニア", "窒素酸化物(NOx)"]
300 }
301 }
302 }"#;
303 let sds: SdsRoot = serde_json::from_str(json)
304 .expect("StabilityReactivity with array Substance should deserialize");
305 let sr = sds.stability_reactivity.as_ref().expect("StabilityReactivity must be Some");
306 let hdp = sr.hazardous_decomposition_products.as_ref().expect("HazardousDecompositionProducts must be Some");
307 let substance = hdp.substance.as_deref().expect("Substance must be Some");
308 assert!(substance.contains("二酸化炭素"), "first element lost: {substance}");
309 assert!(substance.contains("アンモニア"), "second element lost: {substance}");
310 assert!(substance.contains("窒素酸化物"), "third element lost: {substance}");
311 }
312
313 #[test]
314 fn sds_root_round_trip_empty() {
315 let sds = SdsRoot::default();
316 let json = serde_json::to_string(&sds).unwrap();
317 assert_eq!(json, "{}");
318 let sds2: SdsRoot = serde_json::from_str("{}").unwrap();
319 let json2 = serde_json::to_string(&sds2).unwrap();
320 assert_eq!(json2, "{}");
321 }
322
323 #[test]
324 fn sds_root_round_trip_partial() {
325 let json = r#"{"Datasheet":{"IssueDate":"2024-03-31","SDS-SchemaVersionNo":"1.0"}}"#;
326 let sds: SdsRoot = serde_json::from_str(json).unwrap();
327 assert_eq!(sds.datasheet.as_ref().unwrap().issue_date.as_deref(), Some("2024-03-31"));
328 let out = serde_json::to_string(&sds).unwrap();
329 let v1: serde_json::Value = serde_json::from_str(json).unwrap();
330 let v2: serde_json::Value = serde_json::from_str(&out).unwrap();
331 assert_eq!(v1, v2);
332 }
333}