forked from Proxmox/proxmox
schema: implement oneOf schema support
A 'oneOf' schema is basically exactly what a rust `enum` is. Exactly one of the possible values must match the data. This should ultimately be the base to allow using the `#[api]` macro on a newtype style enum as well as using this schema as a configuration for our section config parser. Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com> Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
This commit is contained in:
parent
1c0edfb518
commit
d48a150835
@ -52,7 +52,9 @@ impl<'o> ExtractValueDeserializer<'o> {
|
||||
schema: &'static Schema,
|
||||
) -> Option<Self> {
|
||||
match schema {
|
||||
Schema::Object(_) | Schema::AllOf(_) => Some(Self { object, schema }),
|
||||
Schema::Object(_) | Schema::AllOf(_) | Schema::OneOf(_) => {
|
||||
Some(Self { object, schema })
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@ -106,6 +108,10 @@ impl<'de> de::Deserializer<'de> for ExtractValueDeserializer<'de> {
|
||||
self.object,
|
||||
schema.properties().map(|(name, _, _)| *name),
|
||||
)),
|
||||
Schema::OneOf(schema) => visitor.visit_map(MapAccess::<'de>::new(
|
||||
self.object,
|
||||
schema.properties().map(|(name, _, _)| *name),
|
||||
)),
|
||||
|
||||
// The following should be caught by ExtractValueDeserializer::new()!
|
||||
_ => Err(Error::custom(
|
||||
@ -134,6 +140,10 @@ impl<'de> de::Deserializer<'de> for ExtractValueDeserializer<'de> {
|
||||
self.object,
|
||||
schema.properties().map(|(name, _, _)| *name),
|
||||
)),
|
||||
Schema::OneOf(schema) => visitor.visit_map(MapAccess::<'de>::new(
|
||||
self.object,
|
||||
schema.properties().map(|(name, _, _)| *name),
|
||||
)),
|
||||
|
||||
// The following should be caught by ExtractValueDeserializer::new()!
|
||||
_ => Err(Error::custom(
|
||||
|
@ -129,6 +129,7 @@ impl<'de, 'i> SchemaDeserializer<'de, 'i> {
|
||||
match schema {
|
||||
Schema::Object(schema) => visitor.visit_map(MapAccess::new_cow(self.input, schema)),
|
||||
Schema::AllOf(schema) => visitor.visit_map(MapAccess::new_cow(self.input, schema)),
|
||||
Schema::OneOf(schema) => visitor.visit_map(MapAccess::new_cow(self.input, schema)),
|
||||
_ => Err(Error::msg(
|
||||
"non-object-like schema in ApiStringFormat::PropertyString while deserializing a property string",
|
||||
)),
|
||||
@ -162,6 +163,7 @@ impl<'de, 'i> de::Deserializer<'de> for SchemaDeserializer<'de, 'i> {
|
||||
match self.schema {
|
||||
Schema::Array(schema) => visitor.visit_seq(SeqAccess::new(self.input, schema)),
|
||||
Schema::AllOf(schema) => visitor.visit_map(MapAccess::new_cow(self.input, schema)),
|
||||
Schema::OneOf(schema) => visitor.visit_map(MapAccess::new_cow(self.input, schema)),
|
||||
Schema::Object(schema) => visitor.visit_map(MapAccess::new_cow(self.input, schema)),
|
||||
Schema::Null => Err(Error::msg("null")),
|
||||
Schema::Boolean(_) => visitor.visit_bool(
|
||||
@ -263,6 +265,7 @@ impl<'de, 'i> de::Deserializer<'de> for SchemaDeserializer<'de, 'i> {
|
||||
match self.schema {
|
||||
Schema::Object(schema) => visitor.visit_map(MapAccess::new_cow(self.input, schema)),
|
||||
Schema::AllOf(schema) => visitor.visit_map(MapAccess::new_cow(self.input, schema)),
|
||||
Schema::OneOf(schema) => visitor.visit_map(MapAccess::new_cow(self.input, schema)),
|
||||
Schema::String(schema) => match schema.format {
|
||||
Some(schema::ApiStringFormat::PropertyString(schema)) => {
|
||||
self.deserialize_property_string(visitor, schema)
|
||||
@ -286,6 +289,7 @@ impl<'de, 'i> de::Deserializer<'de> for SchemaDeserializer<'de, 'i> {
|
||||
match self.schema {
|
||||
Schema::Object(schema) => visitor.visit_map(MapAccess::new_cow(self.input, schema)),
|
||||
Schema::AllOf(schema) => visitor.visit_map(MapAccess::new_cow(self.input, schema)),
|
||||
Schema::OneOf(schema) => visitor.visit_map(MapAccess::new_cow(self.input, schema)),
|
||||
Schema::String(schema) => match schema.format {
|
||||
Some(schema::ApiStringFormat::PropertyString(schema)) => {
|
||||
self.deserialize_property_string(visitor, schema)
|
||||
|
@ -120,6 +120,7 @@ impl<'de> Deserialize<'de> for Verifier {
|
||||
Schema::String(_) => deserializer.deserialize_str(visitor),
|
||||
Schema::Object(_) => deserializer.deserialize_map(visitor),
|
||||
Schema::AllOf(_) => deserializer.deserialize_map(visitor),
|
||||
Schema::OneOf(_) => deserializer.deserialize_map(visitor),
|
||||
Schema::Array(_) => deserializer.deserialize_seq(visitor),
|
||||
Schema::Null => deserializer.deserialize_unit(visitor),
|
||||
}
|
||||
@ -153,6 +154,7 @@ impl<'de> de::Visitor<'de> for Visitor {
|
||||
Schema::String(_) => f.write_str("string"),
|
||||
Schema::Object(_) => f.write_str("object"),
|
||||
Schema::AllOf(_) => f.write_str("allOf"),
|
||||
Schema::OneOf(_) => f.write_str("oneOf"),
|
||||
Schema::Array(_) => f.write_str("Array"),
|
||||
Schema::Null => f.write_str("null"),
|
||||
}
|
||||
@ -226,6 +228,7 @@ impl<'de> de::Visitor<'de> for Visitor {
|
||||
let schema: &'static dyn crate::schema::ObjectSchemaType = match self.0 {
|
||||
Schema::Object(schema) => schema,
|
||||
Schema::AllOf(schema) => schema,
|
||||
Schema::OneOf(schema) => schema,
|
||||
_ => return Err(A::Error::invalid_type(Unexpected::Map, &self)),
|
||||
};
|
||||
|
||||
|
@ -213,6 +213,7 @@ pub fn get_property_description(
|
||||
),
|
||||
Schema::Object(ref schema) => (schema.description, None, None),
|
||||
Schema::AllOf(ref schema) => (schema.description, None, None),
|
||||
Schema::OneOf(ref schema) => (schema.description, None, None),
|
||||
Schema::Array(ref schema) => (
|
||||
schema.description,
|
||||
None,
|
||||
@ -318,6 +319,7 @@ pub fn get_schema_type_text(schema: &Schema, _style: ParameterDisplayStyle) -> S
|
||||
Schema::Object(_) => String::from("<object>"),
|
||||
Schema::Array(schema) => get_schema_type_text(schema.items, _style),
|
||||
Schema::AllOf(_) => String::from("<object>"),
|
||||
Schema::OneOf(_) => String::from("<object>"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -459,6 +461,11 @@ pub fn dump_api_return_schema(returns: &ReturnType, style: ParameterDisplayStyle
|
||||
res.push_str(&description);
|
||||
res.push_str(&dump_properties(all_of_schema, "", style, &[]));
|
||||
}
|
||||
Schema::OneOf(all_of_schema) => {
|
||||
let description = wrap_text("", "", all_of_schema.description, 80);
|
||||
res.push_str(&description);
|
||||
res.push_str(&dump_properties(all_of_schema, "", style, &[]));
|
||||
}
|
||||
}
|
||||
|
||||
res.push('\n');
|
||||
|
@ -4,6 +4,7 @@
|
||||
//! completely static API definitions that can be included within the programs read-only text
|
||||
//! segment.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::fmt;
|
||||
|
||||
use anyhow::{bail, format_err, Error};
|
||||
@ -693,18 +694,12 @@ impl AllOfSchema {
|
||||
|
||||
pub fn lookup(&self, key: &str) -> Option<(bool, &Schema)> {
|
||||
for entry in self.list {
|
||||
match entry {
|
||||
Schema::AllOf(s) => {
|
||||
if let Some(v) = s.lookup(key) {
|
||||
return Some(v);
|
||||
}
|
||||
}
|
||||
Schema::Object(s) => {
|
||||
if let Some(v) = s.lookup(key) {
|
||||
return Some(v);
|
||||
}
|
||||
}
|
||||
_ => panic!("non-object-schema in `AllOfSchema`"),
|
||||
if let Some(v) = entry
|
||||
.any_object()
|
||||
.expect("non-object-schema in `AllOfSchema`")
|
||||
.lookup(key)
|
||||
{
|
||||
return Some(v);
|
||||
}
|
||||
}
|
||||
|
||||
@ -724,6 +719,93 @@ impl AllOfSchema {
|
||||
}
|
||||
}
|
||||
|
||||
/// An object schema which is basically like a rust enum: exactly one variant may match.
|
||||
///
|
||||
/// Contrary to JSON Schema, we require there be a 'type' property to distinguish the types.
|
||||
/// In serde-language, we use an internally tagged enum representation.
|
||||
///
|
||||
/// Note that these are limited to object schemas. Other schemas will produce errors.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "test-harness", derive(Eq, PartialEq))]
|
||||
pub struct OneOfSchema {
|
||||
pub description: &'static str,
|
||||
|
||||
/// The type property entry.
|
||||
///
|
||||
/// This must be a static reference due to how we implemented the property iterator.
|
||||
pub type_property_entry: &'static SchemaPropertyEntry,
|
||||
|
||||
/// The parameter is checked against all of the schemas in the list. Note that all schemas must
|
||||
/// be object schemas.
|
||||
pub list: &'static [(&'static str, &'static Schema)],
|
||||
}
|
||||
|
||||
impl OneOfSchema {
|
||||
pub const fn new(
|
||||
description: &'static str,
|
||||
type_property_entry: &'static SchemaPropertyEntry,
|
||||
list: &'static [(&'static str, &'static Schema)],
|
||||
) -> Self {
|
||||
Self {
|
||||
description,
|
||||
type_property_entry,
|
||||
list,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn schema(self) -> Schema {
|
||||
Schema::OneOf(self)
|
||||
}
|
||||
|
||||
pub fn type_property(&self) -> &'static str {
|
||||
self.type_property_entry.0
|
||||
}
|
||||
|
||||
pub fn type_schema(&self) -> &'static Schema {
|
||||
self.type_property_entry.2
|
||||
}
|
||||
|
||||
pub fn lookup(&self, key: &str) -> Option<(bool, &Schema)> {
|
||||
if key == self.type_property() {
|
||||
return Some((false, self.type_schema()));
|
||||
}
|
||||
|
||||
for (_variant, entry) in self.list {
|
||||
if let Some(v) = entry
|
||||
.any_object()
|
||||
.expect("non-object-schema in `OneOfSchema`")
|
||||
.lookup(key)
|
||||
{
|
||||
return Some(v);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn lookup_variant(&self, name: &str) -> Option<&Schema> {
|
||||
Some(
|
||||
self.list[self
|
||||
.list
|
||||
.binary_search_by_key(&name, |(name, _)| name)
|
||||
.ok()?]
|
||||
.1,
|
||||
)
|
||||
}
|
||||
|
||||
/// Parse key/value pairs and verify with object schema
|
||||
///
|
||||
/// - `test_required`: is set, checks if all required properties are
|
||||
/// present.
|
||||
pub fn parse_parameter_strings(
|
||||
&'static self,
|
||||
data: &[(String, String)],
|
||||
test_required: bool,
|
||||
) -> Result<Value, ParameterError> {
|
||||
ParameterSchema::from(self).parse_parameter_strings(data, test_required)
|
||||
}
|
||||
}
|
||||
|
||||
/// Beside [`ObjectSchema`] we also have an [`AllOfSchema`] which also represents objects.
|
||||
pub trait ObjectSchemaType {
|
||||
fn description(&self) -> &'static str;
|
||||
@ -774,6 +856,23 @@ pub trait ObjectSchemaType {
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub enum ObjectPropertyIterator {
|
||||
Simple(SimpleObjectPropertyIterator),
|
||||
OneOf(OneOfPropertyIterator),
|
||||
}
|
||||
|
||||
impl Iterator for ObjectPropertyIterator {
|
||||
type Item = &'static SchemaPropertyEntry;
|
||||
|
||||
fn next(&mut self) -> Option<&'static SchemaPropertyEntry> {
|
||||
match self {
|
||||
Self::Simple(iter) => iter.next(),
|
||||
Self::OneOf(iter) => iter.next(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectSchemaType for ObjectSchema {
|
||||
fn description(&self) -> &'static str {
|
||||
self.description
|
||||
@ -784,11 +883,11 @@ impl ObjectSchemaType for ObjectSchema {
|
||||
}
|
||||
|
||||
fn properties(&self) -> ObjectPropertyIterator {
|
||||
ObjectPropertyIterator {
|
||||
ObjectPropertyIterator::Simple(SimpleObjectPropertyIterator {
|
||||
schemas: [].iter(),
|
||||
properties: Some(self.properties.iter()),
|
||||
nested: None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn additional_properties(&self) -> bool {
|
||||
@ -810,11 +909,11 @@ impl ObjectSchemaType for AllOfSchema {
|
||||
}
|
||||
|
||||
fn properties(&self) -> ObjectPropertyIterator {
|
||||
ObjectPropertyIterator {
|
||||
ObjectPropertyIterator::Simple(SimpleObjectPropertyIterator {
|
||||
schemas: self.list.iter(),
|
||||
properties: None,
|
||||
nested: None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn additional_properties(&self) -> bool {
|
||||
@ -823,11 +922,10 @@ impl ObjectSchemaType for AllOfSchema {
|
||||
|
||||
fn default_key(&self) -> Option<&'static str> {
|
||||
for schema in self.list {
|
||||
let default_key = match schema {
|
||||
Schema::Object(schema) => schema.default_key(),
|
||||
Schema::AllOf(schema) => schema.default_key(),
|
||||
_ => panic!("non-object-schema in `AllOfSchema`"),
|
||||
};
|
||||
let default_key = schema
|
||||
.any_object()
|
||||
.expect("non-object-schema in `AllOfSchema`")
|
||||
.default_key();
|
||||
|
||||
if default_key.is_some() {
|
||||
return default_key;
|
||||
@ -839,13 +937,13 @@ impl ObjectSchemaType for AllOfSchema {
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct ObjectPropertyIterator {
|
||||
pub struct SimpleObjectPropertyIterator {
|
||||
schemas: std::slice::Iter<'static, &'static Schema>,
|
||||
properties: Option<std::slice::Iter<'static, SchemaPropertyEntry>>,
|
||||
nested: Option<Box<ObjectPropertyIterator>>,
|
||||
}
|
||||
|
||||
impl Iterator for ObjectPropertyIterator {
|
||||
impl Iterator for SimpleObjectPropertyIterator {
|
||||
type Item = &'static SchemaPropertyEntry;
|
||||
|
||||
fn next(&mut self) -> Option<&'static SchemaPropertyEntry> {
|
||||
@ -859,6 +957,7 @@ impl Iterator for ObjectPropertyIterator {
|
||||
Some(item) => return Some(item),
|
||||
None => match self.schemas.next()? {
|
||||
Schema::AllOf(o) => self.nested = Some(Box::new(o.properties())),
|
||||
Schema::OneOf(o) => self.nested = Some(Box::new(o.properties())),
|
||||
Schema::Object(o) => self.properties = Some(o.properties.iter()),
|
||||
_ => {
|
||||
self.properties = None;
|
||||
@ -870,6 +969,93 @@ impl Iterator for ObjectPropertyIterator {
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectSchemaType for OneOfSchema {
|
||||
fn description(&self) -> &'static str {
|
||||
self.description
|
||||
}
|
||||
|
||||
fn lookup(&self, key: &str) -> Option<(bool, &Schema)> {
|
||||
OneOfSchema::lookup(self, key)
|
||||
}
|
||||
|
||||
fn properties(&self) -> ObjectPropertyIterator {
|
||||
ObjectPropertyIterator::OneOf(OneOfPropertyIterator {
|
||||
type_property_entry: self.type_property_entry,
|
||||
schemas: self.list.iter(),
|
||||
done: HashSet::new(),
|
||||
nested: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn additional_properties(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn default_key(&self) -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn verify_json(&self, data: &Value) -> Result<(), Error> {
|
||||
let map = match data {
|
||||
Value::Object(ref map) => map,
|
||||
Value::Array(_) => bail!("Expected object - got array."),
|
||||
_ => bail!("Expected object - got scalar value."),
|
||||
};
|
||||
|
||||
// Without the type we also cannot verify anything else...:
|
||||
let variant = match map.get(self.type_property()) {
|
||||
None => bail!("Missing '{}' property", self.type_property()),
|
||||
Some(Value::String(v)) => v,
|
||||
_ => bail!("Expected string in '{}'", self.type_property()),
|
||||
};
|
||||
|
||||
let schema = self
|
||||
.lookup_variant(variant)
|
||||
.ok_or_else(|| format_err!("invalid '{}': {}", self.type_property(), variant))?;
|
||||
|
||||
schema.verify_json(data)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct OneOfPropertyIterator {
|
||||
type_property_entry: &'static SchemaPropertyEntry,
|
||||
schemas: std::slice::Iter<'static, (&'static str, &'static Schema)>,
|
||||
done: HashSet<&'static str>,
|
||||
nested: Option<Box<ObjectPropertyIterator>>,
|
||||
}
|
||||
|
||||
impl Iterator for OneOfPropertyIterator {
|
||||
type Item = &'static SchemaPropertyEntry;
|
||||
|
||||
fn next(&mut self) -> Option<&'static SchemaPropertyEntry> {
|
||||
if self.done.insert(self.type_property_entry.0) {
|
||||
return Some(self.type_property_entry);
|
||||
}
|
||||
|
||||
loop {
|
||||
match self.nested.as_mut().and_then(Iterator::next) {
|
||||
Some(item) => {
|
||||
if !self.done.insert(item.0) {
|
||||
continue;
|
||||
}
|
||||
return Some(item);
|
||||
}
|
||||
None => self.nested = None,
|
||||
}
|
||||
|
||||
self.nested = Some(Box::new(
|
||||
self.schemas
|
||||
.next()?
|
||||
.1
|
||||
.any_object()
|
||||
.expect("non-object-schema in `OneOfSchema`")
|
||||
.properties(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Schemas are used to describe complex data types.
|
||||
///
|
||||
/// All schema types implement constant builder methods, and a final
|
||||
@ -910,6 +1096,7 @@ pub enum Schema {
|
||||
Object(ObjectSchema),
|
||||
Array(ArraySchema),
|
||||
AllOf(AllOfSchema),
|
||||
OneOf(OneOfSchema),
|
||||
}
|
||||
|
||||
impl Schema {
|
||||
@ -928,6 +1115,7 @@ impl Schema {
|
||||
Schema::Number(s) => s.verify_json(data)?,
|
||||
Schema::String(s) => s.verify_json(data)?,
|
||||
Schema::AllOf(s) => s.verify_json(data)?,
|
||||
Schema::OneOf(s) => s.verify_json(data)?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -1072,6 +1260,14 @@ impl Schema {
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the underlying [`OneOfSchema`], panics on different schemas.
|
||||
pub const fn unwrap_one_of_schema(&self) -> &OneOfSchema {
|
||||
match self {
|
||||
Schema::OneOf(s) => s,
|
||||
_ => panic!("unwrap_one_of_schema on different schema"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the underlying [`BooleanSchema`].
|
||||
pub const fn boolean(&self) -> Option<&BooleanSchema> {
|
||||
match self {
|
||||
@ -1128,10 +1324,19 @@ impl Schema {
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the underlying [`AllOfSchema`].
|
||||
pub const fn one_of(&self) -> Option<&OneOfSchema> {
|
||||
match self {
|
||||
Schema::OneOf(s) => Some(s),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn any_object(&self) -> Option<&dyn ObjectSchemaType> {
|
||||
match self {
|
||||
Schema::Object(s) => Some(s),
|
||||
Schema::AllOf(s) => Some(s),
|
||||
Schema::OneOf(s) => Some(s),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@ -1295,6 +1500,7 @@ impl PartialEq for ApiStringFormat {
|
||||
pub enum ParameterSchema {
|
||||
Object(&'static ObjectSchema),
|
||||
AllOf(&'static AllOfSchema),
|
||||
OneOf(&'static OneOfSchema),
|
||||
}
|
||||
|
||||
impl ParameterSchema {
|
||||
@ -1316,6 +1522,7 @@ impl ObjectSchemaType for ParameterSchema {
|
||||
match self {
|
||||
ParameterSchema::Object(o) => o.description(),
|
||||
ParameterSchema::AllOf(o) => o.description(),
|
||||
ParameterSchema::OneOf(o) => o.description(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1323,6 +1530,7 @@ impl ObjectSchemaType for ParameterSchema {
|
||||
match self {
|
||||
ParameterSchema::Object(o) => o.lookup(key),
|
||||
ParameterSchema::AllOf(o) => o.lookup(key),
|
||||
ParameterSchema::OneOf(o) => o.lookup(key),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1330,6 +1538,7 @@ impl ObjectSchemaType for ParameterSchema {
|
||||
match self {
|
||||
ParameterSchema::Object(o) => o.properties(),
|
||||
ParameterSchema::AllOf(o) => o.properties(),
|
||||
ParameterSchema::OneOf(o) => o.properties(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1337,6 +1546,7 @@ impl ObjectSchemaType for ParameterSchema {
|
||||
match self {
|
||||
ParameterSchema::Object(o) => o.additional_properties(),
|
||||
ParameterSchema::AllOf(o) => o.additional_properties(),
|
||||
ParameterSchema::OneOf(o) => o.additional_properties(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1344,6 +1554,7 @@ impl ObjectSchemaType for ParameterSchema {
|
||||
match self {
|
||||
ParameterSchema::Object(o) => o.default_key(),
|
||||
ParameterSchema::AllOf(o) => o.default_key(),
|
||||
ParameterSchema::OneOf(o) => o.default_key(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1360,6 +1571,12 @@ impl From<&'static AllOfSchema> for ParameterSchema {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static OneOfSchema> for ParameterSchema {
|
||||
fn from(schema: &'static OneOfSchema) -> Self {
|
||||
ParameterSchema::OneOf(schema)
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to parse boolean values
|
||||
///
|
||||
/// - true: `1 | on | yes | true`
|
||||
|
Loading…
Reference in New Issue
Block a user