underhill_config/schema/
mod.rs1use self::v1::NAMESPACE_BASE;
9use self::v1::NAMESPACE_NETWORK_ACCELERATION;
10use self::v1::NAMESPACE_NETWORK_DEVICE;
11use crate::Vtl2SettingsErrorInfoVec;
12use crate::errors::ParseErrors;
13use crate::errors::ParseErrorsBase;
14use crate::errors::ParseResultExt;
15use crate::errors::ParsingStopped;
16use thiserror::Error;
17use vtl2_settings_proto::*;
18
19mod v1;
20
21#[derive(Debug, Error)]
22pub enum ParseError {
23 #[error("json parsing failed")]
24 Json(#[source] serde_json::Error),
25 #[error("protobuf parsing failed")]
26 Protobuf(#[source] prost::DecodeError),
27 #[error("validation failed")]
28 Validation(#[source] Vtl2SettingsErrorInfoVec),
29}
30
31enum ParseErrorInner {
32 Parse(ParseError),
33 Validation(ParsingStopped),
34}
35
36impl From<ParseError> for ParseErrorInner {
37 fn from(value: ParseError) -> Self {
38 ParseErrorInner::Parse(value)
39 }
40}
41
42impl From<ParsingStopped> for ParseErrorInner {
43 fn from(value: ParsingStopped) -> Self {
44 ParseErrorInner::Validation(value)
45 }
46}
47
48impl crate::Vtl2Settings {
49 pub fn read_from(
51 data: &[u8],
52 old_settings: crate::Vtl2Settings,
53 ) -> Result<crate::Vtl2Settings, ParseError> {
54 let mut base = ParseErrorsBase::new();
55 let mut errors = base.root();
56 match Self::read_from_inner(data, old_settings, &mut errors) {
57 Ok(v) => {
58 base.result().map_err(ParseError::Validation)?;
59 Ok(v)
60 }
61 Err(ParseErrorInner::Parse(err)) => Err(err),
62 Err(ParseErrorInner::Validation(err)) => {
63 Err::<(), _>(err).collect_error(&mut errors);
64 Err(ParseError::Validation(base.result().unwrap_err()))
65 }
66 }
67 }
68
69 fn read_from_inner(
70 data: &[u8],
71 old_settings: crate::Vtl2Settings,
72 errors: &mut ParseErrors<'_>,
73 ) -> Result<crate::Vtl2Settings, ParseErrorInner> {
74 let mut old_settings = old_settings;
75
76 let decoded: Vtl2Settings = Self::read(data)?;
77
78 let mut has_base: bool = decoded.fixed.is_some() || decoded.dynamic.is_some();
79
80 if has_base {
82 v1::validate_version(decoded.version, errors)?;
83 }
84
85 let mut fixed = decoded.fixed.unwrap_or_default().parse(errors)?;
86 let mut dynamic = decoded.dynamic.unwrap_or_default().parse(errors)?;
87
88 let mut nic_devices: Option<Vec<crate::NicDevice>> = None;
89 let mut nic_acceleration: Option<Vec<crate::NicDevice>> = None;
90
91 for chunk in &decoded.namespace_settings {
92 if chunk.settings.is_empty() {
93 errors.push(v1::Error::EmptyNamespaceChunk(chunk.namespace.as_ref()));
94 }
95 match chunk.namespace.as_str() {
96 NAMESPACE_BASE => {
97 has_base = true;
98 let base: Vtl2SettingsBase = Self::read(&chunk.settings)?;
99 v1::validate_version(base.version, errors)?;
100 fixed = base.fixed.unwrap_or_default().parse(errors)?;
101 dynamic = base.dynamic.unwrap_or_default().parse(errors)?;
102 }
103 NAMESPACE_NETWORK_DEVICE => {
104 let settings: Vtl2SettingsNetworkDevice = Self::read(&chunk.settings)?;
105 nic_devices = Some(
106 settings
107 .nic_devices
108 .iter()
109 .flat_map(|v| v.parse(errors).collect_error(errors))
110 .collect(),
111 );
112 }
113 NAMESPACE_NETWORK_ACCELERATION => {
114 let settings: Vtl2SettingsNetworkAcceleration = Self::read(&chunk.settings)?;
115 nic_acceleration = Some(
116 settings
117 .nic_acceleration
118 .iter()
119 .flat_map(|v| v.parse(errors).collect_error(errors))
120 .collect(),
121 );
122 }
123 _ => {
124 errors.push(v1::Error::UnsupportedSchemaNamespace(
125 chunk.namespace.as_ref(),
126 ));
127 }
128 }
129 }
130
131 if has_base {
133 old_settings.fixed = fixed;
134 let old_nic_devices = std::mem::take(&mut old_settings.dynamic.nic_devices);
135 old_settings.dynamic = dynamic;
136 if old_settings.dynamic.nic_devices.is_empty() && nic_devices.is_none() {
140 old_settings.dynamic.nic_devices = old_nic_devices;
141 }
142 }
143
144 if let Some(nic_devices) = nic_devices {
146 old_settings.dynamic.nic_devices = nic_devices;
147 }
148
149 if let Some(nic_acceleration) = nic_acceleration {
151 for acc in nic_acceleration.iter() {
154 for nic in old_settings.dynamic.nic_devices.iter_mut() {
155 if nic.instance_id == acc.instance_id {
156 nic.subordinate_instance_id = acc.subordinate_instance_id;
157 break;
158 }
159 }
160 }
161 }
162
163 Ok(old_settings)
164 }
165
166 fn read<'a, T>(data: &'a [u8]) -> Result<T, ParseError>
167 where
168 T: Default,
169 T: prost::Message,
170 T: serde::Deserialize<'a>,
171 {
172 let idx = data.iter().position(|&b| !b.is_ascii_whitespace());
176 let is_json = match idx {
177 Some(idx) => data[idx] == b'{',
178 None => false,
179 };
180 let decoded: T = if is_json {
181 match serde_json::from_slice(data).map_err(ParseError::Json) {
188 Ok(json) => json,
189 Err(json_parse_err) => {
190 match prost::Message::decode(data).map_err(ParseError::Protobuf) {
191 Ok(protobuf) => protobuf,
192 Err(_) => return Err(json_parse_err),
193 }
194 }
195 }
196 } else {
197 prost::Message::decode(data).map_err(ParseError::Protobuf)?
198 };
199
200 Ok(decoded)
201 }
202}
203
204impl Default for crate::Vtl2SettingsFixed {
205 fn default() -> Self {
206 Vtl2SettingsFixed::default()
207 .parse(&mut ParseErrorsBase::new().root())
208 .unwrap()
209 }
210}
211
212impl Default for crate::Vtl2SettingsDynamic {
213 fn default() -> Self {
214 Vtl2SettingsDynamic::default()
215 .parse(&mut ParseErrorsBase::new().root())
216 .unwrap()
217 }
218}
219
220pub(crate) trait ParseSchema<T>: Sized {
222 fn parse_schema(&self, errors: &mut ParseErrors<'_>) -> Result<T, ParsingStopped>;
228}
229
230pub(crate) trait ParseSchemaExt {
235 fn parse<T>(&self, errors: &mut ParseErrors<'_>) -> Result<T, ParsingStopped>
237 where
238 Self: ParseSchema<T>;
239}
240
241impl<T> ParseSchemaExt for T {
242 fn parse<U>(&self, errors: &mut ParseErrors<'_>) -> Result<U, ParsingStopped>
243 where
244 T: ParseSchema<U>,
245 {
246 self.parse_schema(errors)
247 }
248}
249
250#[cfg(test)]
251mod test {
252 use super::*;
253 use crate::Vtl2SettingsErrorCode;
254 use crate::Vtl2SettingsErrorInfo;
255 use guid::Guid;
256 use prost::Message;
257
258 #[test]
259 fn smoke_test_sample() {
260 let json = b"{ \"version\": \"V1\" }";
262 crate::Vtl2Settings::read_from(json, Default::default()).unwrap();
263 }
264
265 #[test]
266 fn smoke_test_namespace() {
267 let json = include_bytes!("vtl2s_test_namespace.json");
268 crate::Vtl2Settings::read_from(json, Default::default()).unwrap();
269 }
270
271 #[test]
272 fn smoke_test_namespace_mix_protobuf_json() {
273 let json = include_bytes!("vtl2s_test_namespace.json");
274 let settings: Vtl2Settings = crate::Vtl2Settings::read(json).unwrap();
275 let base_json: &[u8] = &settings.namespace_settings[0].settings;
277 assert_eq!(base_json, include_bytes!("vtl2s_test_json.json"));
278 let mut buf = Vec::new();
279 settings.encode(&mut buf).unwrap();
280 crate::Vtl2Settings::read_from(&buf, Default::default()).unwrap();
281 }
282
283 #[test]
284 fn smoke_test_compat() {
285 crate::Vtl2Settings::read_from(
286 include_bytes!("vtl2s_test_compat.json"),
287 Default::default(),
288 )
289 .unwrap();
290 }
291
292 #[test]
293 fn validation_test_max_storage_controllers() {
294 crate::Vtl2Settings::read_from(
295 include_bytes!("vtl2s_test_max_storage_controllers.json"),
296 Default::default(),
297 )
298 .unwrap();
299 }
300
301 fn stable_error_json(err: &Vtl2SettingsErrorInfo) -> String {
302 let mut value = serde_json::to_value(err).unwrap();
303 let obj = value.as_object_mut().unwrap();
304 obj.remove("file_name").unwrap();
305 obj.remove("line").unwrap();
306 serde_json::to_string(&value).unwrap()
307 }
308
309 #[test]
310 fn validation_test_storage_controllers_exceeds_limits() {
311 let err = crate::Vtl2Settings::read_from(
312 include_bytes!("vtl2s_test_storage_controllers_exceeds_limits.json"),
313 Default::default(),
314 )
315 .unwrap_err();
316
317 let ParseError::Validation(err) = err else {
318 panic!("wrong error {err:?}")
319 };
320 let [err] = err.errors.try_into().unwrap();
321 assert_eq!(
322 err.code(),
323 Vtl2SettingsErrorCode::StorageScsiControllerExceedsMaxLimits
324 );
325 let expected = r#"{"error_id":"Configuration.StorageScsiControllerExceedsMaxLimits","message":"exceeded 4 max SCSI controllers, instance ID: 0bf355d5-0cae-411e-9662-86c3035556ae"}"#;
326 assert_eq!(stable_error_json(&err).as_str(), expected);
327 }
328
329 #[test]
330 fn namespace_test_nic_namespaces() {
331 let settings = crate::Vtl2Settings::read_from(
362 include_bytes!("vtl2s_test_nic_namespaces.json"),
363 Default::default(),
364 )
365 .unwrap();
366
367 assert_eq!(3, settings.dynamic.nic_devices.len());
368
369 let nic0 = settings
370 .dynamic
371 .nic_devices
372 .iter()
373 .find(|nic| {
374 nic.instance_id
375 == "9e14fd10-19cb-4da5-b667-e8e38a436cb8"
376 .parse::<Guid>()
377 .unwrap()
378 })
379 .unwrap();
380 assert_eq!(true, nic0.subordinate_instance_id.is_none());
381
382 let nic1 = settings
383 .dynamic
384 .nic_devices
385 .iter()
386 .find(|nic| {
387 nic.instance_id
388 == "9e14fd11-19cb-4da5-b667-e8e38a436cb8"
389 .parse::<Guid>()
390 .unwrap()
391 })
392 .unwrap();
393 assert_eq!(
394 "12345678-19cb-4da5-b667-e8e38a436cb8"
395 .parse::<Guid>()
396 .unwrap(),
397 nic1.subordinate_instance_id.unwrap()
398 );
399
400 let nic2 = settings
401 .dynamic
402 .nic_devices
403 .iter()
404 .find(|nic| {
405 nic.instance_id
406 == "9e14fd12-19cb-4da5-b667-e8e38a436cb8"
407 .parse::<Guid>()
408 .unwrap()
409 })
410 .unwrap();
411 assert_eq!(true, nic2.subordinate_instance_id.is_none());
412 }
413
414 #[test]
415 fn namespace_test_empty_nic_devices_ignored_json() {
416 let old_settings = crate::Vtl2Settings::read_from(
417 include_bytes!("vtl2s_test_nic_namespaces.json"),
418 Default::default(),
419 )
420 .unwrap();
421 assert_eq!(3, old_settings.dynamic.nic_devices.len());
422
423 let no_nic_settings = crate::Vtl2Settings::read_from(
424 include_bytes!("vtl2s_test_json_no_nic.json"),
425 Default::default(),
426 )
427 .unwrap();
428 assert_eq!(0, no_nic_settings.dynamic.nic_devices.len());
429
430 let settings = crate::Vtl2Settings::read_from(
431 include_bytes!("vtl2s_test_json_no_nic.json"),
432 old_settings.clone(),
433 )
434 .unwrap();
435 assert_eq!(3, settings.dynamic.nic_devices.len());
436 }
437
438 #[test]
439 fn namespace_test_adding_nic_devices_json() {
440 let old_settings = crate::Vtl2Settings::read_from(
441 include_bytes!("vtl2s_test_json_no_nic.json"),
442 Default::default(),
443 )
444 .unwrap();
445 assert_eq!(0, old_settings.dynamic.nic_devices.len());
446
447 let nic_settings = crate::Vtl2Settings::read_from(
448 include_bytes!("vtl2s_test_nic_namespaces.json"),
449 Default::default(),
450 )
451 .unwrap();
452 assert_eq!(3, nic_settings.dynamic.nic_devices.len());
453
454 let settings = crate::Vtl2Settings::read_from(
455 include_bytes!("vtl2s_test_nic_namespaces.json"),
456 old_settings.clone(),
457 )
458 .unwrap();
459 assert_eq!(3, settings.dynamic.nic_devices.len());
460 }
461
462 #[test]
463 fn namespace_test_adding_nic_devices_protobuf() {
464 let json = include_bytes!("vtl2s_test_nic_namespaces.json");
465 let settings: Vtl2Settings = crate::Vtl2Settings::read(json).unwrap();
466 assert_eq!("NetworkDevice", settings.namespace_settings[0].namespace);
467 let mut buf = Vec::new();
468 settings.encode(&mut buf).unwrap();
469 let settings = crate::Vtl2Settings::read_from(&buf, Default::default()).unwrap();
470 assert_eq!(3, settings.dynamic.nic_devices.len());
471 }
472
473 #[test]
474 fn namespace_test_empty_nic_devices_protobuf() {
475 let old_settings = crate::Vtl2Settings::read_from(
476 include_bytes!("vtl2s_test_nic_namespaces.json"),
477 Default::default(),
478 )
479 .unwrap();
480 assert_eq!(3, old_settings.dynamic.nic_devices.len());
481
482 let json = include_bytes!("vtl2s_test_json_no_nic.json");
484 let empty_json_settings: Vtl2Settings = crate::Vtl2Settings::read(json).unwrap();
485 let mut empty_protobuf = Vec::new();
486 empty_json_settings.encode(&mut empty_protobuf).unwrap();
487
488 let mut empty_vtl2_settings: Vtl2Settings =
490 crate::Vtl2Settings::read(include_bytes!("vtl2s_test_nic_namespaces.json")).unwrap();
491 empty_vtl2_settings.namespace_settings.pop(); empty_vtl2_settings.namespace_settings[0].settings = empty_protobuf; let mut buf = Vec::new();
495 empty_vtl2_settings.encode(&mut buf).unwrap();
496 let settings = crate::Vtl2Settings::read_from(&buf, old_settings).unwrap();
497 assert_eq!(0, settings.dynamic.nic_devices.len());
498 }
499}