]> git.proxmox.com Git - proxmox.git/blob - proxmox-schema/src/schema.rs
257cedc8954a457854296b0b133ca8bbbe77fcee
[proxmox.git] / proxmox-schema / src / schema.rs
1 //! Data types to decscribe data types.
2 //!
3 //! This is loosly based on JSON Schema, but uses static rust data types. This way we can build
4 //! completely static API definitions that can be included within the programs read-only text
5 //! segment.
6
7 use std::collections::HashSet;
8 use std::fmt;
9
10 use anyhow::{bail, format_err, Error};
11 use serde_json::{json, Value};
12
13 use crate::ConstRegexPattern;
14
15 /// Error type for schema validation
16 ///
17 /// The validation functions may produce several error message,
18 /// i.e. when validation objects, it can produce one message for each
19 /// erroneous object property.
20 #[derive(Default, Debug)]
21 pub struct ParameterError {
22 error_list: Vec<(String, Error)>,
23 }
24
25 /// Like anyhow's `format_err` but producing a `ParameterError`.
26 #[macro_export]
27 macro_rules! param_format_err {
28 ($field:expr, $err:expr) => {
29 $crate::ParameterError::from(($field, $err))
30 };
31
32 ($field:expr, $($msg:tt)+) => {
33 $crate::ParameterError::from(($field, ::anyhow::format_err!($($msg)+)))
34 };
35 }
36
37 /// Like anyhow's `bail` but enclosing a `ParameterError`, so
38 /// a `downcast` can extract it later. This is useful for
39 /// API calls that need to do parameter checking manually.
40 #[macro_export]
41 macro_rules! param_bail {
42 ($field:expr, $err:expr) => {{
43 return Err($crate::param_format_err!($field, $err).into());
44 }};
45
46 ($field:expr, $($msg:tt)+) => {{
47 return Err($crate::param_format_err!($field, $($msg)+).into());
48 }};
49 }
50
51 impl std::error::Error for ParameterError {}
52
53 impl ParameterError {
54 pub fn new() -> Self {
55 Self {
56 error_list: Vec::new(),
57 }
58 }
59
60 pub fn push(&mut self, name: String, value: Error) {
61 self.error_list.push((name, value));
62 }
63
64 pub fn len(&self) -> usize {
65 self.error_list.len()
66 }
67
68 pub fn errors(&self) -> &[(String, Error)] {
69 &self.error_list
70 }
71
72 pub fn into_inner(self) -> Vec<(String, Error)> {
73 self.error_list
74 }
75
76 pub fn is_empty(&self) -> bool {
77 self.len() == 0
78 }
79
80 pub fn add_errors(&mut self, prefix: &str, err: Error) {
81 match err.downcast::<ParameterError>() {
82 Ok(param_err) => {
83 self.extend(
84 param_err
85 .into_iter()
86 .map(|(key, err)| (format!("{}/{}", prefix, key), err)),
87 );
88 }
89 Err(err) => self.push(prefix.to_string(), err),
90 }
91 }
92
93 pub(crate) fn from_list(error_list: Vec<(String, Error)>) -> Self {
94 Self { error_list }
95 }
96 }
97
98 impl fmt::Display for ParameterError {
99 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
100 use std::fmt::Write;
101
102 let mut msg = String::new();
103
104 if !self.is_empty() {
105 if self.len() == 1 {
106 msg.push_str("parameter verification failed - ");
107 let _ = write!(msg, "'{}': {}", self.error_list[0].0, self.error_list[0].1);
108 } else {
109 msg.push_str("parameter verification failed:\n");
110 for (name, err) in self.error_list.iter() {
111 let _ = writeln!(msg, "- '{}': {}", name, err);
112 }
113 }
114 }
115
116 write!(f, "{}", msg.trim())
117 }
118 }
119
120 impl From<(String, Error)> for ParameterError {
121 fn from(err: (String, Error)) -> Self {
122 let mut this = Self::new();
123 this.push(err.0, err.1);
124 this
125 }
126 }
127
128 impl<'a> From<(&'a str, Error)> for ParameterError {
129 fn from(err: (&'a str, Error)) -> Self {
130 Self::from((err.0.to_string(), err.1))
131 }
132 }
133
134 impl std::iter::Extend<(String, Error)> for ParameterError {
135 fn extend<T>(&mut self, iter: T)
136 where
137 T: IntoIterator<Item = (String, Error)>,
138 {
139 self.error_list.extend(iter);
140 }
141 }
142
143 impl<'a> std::iter::Extend<(&'a str, Error)> for ParameterError {
144 fn extend<T>(&mut self, iter: T)
145 where
146 T: IntoIterator<Item = (&'a str, Error)>,
147 {
148 self.extend(iter.into_iter().map(|(s, e)| (s.to_string(), e)));
149 }
150 }
151
152 impl IntoIterator for ParameterError {
153 type Item = (String, Error);
154 type IntoIter = <Vec<(String, Error)> as IntoIterator>::IntoIter;
155
156 fn into_iter(self) -> Self::IntoIter {
157 self.into_inner().into_iter()
158 }
159 }
160
161 impl FromIterator<(String, Error)> for ParameterError {
162 fn from_iter<T>(iter: T) -> Self
163 where
164 T: IntoIterator<Item = (String, Error)>,
165 {
166 let mut this = Self::new();
167 this.extend(iter);
168 this
169 }
170 }
171
172 impl<'a> FromIterator<(&'a str, Error)> for ParameterError {
173 fn from_iter<T>(iter: T) -> Self
174 where
175 T: IntoIterator<Item = (&'a str, Error)>,
176 {
177 let mut this = Self::new();
178 this.extend(iter);
179 this
180 }
181 }
182
183 /// Data type to describe boolean values
184 #[derive(Debug)]
185 #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
186 pub struct BooleanSchema {
187 pub description: &'static str,
188 /// Optional default value.
189 pub default: Option<bool>,
190 }
191
192 impl BooleanSchema {
193 pub const fn new(description: &'static str) -> Self {
194 BooleanSchema {
195 description,
196 default: None,
197 }
198 }
199
200 pub const fn default(mut self, default: bool) -> Self {
201 self.default = Some(default);
202 self
203 }
204
205 pub const fn schema(self) -> Schema {
206 Schema::Boolean(self)
207 }
208
209 /// Verify JSON value using a `BooleanSchema`.
210 pub fn verify_json(&self, data: &Value) -> Result<(), Error> {
211 if !data.is_boolean() {
212 bail!("Expected boolean value.");
213 }
214 Ok(())
215 }
216 }
217
218 /// Data type to describe integer values.
219 #[derive(Debug)]
220 #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
221 pub struct IntegerSchema {
222 pub description: &'static str,
223 /// Optional minimum.
224 pub minimum: Option<isize>,
225 /// Optional maximum.
226 pub maximum: Option<isize>,
227 /// Optional default.
228 pub default: Option<isize>,
229 }
230
231 impl IntegerSchema {
232 pub const fn new(description: &'static str) -> Self {
233 IntegerSchema {
234 description,
235 default: None,
236 minimum: None,
237 maximum: None,
238 }
239 }
240
241 pub const fn default(mut self, default: isize) -> Self {
242 self.default = Some(default);
243 self
244 }
245
246 pub const fn minimum(mut self, minimum: isize) -> Self {
247 self.minimum = Some(minimum);
248 self
249 }
250
251 pub const fn maximum(mut self, maximium: isize) -> Self {
252 self.maximum = Some(maximium);
253 self
254 }
255
256 pub const fn schema(self) -> Schema {
257 Schema::Integer(self)
258 }
259
260 pub fn check_constraints(&self, value: isize) -> Result<(), Error> {
261 if let Some(minimum) = self.minimum {
262 if value < minimum {
263 bail!(
264 "value must have a minimum value of {} (got {})",
265 minimum,
266 value
267 );
268 }
269 }
270
271 if let Some(maximum) = self.maximum {
272 if value > maximum {
273 bail!(
274 "value must have a maximum value of {} (got {})",
275 maximum,
276 value
277 );
278 }
279 }
280
281 Ok(())
282 }
283
284 /// Verify JSON value using an `IntegerSchema`.
285 pub fn verify_json(&self, data: &Value) -> Result<(), Error> {
286 if let Some(value) = data.as_i64() {
287 self.check_constraints(value as isize)
288 } else {
289 bail!("Expected integer value.");
290 }
291 }
292 }
293
294 /// Data type to describe (JSON like) number value
295 #[derive(Debug)]
296 pub struct NumberSchema {
297 pub description: &'static str,
298 /// Optional minimum.
299 pub minimum: Option<f64>,
300 /// Optional maximum.
301 pub maximum: Option<f64>,
302 /// Optional default.
303 pub default: Option<f64>,
304 }
305
306 impl NumberSchema {
307 pub const fn new(description: &'static str) -> Self {
308 NumberSchema {
309 description,
310 default: None,
311 minimum: None,
312 maximum: None,
313 }
314 }
315
316 pub const fn default(mut self, default: f64) -> Self {
317 self.default = Some(default);
318 self
319 }
320
321 pub const fn minimum(mut self, minimum: f64) -> Self {
322 self.minimum = Some(minimum);
323 self
324 }
325
326 pub const fn maximum(mut self, maximium: f64) -> Self {
327 self.maximum = Some(maximium);
328 self
329 }
330
331 pub const fn schema(self) -> Schema {
332 Schema::Number(self)
333 }
334
335 pub fn check_constraints(&self, value: f64) -> Result<(), Error> {
336 if let Some(minimum) = self.minimum {
337 if value < minimum {
338 bail!(
339 "value must have a minimum value of {} (got {})",
340 minimum,
341 value
342 );
343 }
344 }
345
346 if let Some(maximum) = self.maximum {
347 if value > maximum {
348 bail!(
349 "value must have a maximum value of {} (got {})",
350 maximum,
351 value
352 );
353 }
354 }
355
356 Ok(())
357 }
358
359 /// Verify JSON value using an `NumberSchema`.
360 pub fn verify_json(&self, data: &Value) -> Result<(), Error> {
361 if let Some(value) = data.as_f64() {
362 self.check_constraints(value)
363 } else {
364 bail!("Expected number value.");
365 }
366 }
367 }
368
369 #[cfg(feature = "test-harness")]
370 impl Eq for NumberSchema {}
371
372 #[cfg(feature = "test-harness")]
373 impl PartialEq for NumberSchema {
374 fn eq(&self, rhs: &Self) -> bool {
375 fn f64_eq(l: Option<f64>, r: Option<f64>) -> bool {
376 match (l, r) {
377 (None, None) => true,
378 (Some(l), Some(r)) => (l - r).abs() < 0.0001,
379 _ => false,
380 }
381 }
382
383 self.description == rhs.description
384 && f64_eq(self.minimum, rhs.minimum)
385 && f64_eq(self.maximum, rhs.maximum)
386 && f64_eq(self.default, rhs.default)
387 }
388 }
389
390 /// Data type to describe string values.
391 #[derive(Debug)]
392 #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
393 pub struct StringSchema {
394 pub description: &'static str,
395 /// Optional default value.
396 pub default: Option<&'static str>,
397 /// Optional minimal length.
398 pub min_length: Option<usize>,
399 /// Optional maximal length.
400 pub max_length: Option<usize>,
401 /// Optional microformat.
402 pub format: Option<&'static ApiStringFormat>,
403 /// A text representation of the format/type (used to generate documentation).
404 pub type_text: Option<&'static str>,
405 }
406
407 impl StringSchema {
408 pub const fn new(description: &'static str) -> Self {
409 StringSchema {
410 description,
411 default: None,
412 min_length: None,
413 max_length: None,
414 format: None,
415 type_text: None,
416 }
417 }
418
419 pub const fn default(mut self, text: &'static str) -> Self {
420 self.default = Some(text);
421 self
422 }
423
424 pub const fn format(mut self, format: &'static ApiStringFormat) -> Self {
425 self.format = Some(format);
426 self
427 }
428
429 pub const fn type_text(mut self, type_text: &'static str) -> Self {
430 self.type_text = Some(type_text);
431 self
432 }
433
434 pub const fn min_length(mut self, min_length: usize) -> Self {
435 self.min_length = Some(min_length);
436 self
437 }
438
439 pub const fn max_length(mut self, max_length: usize) -> Self {
440 self.max_length = Some(max_length);
441 self
442 }
443
444 pub const fn schema(self) -> Schema {
445 Schema::String(self)
446 }
447
448 pub(crate) fn check_length(&self, length: usize) -> Result<(), Error> {
449 if let Some(min_length) = self.min_length {
450 if length < min_length {
451 bail!("value must be at least {} characters long", min_length);
452 }
453 }
454
455 if let Some(max_length) = self.max_length {
456 if length > max_length {
457 bail!("value may only be {} characters long", max_length);
458 }
459 }
460
461 Ok(())
462 }
463
464 pub fn check_constraints(&self, value: &str) -> Result<(), Error> {
465 self.check_length(value.chars().count())?;
466
467 if let Some(ref format) = self.format {
468 match format {
469 ApiStringFormat::Pattern(regex) => {
470 if !(regex.regex_obj)().is_match(value) {
471 bail!("value does not match the regex pattern");
472 }
473 }
474 ApiStringFormat::Enum(variants) => {
475 if !variants.iter().any(|e| e.value == value) {
476 bail!("value '{}' is not defined in the enumeration.", value);
477 }
478 }
479 ApiStringFormat::PropertyString(subschema) => {
480 crate::de::verify::verify(subschema, value)?;
481 }
482 ApiStringFormat::VerifyFn(verify_fn) => {
483 verify_fn(value)?;
484 }
485 }
486 }
487
488 Ok(())
489 }
490
491 /// Verify JSON value using this `StringSchema`.
492 pub fn verify_json(&self, data: &Value) -> Result<(), Error> {
493 if let Some(value) = data.as_str() {
494 self.check_constraints(value)
495 } else {
496 bail!("Expected string value.");
497 }
498 }
499
500 /// Get the [`format`](ApiStringFormat), panics if there is no format.
501 pub const fn unwrap_format(&self) -> &'static ApiStringFormat {
502 match self.format {
503 Some(v) => v,
504 None => panic!("unwrap_format on StringSchema without format"),
505 }
506 }
507 }
508
509 /// Data type to describe array of values.
510 ///
511 /// All array elements are of the same type, as defined in the `items`
512 /// schema.
513 #[derive(Debug)]
514 #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
515 pub struct ArraySchema {
516 pub description: &'static str,
517 /// Element type schema.
518 pub items: &'static Schema,
519 /// Optional minimal length.
520 pub min_length: Option<usize>,
521 /// Optional maximal length.
522 pub max_length: Option<usize>,
523 }
524
525 impl ArraySchema {
526 pub const fn new(description: &'static str, item_schema: &'static Schema) -> Self {
527 ArraySchema {
528 description,
529 items: item_schema,
530 min_length: None,
531 max_length: None,
532 }
533 }
534
535 pub const fn min_length(mut self, min_length: usize) -> Self {
536 self.min_length = Some(min_length);
537 self
538 }
539
540 pub const fn max_length(mut self, max_length: usize) -> Self {
541 self.max_length = Some(max_length);
542 self
543 }
544
545 pub const fn schema(self) -> Schema {
546 Schema::Array(self)
547 }
548
549 pub(crate) fn check_length(&self, length: usize) -> Result<(), Error> {
550 if let Some(min_length) = self.min_length {
551 if length < min_length {
552 bail!("array must contain at least {} elements", min_length);
553 }
554 }
555
556 if let Some(max_length) = self.max_length {
557 if length > max_length {
558 bail!("array may only contain {} elements", max_length);
559 }
560 }
561
562 Ok(())
563 }
564
565 /// Verify JSON value using an `ArraySchema`.
566 pub fn verify_json(&self, data: &Value) -> Result<(), Error> {
567 let list = match data {
568 Value::Array(ref list) => list,
569 Value::Object(_) => bail!("Expected array - got object."),
570 _ => bail!("Expected array - got scalar value."),
571 };
572
573 self.check_length(list.len())?;
574
575 for (i, item) in list.iter().enumerate() {
576 let result = self.items.verify_json(item);
577 if let Err(err) = result {
578 param_bail!(format!("[{}]", i), err);
579 }
580 }
581
582 Ok(())
583 }
584 }
585
586 /// Property entry in an object schema:
587 ///
588 /// - `name`: The name of the property
589 /// - `optional`: Set when the property is optional
590 /// - `schema`: Property type schema
591 pub type SchemaPropertyEntry = (&'static str, bool, &'static Schema);
592
593 /// Lookup table to Schema properties
594 ///
595 /// Stores a sorted list of `(name, optional, schema)` tuples:
596 ///
597 /// - `name`: The name of the property
598 /// - `optional`: Set when the property is optional
599 /// - `schema`: Property type schema
600 ///
601 /// **Note:** The list has to be storted by name, because we use
602 /// a binary search to find items.
603 ///
604 /// This is a workaround unless RUST can const_fn `Hash::new()`
605 pub type SchemaPropertyMap = &'static [SchemaPropertyEntry];
606
607 /// Data type to describe objects (maps).
608 #[derive(Debug)]
609 #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
610 pub struct ObjectSchema {
611 pub description: &'static str,
612 /// If set, allow additional properties which are not defined in
613 /// the schema.
614 pub additional_properties: bool,
615 /// Property schema definitions.
616 pub properties: SchemaPropertyMap,
617 /// Default key name - used by `parse_parameter_string()`
618 pub default_key: Option<&'static str>,
619 }
620
621 impl ObjectSchema {
622 pub const fn new(description: &'static str, properties: SchemaPropertyMap) -> Self {
623 ObjectSchema {
624 description,
625 properties,
626 additional_properties: false,
627 default_key: None,
628 }
629 }
630
631 pub const fn additional_properties(mut self, additional_properties: bool) -> Self {
632 self.additional_properties = additional_properties;
633 self
634 }
635
636 pub const fn default_key(mut self, key: &'static str) -> Self {
637 self.default_key = Some(key);
638 self
639 }
640
641 pub const fn schema(self) -> Schema {
642 Schema::Object(self)
643 }
644
645 pub fn lookup(&self, key: &str) -> Option<(bool, &Schema)> {
646 if let Ok(ind) = self
647 .properties
648 .binary_search_by_key(&key, |(name, _, _)| name)
649 {
650 let (_name, optional, prop_schema) = self.properties[ind];
651 Some((optional, prop_schema))
652 } else {
653 None
654 }
655 }
656
657 /// Parse key/value pairs and verify with object schema
658 ///
659 /// - `test_required`: is set, checks if all required properties are
660 /// present.
661 pub fn parse_parameter_strings(
662 &'static self,
663 data: &[(String, String)],
664 test_required: bool,
665 ) -> Result<Value, ParameterError> {
666 ParameterSchema::from(self).parse_parameter_strings(data, test_required)
667 }
668 }
669
670 /// Combines multiple *object* schemas into one.
671 ///
672 /// Note that these are limited to object schemas. Other schemas will produce errors.
673 ///
674 /// Technically this could also contain an `additional_properties` flag, however, in the JSON
675 /// Schema, this is not supported, so here we simply assume additional properties to be allowed.
676 #[derive(Debug)]
677 #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
678 pub struct AllOfSchema {
679 pub description: &'static str,
680
681 /// The parameter is checked against all of the schemas in the list. Note that all schemas must
682 /// be object schemas.
683 pub list: &'static [&'static Schema],
684 }
685
686 impl AllOfSchema {
687 pub const fn new(description: &'static str, list: &'static [&'static Schema]) -> Self {
688 Self { description, list }
689 }
690
691 pub const fn schema(self) -> Schema {
692 Schema::AllOf(self)
693 }
694
695 pub fn lookup(&self, key: &str) -> Option<(bool, &Schema)> {
696 for entry in self.list {
697 if let Some(v) = entry
698 .any_object()
699 .expect("non-object-schema in `AllOfSchema`")
700 .lookup(key)
701 {
702 return Some(v);
703 }
704 }
705
706 None
707 }
708
709 /// Parse key/value pairs and verify with object schema
710 ///
711 /// - `test_required`: is set, checks if all required properties are
712 /// present.
713 pub fn parse_parameter_strings(
714 &'static self,
715 data: &[(String, String)],
716 test_required: bool,
717 ) -> Result<Value, ParameterError> {
718 ParameterSchema::from(self).parse_parameter_strings(data, test_required)
719 }
720 }
721
722 /// An object schema which is basically like a rust enum: exactly one variant may match.
723 ///
724 /// Contrary to JSON Schema, we require there be a 'type' property to distinguish the types.
725 /// In serde-language, we use an internally tagged enum representation.
726 ///
727 /// Note that these are limited to object schemas. Other schemas will produce errors.
728 #[derive(Debug)]
729 #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
730 pub struct OneOfSchema {
731 pub description: &'static str,
732
733 /// The type property entry.
734 ///
735 /// This must be a static reference due to how we implemented the property iterator.
736 pub type_property_entry: &'static SchemaPropertyEntry,
737
738 /// The parameter is checked against all of the schemas in the list. Note that all schemas must
739 /// be object schemas.
740 pub list: &'static [(&'static str, &'static Schema)],
741 }
742
743 impl OneOfSchema {
744 pub const fn new(
745 description: &'static str,
746 type_property_entry: &'static SchemaPropertyEntry,
747 list: &'static [(&'static str, &'static Schema)],
748 ) -> Self {
749 Self {
750 description,
751 type_property_entry,
752 list,
753 }
754 }
755
756 pub const fn schema(self) -> Schema {
757 Schema::OneOf(self)
758 }
759
760 pub fn type_property(&self) -> &'static str {
761 self.type_property_entry.0
762 }
763
764 pub fn type_schema(&self) -> &'static Schema {
765 self.type_property_entry.2
766 }
767
768 pub fn lookup(&self, key: &str) -> Option<(bool, &Schema)> {
769 if key == self.type_property() {
770 return Some((false, self.type_schema()));
771 }
772
773 for (_variant, entry) in self.list {
774 if let Some(v) = entry
775 .any_object()
776 .expect("non-object-schema in `OneOfSchema`")
777 .lookup(key)
778 {
779 return Some(v);
780 }
781 }
782
783 None
784 }
785
786 pub fn lookup_variant(&self, name: &str) -> Option<&Schema> {
787 Some(
788 self.list[self
789 .list
790 .binary_search_by_key(&name, |(name, _)| name)
791 .ok()?]
792 .1,
793 )
794 }
795
796 /// Parse key/value pairs and verify with object schema
797 ///
798 /// - `test_required`: is set, checks if all required properties are
799 /// present.
800 pub fn parse_parameter_strings(
801 &'static self,
802 data: &[(String, String)],
803 test_required: bool,
804 ) -> Result<Value, ParameterError> {
805 ParameterSchema::from(self).parse_parameter_strings(data, test_required)
806 }
807 }
808
809 /// Beside [`ObjectSchema`] we also have an [`AllOfSchema`] which also represents objects.
810 pub trait ObjectSchemaType {
811 fn description(&self) -> &'static str;
812 fn lookup(&self, key: &str) -> Option<(bool, &Schema)>;
813 fn properties(&self) -> ObjectPropertyIterator;
814 fn additional_properties(&self) -> bool;
815 fn default_key(&self) -> Option<&'static str>;
816
817 /// Verify JSON value using an object schema.
818 fn verify_json(&self, data: &Value) -> Result<(), Error> {
819 let map = match data {
820 Value::Object(ref map) => map,
821 Value::Array(_) => bail!("Expected object - got array."),
822 _ => bail!("Expected object - got scalar value."),
823 };
824
825 let mut errors = ParameterError::new();
826
827 let additional_properties = self.additional_properties();
828
829 for (key, value) in map {
830 if let Some((_optional, prop_schema)) = self.lookup(key) {
831 if let Err(err) = prop_schema.verify_json(value) {
832 errors.add_errors(key, err);
833 };
834 } else if !additional_properties {
835 errors.push(
836 key.to_string(),
837 format_err!("schema does not allow additional properties."),
838 );
839 }
840 }
841
842 for (name, optional, _prop_schema) in self.properties() {
843 if !(*optional) && data[name] == Value::Null {
844 errors.push(
845 name.to_string(),
846 format_err!("property is missing and it is not optional."),
847 );
848 }
849 }
850
851 if !errors.is_empty() {
852 Err(errors.into())
853 } else {
854 Ok(())
855 }
856 }
857 }
858
859 #[doc(hidden)]
860 pub enum ObjectPropertyIterator {
861 Simple(SimpleObjectPropertyIterator),
862 OneOf(OneOfPropertyIterator),
863 }
864
865 impl Iterator for ObjectPropertyIterator {
866 type Item = &'static SchemaPropertyEntry;
867
868 fn next(&mut self) -> Option<&'static SchemaPropertyEntry> {
869 match self {
870 Self::Simple(iter) => iter.next(),
871 Self::OneOf(iter) => iter.next(),
872 }
873 }
874 }
875
876 impl ObjectSchemaType for ObjectSchema {
877 fn description(&self) -> &'static str {
878 self.description
879 }
880
881 fn lookup(&self, key: &str) -> Option<(bool, &Schema)> {
882 ObjectSchema::lookup(self, key)
883 }
884
885 fn properties(&self) -> ObjectPropertyIterator {
886 ObjectPropertyIterator::Simple(SimpleObjectPropertyIterator {
887 schemas: [].iter(),
888 properties: Some(self.properties.iter()),
889 nested: None,
890 })
891 }
892
893 fn additional_properties(&self) -> bool {
894 self.additional_properties
895 }
896
897 fn default_key(&self) -> Option<&'static str> {
898 self.default_key
899 }
900 }
901
902 impl ObjectSchemaType for AllOfSchema {
903 fn description(&self) -> &'static str {
904 self.description
905 }
906
907 fn lookup(&self, key: &str) -> Option<(bool, &Schema)> {
908 AllOfSchema::lookup(self, key)
909 }
910
911 fn properties(&self) -> ObjectPropertyIterator {
912 ObjectPropertyIterator::Simple(SimpleObjectPropertyIterator {
913 schemas: self.list.iter(),
914 properties: None,
915 nested: None,
916 })
917 }
918
919 fn additional_properties(&self) -> bool {
920 self.list.iter().any(|schema| {
921 schema
922 .any_object()
923 .expect("non-object-schema in `AllOfSchema`")
924 .additional_properties()
925 })
926 }
927
928 fn default_key(&self) -> Option<&'static str> {
929 for schema in self.list {
930 let default_key = schema
931 .any_object()
932 .expect("non-object-schema in `AllOfSchema`")
933 .default_key();
934
935 if default_key.is_some() {
936 return default_key;
937 }
938 }
939
940 None
941 }
942 }
943
944 #[doc(hidden)]
945 pub struct SimpleObjectPropertyIterator {
946 schemas: std::slice::Iter<'static, &'static Schema>,
947 properties: Option<std::slice::Iter<'static, SchemaPropertyEntry>>,
948 nested: Option<Box<ObjectPropertyIterator>>,
949 }
950
951 impl Iterator for SimpleObjectPropertyIterator {
952 type Item = &'static SchemaPropertyEntry;
953
954 fn next(&mut self) -> Option<&'static SchemaPropertyEntry> {
955 loop {
956 match self.nested.as_mut().and_then(Iterator::next) {
957 Some(item) => return Some(item),
958 None => self.nested = None,
959 }
960
961 match self.properties.as_mut().and_then(Iterator::next) {
962 Some(item) => return Some(item),
963 None => match self.schemas.next()? {
964 Schema::AllOf(o) => self.nested = Some(Box::new(o.properties())),
965 Schema::OneOf(o) => self.nested = Some(Box::new(o.properties())),
966 Schema::Object(o) => self.properties = Some(o.properties.iter()),
967 _ => {
968 self.properties = None;
969 continue;
970 }
971 },
972 }
973 }
974 }
975 }
976
977 impl ObjectSchemaType for OneOfSchema {
978 fn description(&self) -> &'static str {
979 self.description
980 }
981
982 fn lookup(&self, key: &str) -> Option<(bool, &Schema)> {
983 OneOfSchema::lookup(self, key)
984 }
985
986 fn properties(&self) -> ObjectPropertyIterator {
987 ObjectPropertyIterator::OneOf(OneOfPropertyIterator {
988 type_property_entry: self.type_property_entry,
989 schemas: self.list.iter(),
990 done: HashSet::new(),
991 nested: None,
992 })
993 }
994
995 fn additional_properties(&self) -> bool {
996 self.list.iter().any(|(_, schema)| {
997 schema
998 .any_object()
999 .expect("non-object-schema in `OneOfSchema`")
1000 .additional_properties()
1001 })
1002 }
1003
1004 fn default_key(&self) -> Option<&'static str> {
1005 None
1006 }
1007
1008 fn verify_json(&self, data: &Value) -> Result<(), Error> {
1009 let map = match data {
1010 Value::Object(ref map) => map,
1011 Value::Array(_) => bail!("Expected object - got array."),
1012 _ => bail!("Expected object - got scalar value."),
1013 };
1014
1015 // Without the type we also cannot verify anything else...:
1016 let variant = match map.get(self.type_property()) {
1017 None => bail!("Missing '{}' property", self.type_property()),
1018 Some(Value::String(v)) => v,
1019 _ => bail!("Expected string in '{}'", self.type_property()),
1020 };
1021
1022 let schema = self
1023 .lookup_variant(variant)
1024 .ok_or_else(|| format_err!("invalid '{}': {}", self.type_property(), variant))?;
1025
1026 schema.verify_json(data)
1027 }
1028 }
1029
1030 #[doc(hidden)]
1031 pub struct OneOfPropertyIterator {
1032 type_property_entry: &'static SchemaPropertyEntry,
1033 schemas: std::slice::Iter<'static, (&'static str, &'static Schema)>,
1034 done: HashSet<&'static str>,
1035 nested: Option<Box<ObjectPropertyIterator>>,
1036 }
1037
1038 impl Iterator for OneOfPropertyIterator {
1039 type Item = &'static SchemaPropertyEntry;
1040
1041 fn next(&mut self) -> Option<&'static SchemaPropertyEntry> {
1042 if self.done.insert(self.type_property_entry.0) {
1043 return Some(self.type_property_entry);
1044 }
1045
1046 loop {
1047 match self.nested.as_mut().and_then(Iterator::next) {
1048 Some(item) => {
1049 if !self.done.insert(item.0) {
1050 continue;
1051 }
1052 return Some(item);
1053 }
1054 None => self.nested = None,
1055 }
1056
1057 self.nested = Some(Box::new(
1058 self.schemas
1059 .next()?
1060 .1
1061 .any_object()
1062 .expect("non-object-schema in `OneOfSchema`")
1063 .properties(),
1064 ));
1065 }
1066 }
1067 }
1068
1069 /// Schemas are used to describe complex data types.
1070 ///
1071 /// All schema types implement constant builder methods, and a final
1072 /// `schema()` method to convert them into a `Schema`.
1073 ///
1074 /// ```
1075 /// use proxmox_schema::{Schema, BooleanSchema, IntegerSchema, ObjectSchema};
1076 ///
1077 /// const SIMPLE_OBJECT: Schema = ObjectSchema::new(
1078 /// "A very simple object with 2 properties",
1079 /// &[ // this arrays needs to be storted by name!
1080 /// (
1081 /// "property_one",
1082 /// false /* required */,
1083 /// &IntegerSchema::new("A required integer property.")
1084 /// .minimum(0)
1085 /// .maximum(100)
1086 /// .schema()
1087 /// ),
1088 /// (
1089 /// "property_two",
1090 /// true /* optional */,
1091 /// &BooleanSchema::new("An optional boolean property.")
1092 /// .default(true)
1093 /// .schema()
1094 /// ),
1095 /// ],
1096 /// ).schema();
1097 /// ```
1098 #[derive(Debug)]
1099 #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
1100 pub enum Schema {
1101 Null,
1102 Boolean(BooleanSchema),
1103 Integer(IntegerSchema),
1104 Number(NumberSchema),
1105 String(StringSchema),
1106 Object(ObjectSchema),
1107 Array(ArraySchema),
1108 AllOf(AllOfSchema),
1109 OneOf(OneOfSchema),
1110 }
1111
1112 impl Schema {
1113 /// Verify JSON value with `schema`.
1114 pub fn verify_json(&self, data: &Value) -> Result<(), Error> {
1115 match self {
1116 Schema::Null => {
1117 if !data.is_null() {
1118 bail!("Expected Null, but value is not Null.");
1119 }
1120 }
1121 Schema::Object(s) => s.verify_json(data)?,
1122 Schema::Array(s) => s.verify_json(data)?,
1123 Schema::Boolean(s) => s.verify_json(data)?,
1124 Schema::Integer(s) => s.verify_json(data)?,
1125 Schema::Number(s) => s.verify_json(data)?,
1126 Schema::String(s) => s.verify_json(data)?,
1127 Schema::AllOf(s) => s.verify_json(data)?,
1128 Schema::OneOf(s) => s.verify_json(data)?,
1129 }
1130 Ok(())
1131 }
1132
1133 /// Parse a simple value (no arrays and no objects)
1134 pub fn parse_simple_value(&self, value_str: &str) -> Result<Value, Error> {
1135 let value = match self {
1136 Schema::Null => {
1137 bail!("internal error - found Null schema.");
1138 }
1139 Schema::Boolean(_boolean_schema) => {
1140 let res = parse_boolean(value_str)?;
1141 Value::Bool(res)
1142 }
1143 Schema::Integer(integer_schema) => {
1144 let res: isize = value_str.parse()?;
1145 integer_schema.check_constraints(res)?;
1146 Value::Number(res.into())
1147 }
1148 Schema::Number(number_schema) => {
1149 let res: f64 = value_str.parse()?;
1150 number_schema.check_constraints(res)?;
1151 Value::Number(serde_json::Number::from_f64(res).unwrap())
1152 }
1153 Schema::String(string_schema) => {
1154 string_schema.check_constraints(value_str)?;
1155 Value::String(value_str.into())
1156 }
1157 _ => bail!("unable to parse complex (sub) objects."),
1158 };
1159 Ok(value)
1160 }
1161
1162 /// Parse a complex property string (`ApiStringFormat::PropertyString`)
1163 pub fn parse_property_string(&'static self, value_str: &str) -> Result<Value, Error> {
1164 // helper for object/allof schemas:
1165 fn parse_object<T: Into<ParameterSchema>>(
1166 value_str: &str,
1167 schema: T,
1168 default_key: Option<&'static str>,
1169 ) -> Result<Value, Error> {
1170 let mut param_list = Vec::new();
1171 for entry in crate::property_string::PropertyIterator::new(value_str) {
1172 let (key, value) = entry?;
1173 match key {
1174 Some(key) => param_list.push((key.to_string(), value.into_owned())),
1175 None => {
1176 if let Some(key) = default_key {
1177 param_list.push((key.to_string(), value.into_owned()));
1178 } else {
1179 bail!("Value without key, but schema does not define a default key.");
1180 }
1181 }
1182 }
1183 }
1184
1185 schema
1186 .into()
1187 .parse_parameter_strings(&param_list, true)
1188 .map_err(Error::from)
1189 }
1190
1191 match self {
1192 Schema::Object(object_schema) => {
1193 parse_object(value_str, object_schema, object_schema.default_key)
1194 }
1195 Schema::AllOf(all_of_schema) => parse_object(value_str, all_of_schema, None),
1196 Schema::Array(array_schema) => {
1197 let mut array: Vec<Value> = vec![];
1198 let list: Vec<&str> = value_str
1199 .split(|c: char| c == ',' || c == ';' || char::is_ascii_whitespace(&c))
1200 .filter(|s| !s.is_empty())
1201 .collect();
1202
1203 for value in list {
1204 match array_schema.items.parse_simple_value(value.trim()) {
1205 Ok(res) => array.push(res),
1206 Err(err) => bail!("unable to parse array element: {}", err),
1207 }
1208 }
1209 array_schema.check_length(array.len())?;
1210
1211 Ok(array.into())
1212 }
1213 _ => bail!("Got unexpected schema type."),
1214 }
1215 }
1216
1217 /// Gets the underlying [`BooleanSchema`], panics on different schemas.
1218 pub const fn unwrap_boolean_schema(&self) -> &BooleanSchema {
1219 match self {
1220 Schema::Boolean(s) => s,
1221 _ => panic!("unwrap_boolean_schema on different schema"),
1222 }
1223 }
1224
1225 /// Gets the underlying [`IntegerSchema`], panics on different schemas.
1226 pub const fn unwrap_integer_schema(&self) -> &IntegerSchema {
1227 match self {
1228 Schema::Integer(s) => s,
1229 _ => panic!("unwrap_integer_schema on different schema"),
1230 }
1231 }
1232
1233 /// Gets the underlying [`NumberSchema`], panics on different schemas.
1234 pub const fn unwrap_number_schema(&self) -> &NumberSchema {
1235 match self {
1236 Schema::Number(s) => s,
1237 _ => panic!("unwrap_number_schema on different schema"),
1238 }
1239 }
1240
1241 /// Gets the underlying [`StringSchema`], panics on different schemas.
1242 pub const fn unwrap_string_schema(&self) -> &StringSchema {
1243 match self {
1244 Schema::String(s) => s,
1245 _ => panic!("unwrap_string_schema on different schema"),
1246 }
1247 }
1248
1249 /// Gets the underlying [`ObjectSchema`], panics on different schemas.
1250 pub const fn unwrap_object_schema(&self) -> &ObjectSchema {
1251 match self {
1252 Schema::Object(s) => s,
1253 _ => panic!("unwrap_object_schema on different schema"),
1254 }
1255 }
1256
1257 /// Gets the underlying [`ArraySchema`], panics on different schemas.
1258 pub const fn unwrap_array_schema(&self) -> &ArraySchema {
1259 match self {
1260 Schema::Array(s) => s,
1261 _ => panic!("unwrap_array_schema on different schema"),
1262 }
1263 }
1264
1265 /// Gets the underlying [`AllOfSchema`], panics on different schemas.
1266 pub const fn unwrap_all_of_schema(&self) -> &AllOfSchema {
1267 match self {
1268 Schema::AllOf(s) => s,
1269 _ => panic!("unwrap_all_of_schema on different schema"),
1270 }
1271 }
1272
1273 /// Gets the underlying [`OneOfSchema`], panics on different schemas.
1274 pub const fn unwrap_one_of_schema(&self) -> &OneOfSchema {
1275 match self {
1276 Schema::OneOf(s) => s,
1277 _ => panic!("unwrap_one_of_schema on different schema"),
1278 }
1279 }
1280
1281 /// Gets the underlying [`BooleanSchema`].
1282 pub const fn boolean(&self) -> Option<&BooleanSchema> {
1283 match self {
1284 Schema::Boolean(s) => Some(s),
1285 _ => None,
1286 }
1287 }
1288
1289 /// Gets the underlying [`IntegerSchema`].
1290 pub const fn integer(&self) -> Option<&IntegerSchema> {
1291 match self {
1292 Schema::Integer(s) => Some(s),
1293 _ => None,
1294 }
1295 }
1296
1297 /// Gets the underlying [`NumberSchema`].
1298 pub const fn number(&self) -> Option<&NumberSchema> {
1299 match self {
1300 Schema::Number(s) => Some(s),
1301 _ => None,
1302 }
1303 }
1304
1305 /// Gets the underlying [`StringSchema`].
1306 pub const fn string(&self) -> Option<&StringSchema> {
1307 match self {
1308 Schema::String(s) => Some(s),
1309 _ => None,
1310 }
1311 }
1312
1313 /// Gets the underlying [`ObjectSchema`].
1314 pub const fn object(&self) -> Option<&ObjectSchema> {
1315 match self {
1316 Schema::Object(s) => Some(s),
1317 _ => None,
1318 }
1319 }
1320
1321 /// Gets the underlying [`ArraySchema`].
1322 pub const fn array(&self) -> Option<&ArraySchema> {
1323 match self {
1324 Schema::Array(s) => Some(s),
1325 _ => None,
1326 }
1327 }
1328
1329 /// Gets the underlying [`AllOfSchema`].
1330 pub const fn all_of(&self) -> Option<&AllOfSchema> {
1331 match self {
1332 Schema::AllOf(s) => Some(s),
1333 _ => None,
1334 }
1335 }
1336
1337 /// Gets the underlying [`AllOfSchema`].
1338 pub const fn one_of(&self) -> Option<&OneOfSchema> {
1339 match self {
1340 Schema::OneOf(s) => Some(s),
1341 _ => None,
1342 }
1343 }
1344
1345 pub fn any_object(&self) -> Option<&dyn ObjectSchemaType> {
1346 match self {
1347 Schema::Object(s) => Some(s),
1348 Schema::AllOf(s) => Some(s),
1349 Schema::OneOf(s) => Some(s),
1350 _ => None,
1351 }
1352 }
1353 }
1354
1355 /// A string enum entry. An enum entry must have a value and a description.
1356 #[derive(Clone, Debug)]
1357 #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
1358 pub struct EnumEntry {
1359 pub value: &'static str,
1360 pub description: &'static str,
1361 }
1362
1363 impl EnumEntry {
1364 /// Convenience method as long as we only have 2 mandatory fields in an `EnumEntry`.
1365 pub const fn new(value: &'static str, description: &'static str) -> Self {
1366 Self { value, description }
1367 }
1368 }
1369
1370 /// String microformat definitions.
1371 ///
1372 /// Strings are probably the most flexible data type, and there are
1373 /// several ways to define their content.
1374 ///
1375 /// ## Enumerations
1376 ///
1377 /// Simple list all possible values.
1378 ///
1379 /// ```
1380 /// use proxmox_schema::{ApiStringFormat, EnumEntry};
1381 ///
1382 /// const format: ApiStringFormat = ApiStringFormat::Enum(&[
1383 /// EnumEntry::new("vm", "A guest VM run via qemu"),
1384 /// EnumEntry::new("ct", "A guest container run via lxc"),
1385 /// ]);
1386 /// ```
1387 ///
1388 /// ## Regular Expressions
1389 ///
1390 /// Use a regular expression to describe valid strings.
1391 ///
1392 /// ```
1393 /// use proxmox_schema::{const_regex, ApiStringFormat};
1394 ///
1395 /// const_regex! {
1396 /// pub SHA256_HEX_REGEX = r"^[a-f0-9]{64}$";
1397 /// }
1398 /// const format: ApiStringFormat = ApiStringFormat::Pattern(&SHA256_HEX_REGEX);
1399 /// ```
1400 ///
1401 /// ## Property Strings
1402 ///
1403 /// Use a schema to describe complex types encoded as string.
1404 ///
1405 /// Arrays are parsed as comma separated lists, i.e: `"1,2,3"`. The
1406 /// list may be sparated by comma, semicolon or whitespace.
1407 ///
1408 /// Objects are parsed as comma (or semicolon) separated `key=value` pairs, i.e:
1409 /// `"prop1=2,prop2=test"`. Any whitespace is trimmed from key and value.
1410 ///
1411 ///
1412 /// **Note:** This only works for types which does not allow using the
1413 /// comma, semicolon or whitespace separator inside the value,
1414 /// i.e. this only works for arrays of simple data types, and objects
1415 /// with simple properties (no nesting).
1416 ///
1417 /// ```
1418 /// use proxmox_schema::{ApiStringFormat, ArraySchema, IntegerSchema, Schema, StringSchema};
1419 /// use proxmox_schema::{parse_simple_value, parse_property_string};
1420 ///
1421 /// const PRODUCT_LIST_SCHEMA: Schema =
1422 /// ArraySchema::new("Product List.", &IntegerSchema::new("Product ID").schema())
1423 /// .min_length(1)
1424 /// .schema();
1425 ///
1426 /// const SCHEMA: Schema = StringSchema::new("A list of Product IDs, comma separated.")
1427 /// .format(&ApiStringFormat::PropertyString(&PRODUCT_LIST_SCHEMA))
1428 /// .schema();
1429 ///
1430 /// let res = parse_simple_value("", &SCHEMA);
1431 /// assert!(res.is_err());
1432 ///
1433 /// let res = parse_simple_value("1,2,3", &SCHEMA); // parse as String
1434 /// assert!(res.is_ok());
1435 ///
1436 /// let data = parse_property_string("1,2", &PRODUCT_LIST_SCHEMA); // parse as Array
1437 /// assert!(data.is_ok());
1438 /// ```
1439 pub enum ApiStringFormat {
1440 /// Enumerate all valid strings
1441 Enum(&'static [EnumEntry]),
1442 /// Use a regular expression to describe valid strings.
1443 Pattern(&'static ConstRegexPattern),
1444 /// Use a schema to describe complex types encoded as string.
1445 PropertyString(&'static Schema),
1446 /// Use a verification function.
1447 VerifyFn(ApiStringVerifyFn),
1448 }
1449
1450 /// Type of a verification function for [`StringSchema`]s.
1451 pub type ApiStringVerifyFn = fn(&str) -> Result<(), Error>;
1452
1453 impl ApiStringFormat {
1454 /// Gets the underlying [`&[EnumEntry]`](EnumEntry) list, panics on different formats.
1455 pub const fn unwrap_enum_format(&self) -> &'static [EnumEntry] {
1456 match self {
1457 ApiStringFormat::Enum(v) => v,
1458 _ => panic!("unwrap_enum_format on a different ApiStringFormat"),
1459 }
1460 }
1461
1462 /// Gets the underlying [`&ConstRegexPattern`](ConstRegexPattern), panics on different formats.
1463 pub const fn unwrap_pattern_format(&self) -> &'static ConstRegexPattern {
1464 match self {
1465 ApiStringFormat::Pattern(v) => v,
1466 _ => panic!("unwrap_pattern_format on a different ApiStringFormat"),
1467 }
1468 }
1469
1470 /// Gets the underlying property [`&Schema`](Schema), panics on different formats.
1471 pub const fn unwrap_property_string_format(&self) -> &'static Schema {
1472 match self {
1473 ApiStringFormat::PropertyString(v) => v,
1474 _ => panic!("unwrap_property_string_format on a different ApiStringFormat"),
1475 }
1476 }
1477 }
1478
1479 impl std::fmt::Debug for ApiStringFormat {
1480 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1481 match self {
1482 ApiStringFormat::VerifyFn(fnptr) => write!(f, "VerifyFn({:p}", fnptr),
1483 ApiStringFormat::Enum(variants) => write!(f, "Enum({:?}", variants),
1484 ApiStringFormat::Pattern(regex) => write!(f, "Pattern({:?}", regex),
1485 ApiStringFormat::PropertyString(schema) => write!(f, "PropertyString({:?}", schema),
1486 }
1487 }
1488 }
1489
1490 #[cfg(feature = "test-harness")]
1491 impl Eq for ApiStringFormat {}
1492
1493 #[cfg(feature = "test-harness")]
1494 impl PartialEq for ApiStringFormat {
1495 fn eq(&self, rhs: &Self) -> bool {
1496 match (self, rhs) {
1497 (ApiStringFormat::Enum(l), ApiStringFormat::Enum(r)) => l == r,
1498 (ApiStringFormat::Pattern(l), ApiStringFormat::Pattern(r)) => l == r,
1499 (ApiStringFormat::PropertyString(l), ApiStringFormat::PropertyString(r)) => l == r,
1500 (ApiStringFormat::VerifyFn(l), ApiStringFormat::VerifyFn(r)) => std::ptr::eq(l, r),
1501 (_, _) => false,
1502 }
1503 }
1504 }
1505
1506 /// Parameters are objects, but we have two types of object schemas, the regular one and the
1507 /// `AllOf` schema.
1508 #[derive(Clone, Copy, Debug)]
1509 #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
1510 pub enum ParameterSchema {
1511 Object(&'static ObjectSchema),
1512 AllOf(&'static AllOfSchema),
1513 OneOf(&'static OneOfSchema),
1514 }
1515
1516 impl ParameterSchema {
1517 /// Parse key/value pairs and verify with object schema
1518 ///
1519 /// - `test_required`: is set, checks if all required properties are
1520 /// present.
1521 pub fn parse_parameter_strings(
1522 self,
1523 data: &[(String, String)],
1524 test_required: bool,
1525 ) -> Result<Value, ParameterError> {
1526 do_parse_parameter_strings(self, data, test_required)
1527 }
1528 }
1529
1530 impl ObjectSchemaType for ParameterSchema {
1531 fn description(&self) -> &'static str {
1532 match self {
1533 ParameterSchema::Object(o) => o.description(),
1534 ParameterSchema::AllOf(o) => o.description(),
1535 ParameterSchema::OneOf(o) => o.description(),
1536 }
1537 }
1538
1539 fn lookup(&self, key: &str) -> Option<(bool, &Schema)> {
1540 match self {
1541 ParameterSchema::Object(o) => o.lookup(key),
1542 ParameterSchema::AllOf(o) => o.lookup(key),
1543 ParameterSchema::OneOf(o) => o.lookup(key),
1544 }
1545 }
1546
1547 fn properties(&self) -> ObjectPropertyIterator {
1548 match self {
1549 ParameterSchema::Object(o) => o.properties(),
1550 ParameterSchema::AllOf(o) => o.properties(),
1551 ParameterSchema::OneOf(o) => o.properties(),
1552 }
1553 }
1554
1555 fn additional_properties(&self) -> bool {
1556 match self {
1557 ParameterSchema::Object(o) => o.additional_properties(),
1558 ParameterSchema::AllOf(o) => o.additional_properties(),
1559 ParameterSchema::OneOf(o) => o.additional_properties(),
1560 }
1561 }
1562
1563 fn default_key(&self) -> Option<&'static str> {
1564 match self {
1565 ParameterSchema::Object(o) => o.default_key(),
1566 ParameterSchema::AllOf(o) => o.default_key(),
1567 ParameterSchema::OneOf(o) => o.default_key(),
1568 }
1569 }
1570 }
1571
1572 impl From<&'static ObjectSchema> for ParameterSchema {
1573 fn from(schema: &'static ObjectSchema) -> Self {
1574 ParameterSchema::Object(schema)
1575 }
1576 }
1577
1578 impl From<&'static AllOfSchema> for ParameterSchema {
1579 fn from(schema: &'static AllOfSchema) -> Self {
1580 ParameterSchema::AllOf(schema)
1581 }
1582 }
1583
1584 impl From<&'static OneOfSchema> for ParameterSchema {
1585 fn from(schema: &'static OneOfSchema) -> Self {
1586 ParameterSchema::OneOf(schema)
1587 }
1588 }
1589
1590 /// Helper function to parse boolean values
1591 ///
1592 /// - true: `1 | on | yes | true`
1593 /// - false: `0 | off | no | false`
1594 pub fn parse_boolean(value_str: &str) -> Result<bool, Error> {
1595 match value_str.to_lowercase().as_str() {
1596 "1" | "on" | "yes" | "true" => Ok(true),
1597 "0" | "off" | "no" | "false" => Ok(false),
1598 _ => bail!("Unable to parse boolean option."),
1599 }
1600 }
1601
1602 /// Parse a complex property string (`ApiStringFormat::PropertyString`)
1603 #[deprecated(note = "this is now a method of Schema")]
1604 pub fn parse_property_string(value_str: &str, schema: &'static Schema) -> Result<Value, Error> {
1605 schema.parse_property_string(value_str)
1606 }
1607
1608 /// Parse a simple value (no arrays and no objects)
1609 #[deprecated(note = "this is now a method of Schema")]
1610 pub fn parse_simple_value(value_str: &str, schema: &Schema) -> Result<Value, Error> {
1611 schema.parse_simple_value(value_str)
1612 }
1613
1614 /// Parse key/value pairs and verify with object schema
1615 ///
1616 /// - `test_required`: is set, checks if all required properties are
1617 /// present.
1618 #[deprecated(note = "this is now a method of parameter schema types")]
1619 pub fn parse_parameter_strings<T: Into<ParameterSchema>>(
1620 data: &[(String, String)],
1621 schema: T,
1622 test_required: bool,
1623 ) -> Result<Value, ParameterError> {
1624 do_parse_parameter_strings(schema.into(), data, test_required)
1625 }
1626
1627 fn do_parse_parameter_strings(
1628 schema: ParameterSchema,
1629 data: &[(String, String)],
1630 test_required: bool,
1631 ) -> Result<Value, ParameterError> {
1632 let mut params = json!({});
1633
1634 let mut errors = ParameterError::new();
1635
1636 let additional_properties = schema.additional_properties();
1637
1638 for (key, value) in data {
1639 if let Some((_optional, prop_schema)) = schema.lookup(key) {
1640 match prop_schema {
1641 Schema::Array(array_schema) => {
1642 if params[key] == Value::Null {
1643 params[key] = json!([]);
1644 }
1645 match params[key] {
1646 Value::Array(ref mut array) => {
1647 match array_schema.items.parse_simple_value(value) {
1648 Ok(res) => array.push(res), // fixme: check_length??
1649 Err(err) => errors.push(key.into(), err),
1650 }
1651 }
1652 _ => {
1653 errors.push(key.into(), format_err!("expected array - type missmatch"))
1654 }
1655 }
1656 }
1657 _ => match prop_schema.parse_simple_value(value) {
1658 Ok(res) => {
1659 if params[key] == Value::Null {
1660 params[key] = res;
1661 } else {
1662 errors.push(key.into(), format_err!("duplicate parameter."));
1663 }
1664 }
1665 Err(err) => errors.push(key.into(), err),
1666 },
1667 }
1668 } else if additional_properties {
1669 match params[key] {
1670 Value::Null => {
1671 params[key] = Value::String(value.to_owned());
1672 }
1673 Value::String(ref old) => {
1674 params[key] = Value::Array(vec![
1675 Value::String(old.to_owned()),
1676 Value::String(value.to_owned()),
1677 ]);
1678 }
1679 Value::Array(ref mut array) => {
1680 array.push(Value::String(value.to_string()));
1681 }
1682 _ => errors.push(key.into(), format_err!("expected array - type missmatch")),
1683 }
1684 } else {
1685 errors.push(
1686 key.into(),
1687 format_err!("schema does not allow additional properties."),
1688 );
1689 }
1690 }
1691
1692 if test_required && errors.is_empty() {
1693 for (name, optional, _prop_schema) in schema.properties() {
1694 if !(*optional) && params[name] == Value::Null {
1695 errors.push(
1696 name.to_string(),
1697 format_err!("parameter is missing and it is not optional."),
1698 );
1699 }
1700 }
1701 }
1702
1703 if !errors.is_empty() {
1704 Err(errors)
1705 } else {
1706 Ok(params)
1707 }
1708 }
1709
1710 /// Verify JSON value with `schema`.
1711 #[deprecated(note = "use the method schema.verify_json() instead")]
1712 pub fn verify_json(data: &Value, schema: &Schema) -> Result<(), Error> {
1713 schema.verify_json(data)
1714 }
1715
1716 /// Verify JSON value using a `StringSchema`.
1717 #[deprecated(note = "use the method string_schema.verify_json() instead")]
1718 pub fn verify_json_string(data: &Value, schema: &StringSchema) -> Result<(), Error> {
1719 schema.verify_json(data)
1720 }
1721
1722 /// Verify JSON value using a `BooleanSchema`.
1723 #[deprecated(note = "use the method boolean_schema.verify_json() instead")]
1724 pub fn verify_json_boolean(data: &Value, schema: &BooleanSchema) -> Result<(), Error> {
1725 schema.verify_json(data)
1726 }
1727
1728 /// Verify JSON value using an `IntegerSchema`.
1729 #[deprecated(note = "use the method integer_schema.verify_json() instead")]
1730 pub fn verify_json_integer(data: &Value, schema: &IntegerSchema) -> Result<(), Error> {
1731 schema.verify_json(data)
1732 }
1733
1734 /// Verify JSON value using an `NumberSchema`.
1735 #[deprecated(note = "use the method number_schema.verify_json() instead")]
1736 pub fn verify_json_number(data: &Value, schema: &NumberSchema) -> Result<(), Error> {
1737 schema.verify_json(data)
1738 }
1739
1740 /// Verify JSON value using an `ArraySchema`.
1741 #[deprecated(note = "use the method array_schema.verify_json() instead")]
1742 pub fn verify_json_array(data: &Value, schema: &ArraySchema) -> Result<(), Error> {
1743 schema.verify_json(data)
1744 }
1745
1746 /// Verify JSON value using an `ObjectSchema`.
1747 #[deprecated(note = "use the verify_json() method via the ObjectSchemaType trait instead")]
1748 pub fn verify_json_object(data: &Value, schema: &dyn ObjectSchemaType) -> Result<(), Error> {
1749 schema.verify_json(data)
1750 }
1751
1752 /// API types should define an "updater type" via this trait in order to support derived "Updater"
1753 /// structs more easily.
1754 ///
1755 /// Most trivial types can simply use an `Option<Self>` as updater. For types which do not use the
1756 /// `#[api]` macro, this will need to be explicitly created (or derived via
1757 /// `#[derive(UpdaterType)]`.
1758 pub trait UpdaterType: Sized {
1759 type Updater: Updater;
1760 }
1761
1762 #[cfg(feature = "api-macro")]
1763 pub use proxmox_api_macro::UpdaterType;
1764
1765 #[cfg(feature = "api-macro")]
1766 #[doc(hidden)]
1767 pub use proxmox_api_macro::Updater;
1768
1769 macro_rules! basic_updater_type {
1770 ($($ty:ty)*) => {
1771 $(
1772 impl UpdaterType for $ty {
1773 type Updater = Option<Self>;
1774 }
1775 )*
1776 };
1777 }
1778 basic_updater_type! { bool u8 u16 u32 u64 i8 i16 i32 i64 usize isize f32 f64 String char }
1779
1780 impl<T> UpdaterType for Option<T>
1781 where
1782 T: UpdaterType,
1783 {
1784 type Updater = T::Updater;
1785 }
1786
1787 // this will replace the whole Vec
1788 impl<T> UpdaterType for Vec<T> {
1789 type Updater = Option<Self>;
1790 }
1791
1792 /// Trait signifying that a type contains an API schema.
1793 pub trait ApiType {
1794 const API_SCHEMA: Schema;
1795 }
1796
1797 impl<T: ApiType> ApiType for Option<T> {
1798 const API_SCHEMA: Schema = T::API_SCHEMA;
1799 }
1800
1801 /// A helper type for "Updater" structs. This trait is *not* implemented for an api "base" type
1802 /// when deriving an `Updater` for it, though the generated *updater* type does implement this
1803 /// trait!
1804 ///
1805 /// This trait is mostly to figure out if an updater is empty (iow. it should not be applied).
1806 /// In that, it is useful when a type which should have an updater also has optional fields which
1807 /// should not be serialized. Instead of `#[serde(skip_serializing_if = "Option::is_none")]`, this
1808 /// trait's `is_empty` needs to be used via `#[serde(skip_serializing_if = "Updater::is_empty")]`.
1809 pub trait Updater {
1810 /// Check if the updater is "none" or "empty".
1811 fn is_empty(&self) -> bool;
1812 }
1813
1814 impl<T> Updater for Vec<T> {
1815 fn is_empty(&self) -> bool {
1816 self.is_empty()
1817 }
1818 }
1819
1820 impl<T> Updater for Option<T> {
1821 fn is_empty(&self) -> bool {
1822 self.is_none()
1823 }
1824 }
1825
1826 /// Return type schema. Return types may be any schema and additionally be optional.
1827 #[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
1828 pub struct ReturnType {
1829 /// A return type may be optional, meaning the method may return null or some fixed data.
1830 ///
1831 /// If true, the return type in pseudo openapi terms would be `"oneOf": [ "null", "T" ]`.
1832 pub optional: bool,
1833
1834 /// The method's return type.
1835 pub schema: &'static Schema,
1836 }
1837
1838 impl std::fmt::Debug for ReturnType {
1839 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1840 if self.optional {
1841 write!(f, "optional {:?}", self.schema)
1842 } else {
1843 write!(f, "{:?}", self.schema)
1844 }
1845 }
1846 }
1847
1848 impl ReturnType {
1849 pub const fn new(optional: bool, schema: &'static Schema) -> Self {
1850 Self { optional, schema }
1851 }
1852 }