diff --git a/src/builder.rs b/src/builder.rs index c143a57..aff1ecd 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -5442,7 +5442,7 @@ mod tests { let error = ThingBuilder::::new("MyLampThing") .finish_extend() - .uri_variable("uriVariable", |b| b.finish_extend().array()) + .uri_variable("uriVariable", |b| b.finish_extend().vec()) .build() .unwrap_err(); @@ -5465,7 +5465,7 @@ mod tests { .finish_extend() .property("property", |b| { b.finish_extend_data_schema() - .uri_variable("uriVariable", |b| b.finish_extend().array()) + .uri_variable("uriVariable", |b| b.finish_extend().vec()) .string() }) .build() diff --git a/src/builder/affordance.rs b/src/builder/affordance.rs index de91e8e..a594013 100644 --- a/src/builder/affordance.rs +++ b/src/builder/affordance.rs @@ -19,15 +19,16 @@ use crate::{ use super::{ data_schema::{ - buildable_data_schema_delegate, impl_inner_delegate_schema_builder_like_array, - impl_inner_delegate_schema_builder_like_integer, + buildable_data_schema_delegate, impl_inner_delegate_schema_builder_like_integer, impl_inner_delegate_schema_builder_like_number, - impl_inner_delegate_schema_builder_like_object, uri_variables_contains_arrays_objects, - ArrayDataSchemaBuilderLike, BuildableDataSchema, DataSchemaBuilder, EnumerableDataSchema, - IntegerDataSchemaBuilderLike, NumberDataSchemaBuilderLike, ObjectDataSchemaBuilderLike, - PartialDataSchema, PartialDataSchemaBuilder, ReadableWriteableDataSchema, - SpecializableDataSchema, UncheckedDataSchemaFromOther, UncheckedDataSchemaMap, - UnionDataSchema, + impl_inner_delegate_schema_builder_like_object, + impl_inner_delegate_schema_builder_like_tuple, impl_inner_delegate_schema_builder_like_vec, + uri_variables_contains_arrays_objects, BuildableDataSchema, DataSchemaBuilder, + EnumerableDataSchema, IntegerDataSchemaBuilderLike, NumberDataSchemaBuilderLike, + ObjectDataSchemaBuilderLike, PartialDataSchema, PartialDataSchemaBuilder, + ReadableWriteableDataSchema, SpecializableDataSchema, TupleDataSchemaBuilderLike, + UncheckedDataSchemaFromOther, UncheckedDataSchemaMap, UnionDataSchema, + VecDataSchemaBuilderLike, }, human_readable_info::{ impl_delegate_buildable_hr_info, BuildableHumanReadableInfo, HumanReadableInfo, @@ -1592,9 +1593,15 @@ where OtherInteractionAffordance, OtherPropertyAffordance, >; - type Array = PropertyAffordanceBuilder< + type Tuple = PropertyAffordanceBuilder< Other, - DataSchema::Array, + DataSchema::Tuple, + OtherInteractionAffordance, + OtherPropertyAffordance, + >; + type Vec = PropertyAffordanceBuilder< + Other, + DataSchema::Vec, OtherInteractionAffordance, OtherPropertyAffordance, >; @@ -1630,8 +1637,10 @@ where >; impl_property_affordance_builder_delegator!( - array where AS: Default => Self::Array, - array_ext(f: F) where F: FnOnce(AS::Empty) -> AS, AS: Extendable => Self::Array, + tuple where AS: Default => Self::Tuple, + tuple_ext(f: F) where F: FnOnce(AS::Empty) -> AS, AS: Extendable => Self::Tuple, + vec where AS: Default => Self::Vec, + vec_ext(f: F) where F: FnOnce(AS::Empty) -> AS, AS: Extendable => Self::Vec, bool => Self::Stateless, number => Self::Number, integer => Self::Integer, @@ -1789,13 +1798,23 @@ where } impl - ArrayDataSchemaBuilderLike + TupleDataSchemaBuilderLike for PropertyAffordanceBuilder where Other: ExtendableThing, - CDS: ArrayDataSchemaBuilderLike, + CDS: TupleDataSchemaBuilderLike, { - impl_inner_delegate_schema_builder_like_array!(data_schema); + impl_inner_delegate_schema_builder_like_tuple!(data_schema); +} + +impl + VecDataSchemaBuilderLike + for PropertyAffordanceBuilder +where + Other: ExtendableThing, + CDS: VecDataSchemaBuilderLike, +{ + impl_inner_delegate_schema_builder_like_vec!(data_schema); } impl @@ -2777,8 +2796,8 @@ mod test { }, hlist::{Cons, Nil}, thing::{ - DataSchemaFromOther, DataSchemaSubtype, DefaultedFormOperations, FormOperation, - Minimum, NumberSchema, + ArraySchema, BoxedElemOrVec, DataSchemaFromOther, DataSchemaSubtype, + DefaultedFormOperations, FormOperation, Minimum, NumberSchema, }, }; @@ -3771,4 +3790,80 @@ mod test { Error::InvalidLanguageTag("i1t".to_string()), ); } + + #[test] + fn delegate_vec_methods() { + let builder = PropertyAffordanceBuilder::, (), ()>::default() + .vec() + .read_only() + .min_items(3) + .max_items(5) + .set_item(|b| b.finish_extend().integer()) + .into_usable(); + + assert_eq!( + builder.build().unwrap(), + PropertyAffordance { + interaction: Default::default(), + data_schema: DataSchema { + read_only: true, + subtype: Some(DataSchemaSubtype::Array(ArraySchema { + items: Some(BoxedElemOrVec::Elem(Box::new(DataSchema { + subtype: Some(DataSchemaSubtype::Integer(Default::default())), + other: Nil, + ..Default::default() + }))), + min_items: Some(3), + max_items: Some(5), + other: Nil, + })), + other: Nil, + ..Default::default() + }, + observable: Default::default(), + other: Nil, + } + ) + } + + #[test] + fn delegate_tuple_methods() { + let builder = PropertyAffordanceBuilder::, (), ()>::default() + .tuple() + .read_only() + .append(|b| b.finish_extend().integer()) + .append(|b| b.finish_extend().string()) + .into_usable(); + + assert_eq!( + builder.build().unwrap(), + PropertyAffordance { + interaction: Default::default(), + data_schema: DataSchema { + read_only: true, + subtype: Some(DataSchemaSubtype::Array(ArraySchema { + items: Some(BoxedElemOrVec::Vec(vec![ + DataSchema { + subtype: Some(DataSchemaSubtype::Integer(Default::default())), + other: Nil, + ..Default::default() + }, + DataSchema { + subtype: Some(DataSchemaSubtype::String(Default::default())), + other: Nil, + ..Default::default() + }, + ])), + min_items: None, + max_items: None, + other: Nil, + })), + other: Nil, + ..Default::default() + }, + observable: Default::default(), + other: Nil, + } + ) + } } diff --git a/src/builder/data_schema.rs b/src/builder/data_schema.rs index 4e15979..55d3d4a 100644 --- a/src/builder/data_schema.rs +++ b/src/builder/data_schema.rs @@ -44,9 +44,9 @@ use std::{cmp::Ordering, collections::HashMap, marker::PhantomData, num::NonZero use crate::{ extend::{Extend, Extendable, ExtendableThing}, thing::{ - ArraySchema, DataSchema, DataSchemaSubtype, IntegerSchema, Maximum, Minimum, NumberSchema, - ObjectSchema, StringSchema, UncheckedArraySchema, UncheckedDataSchemaSubtype, - UncheckedObjectSchema, + ArraySchema, BoxedElemOrVec, DataSchema, DataSchemaSubtype, IntegerSchema, Maximum, + Minimum, NumberSchema, ObjectSchema, StringSchema, UncheckedArraySchema, + UncheckedDataSchemaSubtype, UncheckedObjectSchema, }, }; @@ -459,8 +459,11 @@ pub trait SpecializableDataSchema: BuildableDataSchema; - /// The _array_ specialization of the data schema builder. - type Array: BuildableDataSchema; + /// The _array_ specialization of the data schema builder, representing a tuple of items. + type Tuple: BuildableDataSchema; + + /// The _array_ specialization of the data schema builder, representing an _homogeneous list_. + type Vec: BuildableDataSchema; /// The _number_ specialization of the data schema builder. type Number: BuildableDataSchema; @@ -477,13 +480,13 @@ pub trait SpecializableDataSchema: BuildableDataSchema; - /// Specialize the builder into an _array_ data schema builder, initializing the array - /// extensions with default values. + /// Specialize the builder into an _array_ data schema builder representing a tuple, + /// initializing the array extensions with default values. /// /// Note that this function can only be called if `AS` implements [`Default`], use - /// [`array_ext`] otherwise. + /// [`tuple_ext`] otherwise. /// - /// [`array_ext`]: Self::array_ext + /// [`tuple_ext`]: Self::tuple_ext /// /// # Examples /// ``` @@ -517,7 +520,7 @@ pub trait SpecializableDataSchema: BuildableDataSchema: BuildableDataSchema: BuildableDataSchema: BuildableDataSchema: BuildableDataSchema: BuildableDataSchema Self::Array + fn tuple(self) -> Self::Tuple where AS: Default; - /// Specialize the builder into an _array_ data schema builder, passing a function to - /// create the array extensions. + /// Specialize the builder into an _array_ data schema builder to represent a _homogeneous + /// list_, initializing the array extensions with default values. + /// + /// Note that this function can only be called if `AS` implements [`Default`], use + /// [`vec_ext`] otherwise. + /// + /// [`vec_ext`]: Self::vec_ext + /// + /// # Examples + /// ``` + /// # use serde::{Deserialize, Serialize}; + /// # use serde_json::json; + /// # use wot_td::{ + /// # builder::data_schema::SpecializableDataSchema, extend::ExtendableThing, thing::Thing, + /// # }; + /// # + /// #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] + /// struct ThingExtension {} + /// + /// #[derive(Debug, Default, PartialEq, Serialize, Deserialize)] + /// struct ArraySchemaExtension { + /// array_field: u32, + /// } + /// + /// impl ExtendableThing for ThingExtension { + /// type ArraySchema = ArraySchemaExtension; + /// /* Other types set to `()` */ + /// # type Form = (); + /// # type InteractionAffordance = (); + /// # type PropertyAffordance = (); + /// # type ActionAffordance = (); + /// # type EventAffordance = (); + /// # type ExpectedResponse = (); + /// # type DataSchema = (); + /// # type ObjectSchema = (); + /// } + /// + /// let thing = Thing::builder("Thing name") + /// .ext(ThingExtension {}) + /// .finish_extend() + /// .schema_definition("test", |b| b.ext(()).finish_extend().vec()) + /// .build() + /// .unwrap(); + /// + /// assert_eq!( + /// serde_json::to_value(thing).unwrap(), + /// json!({ + /// "@context": "https://www.w3.org/2022/wot/td/v1.1", + /// "title": "Thing name", + /// "schemaDefinitions": { + /// "test": { + /// "type": "array", + /// "array_field": 0, + /// "readOnly": false, + /// "writeOnly": false, + /// } + /// }, + /// "security": [], + /// "securityDefinitions": {}, + /// }) + /// ); + /// ``` + /// + /// The following does not work instead: + /// + /// ```compile_fail + /// # use serde::{Deserialize, Serialize}; + /// # use serde_json::json; + /// # use wot_td::{ + /// # builder::data_schema::SpecializableDataSchema, extend::ExtendableThing, thing::Thing, + /// # }; + /// # + /// # #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] + /// # struct ThingExtension {} + /// # + /// #[derive(Debug, PartialEq, Serialize, Deserialize)] + /// struct NotDefaultableU32(u32); + /// + /// #[derive(Debug, PartialEq, Serialize, Deserialize)] + /// struct ArraySchemaExtension { + /// array_field: NotDefaultableU32, + /// } + /// + /// # impl ExtendableThing for ThingExtension { + /// # type ArraySchema = ArraySchemaExtension; + /// # /* Other types set to `()` */ + /// # type Form = (); + /// # type InteractionAffordance = (); + /// # type PropertyAffordance = (); + /// # type ActionAffordance = (); + /// # type EventAffordance = (); + /// # type ExpectedResponse = (); + /// # type DataSchema = (); + /// # type ObjectSchema = (); + /// # } + /// # + /// let thing = Thing::builder("Thing name") + /// .ext(ThingExtension {}) + /// .finish_extend() + /// .schema_definition("test", |b| b.ext(()).finish_extend().vec()) + /// .build() + /// .unwrap(); + /// ``` + /// + /// In this case, the following is necessary: + /// + /// ``` + /// # use serde::{Deserialize, Serialize}; + /// # use serde_json::json; + /// # use wot_td::{ + /// # builder::data_schema::SpecializableDataSchema, + /// # extend::{Extend, ExtendableThing}, + /// # thing::Thing, + /// # }; + /// # + /// # #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] + /// # struct ThingExtension {} + /// # + /// # #[derive(Debug, PartialEq, Serialize, Deserialize)] + /// # struct NotDefaultableU32(u32); + /// # + /// # #[derive(Debug, PartialEq, Serialize, Deserialize)] + /// # struct ArraySchemaExtension { + /// # array_field: NotDefaultableU32, + /// # } + /// # + /// # impl ExtendableThing for ThingExtension { + /// # type ArraySchema = ArraySchemaExtension; + /// # /* Other types set to `()` */ + /// # type Form = (); + /// # type InteractionAffordance = (); + /// # type PropertyAffordance = (); + /// # type ActionAffordance = (); + /// # type EventAffordance = (); + /// # type ExpectedResponse = (); + /// # type DataSchema = (); + /// # type ObjectSchema = (); + /// # } + /// # + /// let thing = Thing::builder("Thing name") + /// .ext(ThingExtension {}) + /// .finish_extend() + /// .schema_definition("test", |b| { + /// b.ext(()).finish_extend().vec_ext(|b| { + /// b.ext(ArraySchemaExtension { + /// array_field: NotDefaultableU32(42), + /// }) + /// }) + /// }) + /// .build() + /// .unwrap(); + /// # + /// # assert_eq!( + /// # serde_json::to_value(thing).unwrap(), + /// # json!({ + /// # "@context": "https://www.w3.org/2022/wot/td/v1.1", + /// # "title": "Thing name", + /// # "schemaDefinitions": { + /// # "test": { + /// # "type": "array", + /// # "array_field": 42, + /// # "readOnly": false, + /// # "writeOnly": false, + /// # } + /// # }, + /// # "security": [], + /// # "securityDefinitions": {}, + /// # }) + /// # ); + /// ``` + fn vec(self) -> Self::Vec + where + AS: Default; + + /// Specialize the builder into an _array_ data schema builder representing a tuple, passing a + /// function to create the array extensions. /// /// # Example /// @@ -693,7 +872,7 @@ pub trait SpecializableDataSchema: BuildableDataSchema: BuildableDataSchema: BuildableDataSchema(self, f: F) -> Self::Array + fn tuple_ext(self, f: F) -> Self::Tuple + where + F: FnOnce(AS::Empty) -> AS, + AS: Extendable; + + /// Specialize the builder into an _array_ data schema builder representing a _homogeneous + /// list_, passing a function to create the array extensions. + /// + /// # Example + /// + /// ``` + /// # use serde::{Deserialize, Serialize}; + /// # use serde_json::json; + /// # use wot_td::{ + /// # builder::data_schema::SpecializableDataSchema, + /// # extend::{Extend, ExtendableThing}, + /// # thing::Thing, + /// # }; + /// # + /// #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] + /// struct ThingExtension {} + /// + /// #[derive(Debug, PartialEq, Serialize, Deserialize)] + /// struct NotDefaultableU32(u32); + /// + /// #[derive(Debug, PartialEq, Serialize, Deserialize)] + /// struct ArraySchemaExtension { + /// array_field: NotDefaultableU32, + /// } + /// + /// impl ExtendableThing for ThingExtension { + /// type ArraySchema = ArraySchemaExtension; + /// # /* Other types set to `()` */ + /// # type Form = (); + /// # type InteractionAffordance = (); + /// # type PropertyAffordance = (); + /// # type ActionAffordance = (); + /// # type EventAffordance = (); + /// # type ExpectedResponse = (); + /// # type DataSchema = (); + /// # type ObjectSchema = (); + /// } + /// + /// let thing = Thing::builder("Thing name") + /// .ext(ThingExtension {}) + /// .finish_extend() + /// .schema_definition("test", |b| { + /// b.ext(()).finish_extend().vec_ext(|b| { + /// b.ext(ArraySchemaExtension { + /// array_field: NotDefaultableU32(42), + /// }) + /// }) + /// }) + /// .build() + /// .unwrap(); + /// + /// assert_eq!( + /// serde_json::to_value(thing).unwrap(), + /// json!({ + /// "@context": "https://www.w3.org/2022/wot/td/v1.1", + /// "title": "Thing name", + /// "schemaDefinitions": { + /// "test": { + /// "type": "array", + /// "array_field": 42, + /// "readOnly": false, + /// "writeOnly": false, + /// } + /// }, + /// "security": [], + /// "securityDefinitions": {}, + /// }) + /// ); + /// ``` + fn vec_ext(self, f: F) -> Self::Vec where F: FnOnce(AS::Empty) -> AS, AS: Extendable; @@ -1219,10 +1473,21 @@ pub trait ReadableWriteableDataSchema: fn write_only(self) -> Self::WriteOnly; } -/// The builder for an [`ArraySchema`](crate::thing::ArraySchema) builder. -pub struct ArrayDataSchemaBuilder { +/// The builder for an [`ArraySchema`](crate::thing::ArraySchema) builder with a set of `items` to +/// represent a tuple of elements. +pub struct TupleDataSchemaBuilder { inner: Inner, items: Vec>, + + /// Array data schema extension. + pub other: AS, +} + +/// The builder for an [`ArraySchema`](crate::thing::ArraySchema) builder with a single `item` to +/// represent the underlying type of a _homogeneous list_. +pub struct VecDataSchemaBuilder { + inner: Inner, + item: Option>, min_items: Option, max_items: Option, @@ -1321,18 +1586,71 @@ macro_rules! opt_field_into_decl { }; } -/// An interface for things behaving like an array data schema builder. -pub trait ArrayDataSchemaBuilderLike { +/// An interface for things behaving like an array data schema builder representing a _homogeneous +/// list_. +pub trait VecDataSchemaBuilderLike { opt_field_decl!(min_items: u32, max_items: u32); - /// Append an element to the array. + /// Sets the data schema of the underlying type. + /// + /// # Example + /// + /// ``` + /// # use serde_json::json; + /// # use wot_td::{ + /// # builder::data_schema::{VecDataSchemaBuilderLike, SpecializableDataSchema}, + /// # thing::Thing, + /// # }; + /// # + /// let thing = Thing::builder("Thing name") + /// .finish_extend() + /// .schema_definition("test", |b| { + /// b.finish_extend() + /// .vec() + /// .set_item(|b| b.finish_extend().number()) + /// }) + /// .build() + /// .unwrap(); + /// + /// assert_eq!( + /// serde_json::to_value(thing).unwrap(), + /// json!({ + /// "@context": "https://www.w3.org/2022/wot/td/v1.1", + /// "title": "Thing name", + /// "schemaDefinitions": { + /// "test": { + /// "type": "array", + /// "items": { + /// "type": "number", + /// "readOnly": false, + /// "writeOnly": false, + /// }, + /// "readOnly": false, + /// "writeOnly": false, + /// } + /// }, + /// "security": [], + /// "securityDefinitions": {}, + /// }) + /// ); + /// ``` + fn set_item(self, f: F) -> Self + where + F: FnOnce(DataSchemaBuilder<::Empty, AS, OS, ToExtend>) -> T, + DS: Extendable, + T: Into>; +} + +/// An interface for things behaving like an array data schema builder representing a tuple. +pub trait TupleDataSchemaBuilderLike { + /// Append an element to the tuple of inner data schemas. /// /// # Example /// /// ``` /// # use serde_json::json; /// # use wot_td::{ - /// # builder::data_schema::{ArrayDataSchemaBuilderLike, SpecializableDataSchema}, + /// # builder::data_schema::{TupleDataSchemaBuilderLike, SpecializableDataSchema}, /// # thing::Thing, /// # }; /// # @@ -1340,7 +1658,7 @@ pub trait ArrayDataSchemaBuilderLike { /// .finish_extend() /// .schema_definition("test", |b| { /// b.finish_extend() - /// .array() + /// .tuple() /// .append(|b| b.finish_extend().number()) /// .append(|b| b.finish_extend().null()) /// }) @@ -1504,13 +1822,11 @@ macro_rules! opt_field_into_builder { }; } -impl ArrayDataSchemaBuilderLike - for ArrayDataSchemaBuilder +impl TupleDataSchemaBuilderLike + for TupleDataSchemaBuilder where Inner: BuildableDataSchema, { - opt_field_builder!(min_items: u32, max_items: u32); - fn append(mut self, f: F) -> Self where F: FnOnce(DataSchemaBuilder<::Empty, AS, OS, ToExtend>) -> T, @@ -1523,6 +1839,24 @@ where } } +impl VecDataSchemaBuilderLike + for VecDataSchemaBuilder +where + Inner: BuildableDataSchema, +{ + opt_field_builder!(min_items: u32, max_items: u32); + + fn set_item(mut self, f: F) -> Self + where + F: FnOnce(DataSchemaBuilder<::Empty, AS, OS, ToExtend>) -> T, + DS: Extendable, + T: Into>, + { + self.item = Some(f(DataSchemaBuilder::::empty()).into()); + self + } +} + impl, DS, AS, OS> NumberDataSchemaBuilderLike for NumberDataSchemaBuilder { @@ -1610,7 +1944,7 @@ impl, DS, AS, OS> ); } -macro_rules! impl_inner_delegate_schema_builder_like_array { +macro_rules! impl_inner_delegate_schema_builder_like_vec { ($inner:ident) => { #[inline] fn min_items(mut self, value: u32) -> Self { @@ -1624,6 +1958,28 @@ macro_rules! impl_inner_delegate_schema_builder_like_array { self } + #[inline] + fn set_item(mut self, f: F) -> Self + where + F: FnOnce( + crate::builder::data_schema::DataSchemaBuilder< + ::Empty, + AS, + OS, + crate::builder::ToExtend, + >, + ) -> T, + DS: Extendable, + T: Into>, + { + self.$inner = self.$inner.set_item(f); + self + } + }; +} + +macro_rules! impl_inner_delegate_schema_builder_like_tuple { + ($inner:ident) => { #[inline] fn append(mut self, f: F) -> Self where @@ -1737,8 +2093,12 @@ macro_rules! impl_inner_delegate_schema_builder_like_object { macro_rules! impl_delegate_schema_builder_like { ($( $ty:ident <$( $generic:ident ),+> on $inner:ident ),+ $(,)?) => { $( - impl),+ > crate::builder::data_schema::ArrayDataSchemaBuilderLike for $ty< $($generic),+ > { - crate::builder::data_schema::impl_inner_delegate_schema_builder_like_array!($inner); + impl),+ > crate::builder::data_schema::VecDataSchemaBuilderLike for $ty< $($generic),+ > { + crate::builder::data_schema::impl_inner_delegate_schema_builder_like_vec!($inner); + } + + impl),+ > crate::builder::data_schema::TupleDataSchemaBuilderLike for $ty< $($generic),+ > { + crate::builder::data_schema::impl_inner_delegate_schema_builder_like_tuple!($inner); } impl),+ > crate::builder::data_schema::NumberDataSchemaBuilderLike for $ty< $($generic),+ > { @@ -1756,10 +2116,11 @@ macro_rules! impl_delegate_schema_builder_like { }; } pub(super) use impl_delegate_schema_builder_like; -pub(super) use impl_inner_delegate_schema_builder_like_array; pub(super) use impl_inner_delegate_schema_builder_like_integer; pub(super) use impl_inner_delegate_schema_builder_like_number; pub(super) use impl_inner_delegate_schema_builder_like_object; +pub(super) use impl_inner_delegate_schema_builder_like_tuple; +pub(super) use impl_inner_delegate_schema_builder_like_vec; impl_delegate_schema_builder_like!(ReadOnly on inner, WriteOnly on inner); @@ -1835,7 +2196,8 @@ macro_rules! impl_delegate_buildable_data_schema { } impl_delegate_buildable_data_schema!( - ArrayDataSchemaBuilder, + TupleDataSchemaBuilder, + VecDataSchemaBuilder, NumberDataSchemaBuilder, IntegerDataSchemaBuilder, ObjectDataSchemaBuilder, @@ -1897,7 +2259,8 @@ impl BuildableDataSchema } impl_delegate_buildable_hr_info!( - ArrayDataSchemaBuilder on inner, + TupleDataSchemaBuilder on inner, + VecDataSchemaBuilder on inner, NumberDataSchemaBuilder on inner, IntegerDataSchemaBuilder on inner, ObjectDataSchemaBuilder on inner, @@ -1914,36 +2277,62 @@ macro_rules! impl_specializable_data_schema { $( impl SpecializableDataSchema for $ty { type Stateless = StatelessDataSchemaBuilder; - type Array = ArrayDataSchemaBuilder; + type Tuple = TupleDataSchemaBuilder; + type Vec = VecDataSchemaBuilder; type Number = NumberDataSchemaBuilder; type Integer = IntegerDataSchemaBuilder; type Object = ObjectDataSchemaBuilder; type String = StringDataSchemaBuilder; type Constant = ReadOnly>; - fn array(self) -> Self::Array + fn tuple(self) -> Self::Tuple where AS: Default { - ArrayDataSchemaBuilder { + TupleDataSchemaBuilder { + inner: self, + items: Default::default(), + other: Default::default(), + } + } + + fn tuple_ext(self, f: F) -> Self::Tuple + where + F: FnOnce(AS::Empty) -> AS, + AS: Extendable, + { + let other = f(AS::empty()); + + TupleDataSchemaBuilder { inner: self, items: Default::default(), + other, + } + } + + fn vec(self) -> Self::Vec + where + AS: Default + { + VecDataSchemaBuilder { + inner: self, + item: Default::default(), min_items: Default::default(), max_items: Default::default(), other: Default::default(), } } - fn array_ext(self, f: F) -> Self::Array + fn vec_ext(self, f: F) -> Self::Vec where F: FnOnce(AS::Empty) -> AS, AS: Extendable, { let other = f(AS::empty()); - ArrayDataSchemaBuilder { + VecDataSchemaBuilder { inner: self, - items: Default::default(), + item: Default::default(), min_items: Default::default(), max_items: Default::default(), other, @@ -2234,8 +2623,10 @@ macro_rules! impl_rw_data_schema { impl_rw_data_schema!( StatelessDataSchemaBuilder>; inner.partial, StatelessDataSchemaBuilder>; inner, - ArrayDataSchemaBuilder, DS, AS, OS>; inner.partial, - ArrayDataSchemaBuilder, DS, AS, OS>; inner, + TupleDataSchemaBuilder, DS, AS, OS>; inner.partial, + TupleDataSchemaBuilder, DS, AS, OS>; inner, + VecDataSchemaBuilder, DS, AS, OS>; inner.partial, + VecDataSchemaBuilder, DS, AS, OS>; inner, NumberDataSchemaBuilder>; inner.partial, NumberDataSchemaBuilder>; inner, IntegerDataSchemaBuilder>; inner.partial, @@ -2403,14 +2794,76 @@ where } } -impl From> for UncheckedDataSchema +impl From> for UncheckedDataSchema where T: Into>, { - fn from(builder: ArrayDataSchemaBuilder) -> Self { - let ArrayDataSchemaBuilder { + fn from(builder: TupleDataSchemaBuilder) -> Self { + let TupleDataSchemaBuilder { inner, items, + other: other_array_schema, + } = builder; + let DataSchemaBuilder { + partial: + PartialDataSchemaBuilder { + constant: _, + default, + unit, + one_of: _, + enumeration: _, + read_only, + write_only, + format, + other: other_data_schema, + _marker: _, + }, + info: + HumanReadableInfo { + attype, + title, + titles, + description, + descriptions, + }, + } = inner.into(); + + let items = Some(BoxedElemOrVec::Vec(items)); + let subtype = Some(UncheckedDataSchemaSubtype::Array(UncheckedArraySchema { + items, + min_items: None, + max_items: None, + other: other_array_schema, + })); + + UncheckedDataSchema { + attype, + title, + titles, + description, + descriptions, + constant: None, + default, + unit, + one_of: None, + enumeration: None, + read_only, + write_only, + format, + subtype, + other: other_data_schema, + } + } +} + +impl From> for UncheckedDataSchema +where + T: Into>, +{ + fn from(builder: VecDataSchemaBuilder) -> Self { + let VecDataSchemaBuilder { + inner, + item, min_items, max_items, other: other_array_schema, @@ -2439,7 +2892,7 @@ where }, } = inner.into(); - let items = items.is_empty().not().then_some(items); + let items = item.map(|item| BoxedElemOrVec::Elem(Box::new(item))); let subtype = Some(UncheckedDataSchemaSubtype::Array(UncheckedArraySchema { items, min_items, @@ -2467,26 +2920,84 @@ where } } -impl TryFrom> for DataSchema +impl TryFrom> for DataSchema +where + T: Into>, +{ + type Error = Error; + + fn try_from(value: TupleDataSchemaBuilder) -> Result { + let data_schema: UncheckedDataSchema<_, _, _> = value.into(); + data_schema.try_into() + } +} + +impl TryFrom> for DataSchema where T: Into>, { type Error = Error; - fn try_from(value: ArrayDataSchemaBuilder) -> Result { + fn try_from(value: VecDataSchemaBuilder) -> Result { let data_schema: UncheckedDataSchema<_, _, _> = value.into(); data_schema.try_into() } } -impl From> for PartialDataSchema +impl From> for PartialDataSchema where T: Into>, { - fn from(builder: ArrayDataSchemaBuilder) -> Self { - let ArrayDataSchemaBuilder { + fn from(builder: TupleDataSchemaBuilder) -> Self { + let TupleDataSchemaBuilder { inner, items, + other: other_array_schema, + } = builder; + let PartialDataSchemaBuilder { + constant: _, + default, + unit, + one_of: _, + enumeration: _, + read_only, + write_only, + format, + other: other_data_schema, + _marker: _, + } = inner.into(); + + let items = Some(BoxedElemOrVec::Vec(items)); + let subtype = Some(UncheckedDataSchemaSubtype::Array(UncheckedArraySchema { + items, + min_items: None, + max_items: None, + other: other_array_schema, + })); + + PartialDataSchema { + constant: None, + default, + unit, + one_of: None, + enumeration: None, + read_only, + write_only, + format, + subtype, + other: other_data_schema, + } + } +} + +impl From> for PartialDataSchema +where + T: Into>, +{ + fn from(builder: VecDataSchemaBuilder) -> Self { + let VecDataSchemaBuilder { + inner, + item, min_items, max_items, other: other_array_schema, @@ -2504,7 +3015,7 @@ where _marker: _, } = inner.into(); - let items = items.is_empty().not().then_some(items); + let items = item.map(|item| BoxedElemOrVec::Elem(Box::new(item))); let subtype = Some(UncheckedDataSchemaSubtype::Array(UncheckedArraySchema { items, min_items, @@ -3327,8 +3838,11 @@ pub(super) fn check_data_schema_subtype( _ => {} }; - if let Some(items) = array.items.as_deref() { - stack.extend(items.iter()); + if let Some(items) = &array.items { + match items { + BoxedElemOrVec::Elem(item) => stack.push(item.as_ref()), + BoxedElemOrVec::Vec(items) => stack.extend(items.iter()), + } } } Number(number) => { @@ -3489,7 +4003,16 @@ impl TryFrom> for ArraySchema (*item) + .try_into() + .map(|item| BoxedElemOrVec::Elem(Box::new(item))), + BoxedElemOrVec::Vec(items) => items + .into_iter() + .map(TryInto::try_into) + .collect::>() + .map(BoxedElemOrVec::Vec), + }) .transpose()?; Ok(Self { @@ -3535,7 +4058,7 @@ mod tests { use crate::{ extend::ExtendableThing, hlist::{Cons, Nil}, - thing::{ArraySchema, DataSchemaFromOther, ObjectSchema}, + thing::{ArraySchema, BoxedElemOrVec, DataSchemaFromOther, ObjectSchema}, }; use super::*; @@ -3694,9 +4217,9 @@ mod tests { } #[test] - fn empty_simple_array() { + fn empty_simple_vec() { let data_schema: DataSchemaFromOther = - DataSchemaBuilder::default().array().try_into().unwrap(); + DataSchemaBuilder::default().vec().try_into().unwrap(); assert_eq!( data_schema, DataSchema { @@ -3725,9 +4248,9 @@ mod tests { } #[test] - fn empty_partial_array() { + fn empty_partial_vec() { let data_schema: PartialDataSchema = - PartialDataSchemaBuilder::default().array().into(); + PartialDataSchemaBuilder::default().vec().into(); assert_eq!( data_schema, PartialDataSchema { @@ -3750,6 +4273,63 @@ mod tests { ); } + #[test] + fn empty_simple_tuple() { + let data_schema: DataSchemaFromOther = + DataSchemaBuilder::default().tuple().try_into().unwrap(); + assert_eq!( + data_schema, + DataSchema { + attype: None, + title: None, + titles: None, + description: None, + descriptions: None, + constant: None, + default: None, + unit: None, + one_of: None, + enumeration: None, + read_only: false, + write_only: false, + format: None, + subtype: Some(DataSchemaSubtype::Array(ArraySchema { + items: Some(BoxedElemOrVec::Vec(vec![])), + min_items: None, + max_items: None, + other: Nil, + })), + other: Nil, + } + ); + } + + #[test] + fn empty_partial_tuple() { + let data_schema: PartialDataSchema = + PartialDataSchemaBuilder::default().tuple().into(); + assert_eq!( + data_schema, + PartialDataSchema { + constant: None, + default: None, + unit: None, + one_of: None, + enumeration: None, + read_only: false, + write_only: false, + format: None, + subtype: Some(UncheckedDataSchemaSubtype::Array(UncheckedArraySchema { + items: Some(BoxedElemOrVec::Vec(vec![])), + min_items: None, + max_items: None, + other: Nil, + })), + other: Nil, + } + ); + } + #[test] fn number_simple() { let data_schema: DataSchemaFromOther = @@ -4354,11 +4934,9 @@ mod tests { } #[test] - fn array_with_content() { + fn tuple_with_content() { let data_schema: DataSchemaFromOther = DataSchemaBuilder::default() - .array() - .min_items(0) - .max_items(5) + .tuple() .append(|b| b.finish_extend().constant("hello")) .append(|b| b.finish_extend().bool()) .try_into() @@ -4380,7 +4958,7 @@ mod tests { write_only: false, format: None, subtype: Some(DataSchemaSubtype::Array(ArraySchema { - items: Some(vec![ + items: Some(BoxedElemOrVec::Vec(vec![ DataSchema { attype: None, title: None, @@ -4415,7 +4993,59 @@ mod tests { subtype: Some(DataSchemaSubtype::Boolean), other: Nil, }, - ]), + ])), + min_items: None, + max_items: None, + other: Nil, + })), + other: Nil, + } + ); + } + + #[test] + fn vec_with_content() { + let data_schema: DataSchemaFromOther = DataSchemaBuilder::default() + .vec() + .min_items(0) + .max_items(5) + .set_item(|b| b.finish_extend().constant("hello")) + .try_into() + .unwrap(); + assert_eq!( + data_schema, + DataSchema { + attype: None, + title: None, + titles: None, + description: None, + descriptions: None, + constant: None, + default: None, + unit: None, + one_of: None, + enumeration: None, + read_only: false, + write_only: false, + format: None, + subtype: Some(DataSchemaSubtype::Array(ArraySchema { + items: Some(BoxedElemOrVec::Elem(Box::new(DataSchema { + attype: None, + title: None, + titles: None, + description: None, + descriptions: None, + constant: Some("hello".into()), + default: None, + unit: None, + one_of: None, + enumeration: None, + read_only: true, + write_only: false, + format: None, + subtype: None, + other: Nil, + },))), min_items: Some(0), max_items: Some(5), other: Nil, @@ -4426,11 +5056,9 @@ mod tests { } #[test] - fn array_partial_with_content() { + fn tuple_partial_with_content() { let data_schema: PartialDataSchema = PartialDataSchemaBuilder::default() - .array() - .min_items(0) - .max_items(5) + .tuple() .append(|b| b.finish_extend().constant("hello")) .append(|b| b.finish_extend().bool()) .try_into() @@ -4447,7 +5075,7 @@ mod tests { write_only: false, format: None, subtype: Some(UncheckedDataSchemaSubtype::Array(UncheckedArraySchema { - items: Some(vec![ + items: Some(BoxedElemOrVec::Vec(vec![ UncheckedDataSchema { attype: None, title: None, @@ -4482,7 +5110,54 @@ mod tests { subtype: Some(UncheckedDataSchemaSubtype::Boolean), other: Nil, }, - ]), + ])), + min_items: None, + max_items: None, + other: Nil, + })), + other: Nil, + } + ); + } + + #[test] + fn vec_partial_with_content() { + let data_schema: PartialDataSchema = PartialDataSchemaBuilder::default() + .vec() + .min_items(0) + .max_items(5) + .set_item(|b| b.finish_extend().constant("hello")) + .try_into() + .unwrap(); + assert_eq!( + data_schema, + PartialDataSchema { + constant: None, + default: None, + unit: None, + one_of: None, + enumeration: None, + read_only: false, + write_only: false, + format: None, + subtype: Some(UncheckedDataSchemaSubtype::Array(UncheckedArraySchema { + items: Some(BoxedElemOrVec::Elem(Box::new(UncheckedDataSchema { + attype: None, + title: None, + titles: None, + description: None, + descriptions: None, + constant: Some("hello".into()), + default: None, + unit: None, + one_of: None, + enumeration: None, + read_only: true, + write_only: false, + format: None, + subtype: None, + other: Nil, + },))), min_items: Some(0), max_items: Some(5), other: Nil, @@ -5033,17 +5708,20 @@ mod tests { let data_schema: UncheckedDataSchemaFromOther = DataSchemaBuilder::default() .one_of(|b| { b.finish_extend() - .array() + .vec() .min_items(2) .max_items(5) - .append(|b| { + .set_item(|b| { b.finish_extend() - .number() - .minimum(0.) - .maximum(5.) - .multiple_of(2.) + .one_of(|b| { + b.finish_extend() + .number() + .minimum(0.) + .maximum(5.) + .multiple_of(2.) + }) + .one_of(|b| b.finish_extend().integer().minimum(5).maximum(10)) }) - .append(|b| b.finish_extend().integer().minimum(5).maximum(10)) }) .one_of(|b| { b.finish_extend() @@ -5067,11 +5745,14 @@ mod tests { let data_schema: UncheckedDataSchemaFromOther = DataSchemaBuilder::default() .one_of(|b| { b.finish_extend() - .array() + .vec() .min_items(5) .max_items(2) - .append(|b| b.finish_extend().number().minimum(0.).maximum(5.)) - .append(|b| b.finish_extend().integer().minimum(5).maximum(10)) + .set_item(|b| { + b.finish_extend() + .one_of(|b| b.finish_extend().number().minimum(0.).maximum(5.)) + .one_of(|b| b.finish_extend().integer().minimum(5).maximum(10)) + }) }) .one_of(|b| b.finish_extend().number().minimum(20.).maximum(42.)) .one_of(|b| { @@ -5086,11 +5767,14 @@ mod tests { let data_schema: UncheckedDataSchemaFromOther = DataSchemaBuilder::default() .one_of(|b| { b.finish_extend() - .array() + .vec() .min_items(2) .max_items(5) - .append(|b| b.finish_extend().number().minimum(5.).maximum(0.)) - .append(|b| b.finish_extend().integer().minimum(5).maximum(10)) + .set_item(|b| { + b.finish_extend() + .one_of(|b| b.finish_extend().number().minimum(5.).maximum(0.)) + .one_of(|b| b.finish_extend().integer().minimum(5).maximum(10)) + }) }) .one_of(|b| b.finish_extend().number().minimum(20.).maximum(42.)) .one_of(|b| { @@ -5105,11 +5789,14 @@ mod tests { let data_schema: UncheckedDataSchemaFromOther = DataSchemaBuilder::default() .one_of(|b| { b.finish_extend() - .array() + .vec() .min_items(2) .max_items(5) - .append(|b| b.finish_extend().number().minimum(0.).maximum(5.)) - .append(|b| b.finish_extend().integer().minimum(10).maximum(5)) + .set_item(|b| { + b.finish_extend() + .one_of(|b| b.finish_extend().number().minimum(0.).maximum(5.)) + .one_of(|b| b.finish_extend().integer().minimum(10).maximum(5)) + }) }) .one_of(|b| b.finish_extend().number().minimum(20.).maximum(42.)) .one_of(|b| { @@ -5124,11 +5811,14 @@ mod tests { let data_schema: UncheckedDataSchemaFromOther = DataSchemaBuilder::default() .one_of(|b| { b.finish_extend() - .array() + .vec() .min_items(2) .max_items(5) - .append(|b| b.finish_extend().number().minimum(0.).maximum(5.)) - .append(|b| b.finish_extend().integer().minimum(5).maximum(10)) + .set_item(|b| { + b.finish_extend() + .one_of(|b| b.finish_extend().number().minimum(0.).maximum(5.)) + .one_of(|b| b.finish_extend().integer().minimum(5).maximum(10)) + }) }) .one_of(|b| b.finish_extend().number().minimum(42.).maximum(20.)) .one_of(|b| { @@ -5143,11 +5833,14 @@ mod tests { let data_schema: UncheckedDataSchemaFromOther = DataSchemaBuilder::default() .one_of(|b| { b.finish_extend() - .array() + .vec() .min_items(2) .max_items(5) - .append(|b| b.finish_extend().number().minimum(0.).maximum(5.)) - .append(|b| b.finish_extend().integer().minimum(5).maximum(10)) + .set_item(|b| { + b.finish_extend() + .one_of(|b| b.finish_extend().number().minimum(0.).maximum(5.)) + .one_of(|b| b.finish_extend().integer().minimum(5).maximum(10)) + }) }) .one_of(|b| b.finish_extend().number().minimum(20.).maximum(f64::NAN)) .one_of(|b| { @@ -5162,11 +5855,14 @@ mod tests { let data_schema: UncheckedDataSchemaFromOther = DataSchemaBuilder::default() .one_of(|b| { b.finish_extend() - .array() + .vec() .min_items(2) .max_items(5) - .append(|b| b.finish_extend().number().minimum(0.).maximum(5.)) - .append(|b| b.finish_extend().integer().minimum(5).maximum(10)) + .set_item(|b| { + b.finish_extend() + .one_of(|b| b.finish_extend().number().minimum(0.).maximum(5.)) + .one_of(|b| b.finish_extend().integer().minimum(5).maximum(10)) + }) }) .one_of(|b| b.finish_extend().number().minimum(f64::NAN).maximum(42.)) .one_of(|b| { @@ -5181,11 +5877,14 @@ mod tests { let data_schema: UncheckedDataSchemaFromOther = DataSchemaBuilder::default() .one_of(|b| { b.finish_extend() - .array() + .vec() .min_items(2) .max_items(5) - .append(|b| b.finish_extend().number().minimum(0.).maximum(5.)) - .append(|b| b.finish_extend().integer().minimum(5).maximum(10)) + .set_item(|b| { + b.finish_extend() + .one_of(|b| b.finish_extend().number().minimum(0.).maximum(5.)) + .one_of(|b| b.finish_extend().integer().minimum(5).maximum(10)) + }) }) .one_of(|b| b.finish_extend().number().minimum(20.).maximum(42.)) .one_of(|b| { @@ -5200,11 +5899,14 @@ mod tests { let data_schema: UncheckedDataSchemaFromOther = DataSchemaBuilder::default() .one_of(|b| { b.finish_extend() - .array() + .vec() .min_items(2) .max_items(5) - .append(|b| b.finish_extend().number().minimum(0.).maximum(5.)) - .append(|b| b.finish_extend().integer().minimum(5).maximum(10)) + .set_item(|b| { + b.finish_extend() + .one_of(|b| b.finish_extend().number().minimum(0.).maximum(5.)) + .one_of(|b| b.finish_extend().integer().minimum(5).maximum(10)) + }) }) .one_of(|b| b.finish_extend().number().minimum(20.).maximum(42.)) .one_of(|b| { @@ -5224,11 +5926,14 @@ mod tests { let data_schema: UncheckedDataSchemaFromOther = DataSchemaBuilder::default() .one_of(|b| { b.finish_extend() - .array() + .vec() .min_items(2) .max_items(5) - .append(|b| b.finish_extend().number().minimum(0.).maximum(5.)) - .append(|b| b.finish_extend().integer().minimum(5).maximum(10)) + .set_item(|b| { + b.finish_extend() + .one_of(|b| b.finish_extend().number().minimum(0.).maximum(5.)) + .one_of(|b| b.finish_extend().integer().minimum(5).maximum(10)) + }) }) .one_of(|b| b.finish_extend().number().minimum(20.).maximum(42.)) .one_of(|b| { @@ -5247,14 +5952,17 @@ mod tests { let data_schema: UncheckedDataSchemaFromOther = DataSchemaBuilder::default() .one_of(|b| { b.finish_extend() - .array() + .vec() .min_items(2) .max_items(5) - .append(|b| { + .set_item(|b| { b.finish_extend() - .one_of(|b| b.finish_extend().number().minimum(5.).maximum(0.)) + .one_of(|b| { + b.finish_extend() + .one_of(|b| b.finish_extend().number().minimum(5.).maximum(0.)) + }) + .one_of(|b| b.finish_extend().integer().minimum(5).maximum(10)) }) - .append(|b| b.finish_extend().integer().minimum(5).maximum(10)) }) .one_of(|b| b.finish_extend().number().minimum(20.).maximum(42.)) .one_of(|b| { @@ -5305,15 +6013,15 @@ mod tests { #[test] fn check_invalid_data_schema_multiple_of() { let data_schema: UncheckedDataSchemaFromOther = DataSchemaBuilder::default() - .array() - .append(|b| b.finish_extend().number().multiple_of(0.)) + .vec() + .set_item(|b| b.finish_extend().number().multiple_of(0.)) .into(); assert_eq!(data_schema.check().unwrap_err(), Error::InvalidMultipleOf); let data_schema: UncheckedDataSchemaFromOther = DataSchemaBuilder::default() - .array() - .append(|b| b.finish_extend().number().multiple_of(-2.)) + .vec() + .set_item(|b| b.finish_extend().number().multiple_of(-2.)) .into(); assert_eq!(data_schema.check().unwrap_err(), Error::InvalidMultipleOf); @@ -5324,17 +6032,20 @@ mod tests { let data_schema: PartialDataSchema = PartialDataSchemaBuilder::default() .one_of(|b| { b.finish_extend() - .array() + .vec() .min_items(2) .max_items(5) - .append(|b| { + .set_item(|b| { b.finish_extend() - .number() - .minimum(0.) - .maximum(5.) - .multiple_of(2.) + .one_of(|b| { + b.finish_extend() + .number() + .minimum(0.) + .maximum(5.) + .multiple_of(2.) + }) + .one_of(|b| b.finish_extend().integer().minimum(5).maximum(10)) }) - .append(|b| b.finish_extend().integer().minimum(5).maximum(10)) }) .one_of(|b| { b.finish_extend() @@ -5457,7 +6168,7 @@ mod tests { } #[test] - fn extend_data_schema_with_array() { + fn extend_data_schema_with_vec() { let data_schema: DataSchemaFromOther>> = DataSchemaBuilder::< Cons>, @@ -5471,7 +6182,7 @@ mod tests { }) .finish_extend() .title("title") - .array_ext(|b| { + .vec_ext(|b| { b.ext(ArraySchemaExtA { b: A(2) }) .ext_with(|| ArraySchemaExtB { e: B("world".to_string()), @@ -5512,6 +6223,61 @@ mod tests { ); } + #[test] + fn extend_data_schema_with_tuple() { + let data_schema: DataSchemaFromOther>> = + DataSchemaBuilder::< + Cons>, + Cons>, + _, + _, + >::empty() + .ext(DataSchemaExtA { a: A(1) }) + .ext_with(|| DataSchemaExtB { + d: B("hello".to_string()), + }) + .finish_extend() + .title("title") + .tuple_ext(|b| { + b.ext(ArraySchemaExtA { b: A(2) }) + .ext_with(|| ArraySchemaExtB { + e: B("world".to_string()), + }) + }) + .try_into() + .unwrap(); + + assert_eq!( + data_schema, + DataSchema { + title: Some("title".to_string()), + other: Nil::cons(DataSchemaExtA { a: A(1) }).cons(DataSchemaExtB { + d: B("hello".to_string()) + }), + attype: Default::default(), + titles: Default::default(), + description: Default::default(), + descriptions: Default::default(), + constant: Default::default(), + default: Default::default(), + unit: Default::default(), + one_of: Default::default(), + enumeration: Default::default(), + read_only: Default::default(), + write_only: Default::default(), + format: Default::default(), + subtype: Some(DataSchemaSubtype::Array(ArraySchema { + other: Nil::cons(ArraySchemaExtA { b: A(2) }).cons(ArraySchemaExtB { + e: B("world".to_string()) + }), + items: Some(BoxedElemOrVec::Vec(Vec::new())), + max_items: Default::default(), + min_items: Default::default(), + })), + } + ); + } + #[test] fn extend_data_schema_with_object() { let data_schema: DataSchemaFromOther>> = @@ -5600,9 +6366,9 @@ mod tests { } #[test] - fn valid_unchecked_array_data_schema() { + fn valid_unchecked_tuple_data_schema() { let data_schema = UncheckedArraySchema:: { - items: Some(vec![ + items: Some(BoxedElemOrVec::Vec(vec![ UncheckedDataSchema { titles: Some({ let mut multilang = MultiLanguageBuilder::default(); @@ -5633,15 +6399,14 @@ mod tests { }), ..Default::default() }, - ]), - min_items: Some(1), + ])), ..Default::default() }; assert_eq!( ArraySchema::try_from(data_schema).unwrap(), ArraySchema { - items: Some(vec![ + items: Some(BoxedElemOrVec::Vec(vec![ DataSchema { titles: Some( [ @@ -5680,17 +6445,16 @@ mod tests { ), ..Default::default() }, - ]), - min_items: Some(1), + ])), ..Default::default() } ); } #[test] - fn invalid_unchecked_array_data_schema() { + fn invalid_unchecked_tuple_data_schema() { let data_schema = UncheckedArraySchema:: { - items: Some(vec![ + items: Some(BoxedElemOrVec::Vec(vec![ UncheckedDataSchema { titles: Some({ let mut multilang = MultiLanguageBuilder::default(); @@ -5721,7 +6485,84 @@ mod tests { }), ..Default::default() }, - ]), + ])), + ..Default::default() + }; + + assert_eq!( + ArraySchema::try_from(data_schema).unwrap_err(), + Error::InvalidLanguageTag("e1n".to_string()), + ); + } + + #[test] + fn valid_unchecked_vec_data_schema() { + let data_schema = UncheckedArraySchema:: { + items: Some(BoxedElemOrVec::Elem(Box::new(UncheckedDataSchema { + titles: Some({ + let mut multilang = MultiLanguageBuilder::default(); + multilang.add("it", "title1").add("en", "title2"); + multilang + }), + descriptions: Some({ + let mut multilang = MultiLanguageBuilder::default(); + multilang + .add("it", "description1") + .add("en", "description2"); + multilang + }), + ..Default::default() + }))), + min_items: Some(1), + ..Default::default() + }; + + assert_eq!( + ArraySchema::try_from(data_schema).unwrap(), + ArraySchema { + items: Some(BoxedElemOrVec::Elem(Box::new(DataSchema { + titles: Some( + [ + ("it".parse().unwrap(), "title1".to_string()), + ("en".parse().unwrap(), "title2".to_string()) + ] + .into_iter() + .collect() + ), + descriptions: Some( + [ + ("it".parse().unwrap(), "description1".to_string()), + ("en".parse().unwrap(), "description2".to_string()) + ] + .into_iter() + .collect() + ), + ..Default::default() + },))), + min_items: Some(1), + ..Default::default() + } + ); + } + + #[test] + fn invalid_unchecked_vec_data_schema() { + let data_schema = UncheckedArraySchema:: { + items: Some(BoxedElemOrVec::Elem(Box::new(UncheckedDataSchema { + titles: Some({ + let mut multilang = MultiLanguageBuilder::default(); + multilang.add("it", "title1").add("en", "title2"); + multilang + }), + descriptions: Some({ + let mut multilang = MultiLanguageBuilder::default(); + multilang + .add("it", "description1") + .add("e1n", "description2"); + multilang + }), + ..Default::default() + }))), min_items: Some(1), ..Default::default() }; diff --git a/src/thing.rs b/src/thing.rs index 8aa0153..9e41789 100644 --- a/src/thing.rs +++ b/src/thing.rs @@ -785,9 +785,18 @@ impl Default for DataSchemaSubtype { ))] pub struct ArraySchema { /// The characteristics of the JSON array. - #[serde(default)] - #[serde_as(as = "Option>")] - pub items: Option>>, + /// + /// An item has a different semantic than a `Vec` of one item: + /// + /// - a _single_ item `T` represents an array of element where every element must follow the + /// schema defined by `T`; + /// - a `Vec` of one element `T` represent a tuple of one single element, which must follow the + /// schema defined by `T`. + /// + /// In general, using a `Vec` of data schemas expresses a tuple of elements with a 1:1 + /// correspondence. + #[serde(skip_serializing_if = "Option::is_none")] + pub items: Option>>, /// The minimum number of items that have to be in the JSON array. pub min_items: Option, @@ -800,9 +809,16 @@ pub struct ArraySchema { pub other: AS, } +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(untagged)] +pub enum BoxedElemOrVec { + Elem(Box), + Vec(Vec), +} + #[derive(Clone, Debug, Default, PartialEq)] pub(crate) struct UncheckedArraySchema { - pub(crate) items: Option>>, + pub(crate) items: Option>>, pub(crate) min_items: Option, pub(crate) max_items: Option, pub(crate) other: AS,