wot_td/
builder.rs

1//! Thing Description builder
2//!
3//! The main entry point is [ThingBuilder].
4//!
5//! # Usage
6//!
7//! The basic usage consists of calling [`Thing::builder`] or [`ThingBuilder::new`], and then using
8//! a build pattern for all the parts that need to be customized:
9//!
10//! ```
11//! # use wot_td::{builder::data_schema::SpecializableDataSchema, thing::Thing};
12//! #
13//! let thing = Thing::builder("Thing name")
14//!     .id("thing-id-1234")
15//!     .finish_extend()
16//!     .property("first-property", |prop_builder| {
17//!         prop_builder
18//!             .finish_extend_data_schema()
19//!             .observable(true)
20//!             .bool()
21//!     })
22//!     .build()
23//!     .unwrap();
24//! # drop(thing);
25//! ```
26//!
27//! It is worth noting the usage of `.finish_*` methods, which makes the relative builder
28//! _unextendable_ and fully customizable. Some builders are extendable by defaults and some of
29//! these require a _finish_ method to be called.
30//!
31//! # Extendability
32//!
33//! Many parts of a Thing can be _extended_ with arbitrary content. This is necessary to grant the
34//! flexibility of the Web of Things Description specification without losing type safety.
35//!
36//! In order to extend a `Thing`, a new struct has to be created and the
37//! [`ExtendableThing`](crate::extend::ExtendableThing) needs to be implemented:
38//!
39//! ```
40//! # use serde::{Deserialize, Serialize};
41//! # use wot_td::{extend::ExtendableThing};
42//! #
43//! #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
44//! struct ThingExtension {
45//!     a_field: String,
46//!     another_field: u32,
47//! }
48//!
49//! impl ExtendableThing for ThingExtension {
50//!     type InteractionAffordance = ();
51//!     type PropertyAffordance = ();
52//!     type ActionAffordance = ();
53//!     type EventAffordance = ();
54//!     type Form = ();
55//!     type ExpectedResponse = ();
56//!     type DataSchema = ();
57//!     type ObjectSchema = ();
58//!     type ArraySchema = ();
59//! }
60//! ```
61//!
62//! In this example we are just extending the _base_ structure of the `Thing`, which is expressed
63//! by the associated types set to the unit type. Also notice that both `Serialize` and
64//! `Deserialize` traits are required by the `ExtendableThing` trait.
65//!
66//! This extension can be used in the `ThingBuilder`, and the serialized data contains all the
67//! extension fields:
68//!
69//! ```
70//! # use serde::{Deserialize, Serialize};
71//! # use serde_json::json;
72//! # use wot_td::{extend::ExtendableThing, thing::Thing};
73//! #
74//! # #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
75//! # struct ThingExtension {
76//! #     a_field: String,
77//! #     another_field: u32,
78//! # }
79//! #
80//! # impl ExtendableThing for ThingExtension {
81//! #     type InteractionAffordance = ();
82//! #     type PropertyAffordance = ();
83//! #     type ActionAffordance = ();
84//! #     type EventAffordance = ();
85//! #     type Form = ();
86//! #     type ExpectedResponse = ();
87//! #     type DataSchema = ();
88//! #     type ObjectSchema = ();
89//! #     type ArraySchema = ();
90//! # }
91//! #
92//! let thing = Thing::builder("Thing name")
93//!     .ext(ThingExtension {
94//!         a_field: "hello world".to_string(),
95//!         another_field: 42,
96//!     })
97//!     .finish_extend()
98//!     .build()
99//!     .unwrap();
100//!
101//! assert_eq!(
102//!     serde_json::to_value(thing).unwrap(),
103//!     json!({
104//!         "@context": "https://www.w3.org/2022/wot/td/v1.1",
105//!         "title": "Thing name",
106//!         "a_field": "hello world",
107//!         "another_field": 42,
108//!         "security": [],
109//!         "securityDefinitions": {},
110//!     })
111//! );
112//! ```
113//!
114//! All the associated types can be set in order to extend the relative _piece_ of the thing.
115//! Moreover, an arbitrary number of extension can be used as well.
116//!
117//! ```
118//! # use serde::{Deserialize, Serialize};
119//! # use serde_json::json;
120//! # use wot_td::{
121//! #     extend::ExtendableThing,
122//! #     thing::{FormOperation, Thing},
123//! # };
124//! #
125//! #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
126//! struct ThingExtension {
127//!     a_field: String,
128//!     another_field: u32,
129//! }
130//!
131//! #[derive(Debug, PartialEq, Serialize, Deserialize)]
132//! struct FormExtension {
133//!     form_field: f32,
134//! }
135//!
136//! impl ExtendableThing for ThingExtension {
137//!     type Form = FormExtension;
138//!     /* Other types set to `()` */
139//! #   type InteractionAffordance = ();
140//! #   type PropertyAffordance = ();
141//! #   type ActionAffordance = ();
142//! #   type EventAffordance = ();
143//! #   type ExpectedResponse = ();
144//! #   type DataSchema = ();
145//! #   type ObjectSchema = ();
146//! #   type ArraySchema = ();
147//! }
148//!
149//! #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
150//! struct AnotherExtension {
151//!     another_field_again: Vec<String>,
152//! }
153//!
154//! impl ExtendableThing for AnotherExtension {
155//!     /* Types set to `()` */
156//! #   type InteractionAffordance = ();
157//! #   type PropertyAffordance = ();
158//! #   type ActionAffordance = ();
159//! #   type EventAffordance = ();
160//! #   type Form = ();
161//! #   type ExpectedResponse = ();
162//! #   type DataSchema = ();
163//! #   type ObjectSchema = ();
164//! #   type ArraySchema = ();
165//! }
166//!
167//! let thing = Thing::builder("Thing name")
168//!     .ext(ThingExtension {
169//!         a_field: "hello world".to_string(),
170//!         another_field: 42,
171//!     })
172//!     .ext(AnotherExtension {
173//!         another_field_again: vec!["field1".to_string(), "field2".to_string()],
174//!     })
175//!     .finish_extend()
176//!     .form(|form_builder| {
177//!         form_builder
178//!             .ext(FormExtension { form_field: 23. })
179//!             .ext(())
180//!             .href("test_href")
181//!             .op(FormOperation::QueryAllActions)
182//!     })
183//!     .build()
184//!     .unwrap();
185//!
186//! assert_eq!(
187//!     serde_json::to_value(thing).unwrap(),
188//!     json!({
189//!         "@context": "https://www.w3.org/2022/wot/td/v1.1",
190//!         "title": "Thing name",
191//!         "a_field": "hello world",
192//!         "another_field": 42,
193//!         "another_field_again": ["field1", "field2"],
194//!         "forms": [{
195//!             "href": "test_href",
196//!             "form_field": 23.0,
197//!             "op": ["queryallactions"],
198//!         }],
199//!         "security": [],
200//!         "securityDefinitions": {},
201//!     })
202//! );
203//! ```
204
205pub mod affordance;
206pub mod data_schema;
207mod human_readable_info;
208
209use alloc::{borrow::ToOwned, fmt, string::*, vec, vec::Vec};
210use core::{marker::PhantomData, ops::Not};
211
212use hashbrown::{hash_map::Entry, HashMap};
213use oxilangtag::LanguageTag;
214use serde_json::Value;
215use time::OffsetDateTime;
216
217use crate::{
218    extend::{Extend, Extendable, ExtendableThing},
219    thing::{
220        AdditionalExpectedResponse, ComboSecurityScheme, DataSchemaFromOther,
221        DefaultedFormOperations, ExpectedResponse, Form, FormOperation, KnownSecuritySchemeSubtype,
222        Link, SecurityScheme, SecuritySchemeSubtype, Thing, UnknownSecuritySchemeSubtype,
223        VersionInfo, TD_CONTEXT_11,
224    },
225};
226
227use self::{
228    affordance::{
229        AffordanceBuilder, BuildableAffordance, CheckableInteractionAffordanceBuilder,
230        UsableActionAffordanceBuilder, UsableEventAffordanceBuilder,
231        UsablePropertyAffordanceBuilder,
232    },
233    data_schema::{
234        uri_variables_contains_arrays_objects, CheckableDataSchema, UncheckedDataSchemaFromOther,
235    },
236};
237
238pub use self::{affordance::*, data_schema::*};
239
240pub use self::human_readable_info::*;
241
242/// Builder typetags
243pub mod typetags {
244    /// A _typetag_ for types that needs to be extended.
245    #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
246    pub struct ToExtend;
247
248    /// A _typetag_ for types that have been already extended.
249    #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
250    pub struct Extended;
251}
252
253pub use self::typetags::*;
254
255/// A builder for a [Thing]
256///
257/// A `ThingBuilder` can be created using [`ThingBuilder::new`] or [`Thing::builder`], and after
258/// all the customization, [`ThingBuilder::build`] needs to be called in order to create a
259/// [`Thing`].
260#[must_use]
261pub struct ThingBuilder<Other: ExtendableThing, Status> {
262    context: Vec<Context>,
263    id: Option<String>,
264    attype: Option<Vec<String>>,
265    title: String,
266    titles: Option<MultiLanguageBuilder<String>>,
267    description: Option<String>,
268    descriptions: Option<MultiLanguageBuilder<String>>,
269    version: Option<VersionInfo>,
270    created: Option<OffsetDateTime>,
271    modified: Option<OffsetDateTime>,
272    support: Option<String>,
273    base: Option<String>,
274    properties: Vec<AffordanceBuilder<UsablePropertyAffordanceBuilder<Other>>>,
275    actions: Vec<AffordanceBuilder<UsableActionAffordanceBuilder<Other>>>,
276    events: Vec<AffordanceBuilder<UsableEventAffordanceBuilder<Other>>>,
277    links: Option<Vec<UncheckedLink>>,
278    forms: Option<Vec<FormBuilder<Other, String, Other::Form>>>,
279    uri_variables: Option<HashMap<String, UncheckedDataSchemaFromOther<Other>>>,
280    security: Vec<String>,
281    security_definitions: Vec<(String, UncheckedSecurityScheme)>,
282    profile: Vec<String>,
283    schema_definitions: HashMap<String, UncheckedDataSchemaFromOther<Other>>,
284
285    /// Thing extension.
286    pub other: Other,
287    _marker: PhantomData<Status>,
288}
289
290macro_rules! opt_field_builder {
291    ($($field:ident : $ty:ty),* $(,)?) => {
292        $(
293            #[doc = concat!("Sets the value of the `", stringify!($field), "` field.")]
294            pub fn $field(mut self, value: impl Into<$ty>) -> Self {
295                self.$field = Some(value.into());
296                self
297            }
298        )*
299    };
300}
301
302/// Builder errors
303///
304/// Most of the Thing Description conflicts are caught at compile time.
305/// The few errors that may be discovered at only runtime are the following.
306#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)]
307pub enum Error {
308    /// The WoT security definitions must have an unique name
309    #[error("Two security definitions use the name \"{0}\"")]
310    DuplicatedSecurityDefinition(String),
311
312    /// The forms have defaults that depend on the Affordance that contains them.
313    /// The Thing-level forms must be explicit on the operation
314    #[error("A Form directly placed in a Thing must contain at least one relevant operation")]
315    MissingOpInForm,
316
317    /// The Form can use only a specific set of operations depending on the context.
318    #[error("Invalid Form operation {operation} in {context} context")]
319    InvalidOpInForm {
320        /// The context of the invalid operation.
321        context: FormContext,
322
323        /// The invalid operation for the `Form`.
324        operation: FormOperation,
325    },
326
327    /// The security field must refer to existing security definitions.
328    #[error("Security \"{0}\" is not specified in Thing security definitions")]
329    UndefinedSecurity(String),
330
331    /// When both min and max are specified, min must be less or equal than max
332    #[error("Min value greater than max value")]
333    InvalidMinMax,
334
335    /// Neither minimum or maximum value can be NaN
336    #[error("Min or Max value is NaN")]
337    NanMinMax,
338
339    /// For each type of affordance, names must be unique
340    #[error("Two affordances of type {ty} use the name \"{name}\"")]
341    DuplicatedAffordance {
342        /// The type of the affordance
343        ty: AffordanceType,
344
345        /// The duplicated name
346        name: String,
347    },
348
349    /// Invalid `multiple_of` field, that must strictly greater than zero.
350    #[error("\"multipleOf\" field must be strictly greater than 0")]
351    InvalidMultipleOf,
352
353    /// A schema has been referenced using a specific name, but it is not been declared.
354    #[error("Using the data schema \"{0}\", which is not declared in the schema definitions")]
355    MissingSchemaDefinition(String),
356
357    /// Invalid URI variable, which cannot be an object or an array.
358    #[error("An uriVariable cannot be an ObjectSchema or ArraySchema")]
359    InvalidUriVariables,
360
361    /// Language tag is not conforming to [BCP47](https://www.rfc-editor.org/info/bcp47).
362    #[error("Invalid language tag \"{0}\"")]
363    InvalidLanguageTag(String),
364
365    /// A `Link` contains a `sizes` field but its `rel` field is not equal to `icon`.
366    #[error("A sizes field can be used only when \"rel\" is \"icon\"")]
367    SizesWithRelNotIcon,
368}
369
370/// Context of a [`Form`]
371///
372/// [`Form`]: `crate::thing::Form`
373#[derive(Debug, Clone, PartialEq, Eq, Hash)]
374pub enum FormContext {
375    /// The root Thing context
376    Thing,
377
378    /// A property affordance context
379    Property,
380
381    /// An action affordance context
382    Action,
383
384    /// An event affordance context
385    Event,
386}
387
388impl fmt::Display for FormContext {
389    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390        let s = match self {
391            Self::Thing => "Thing",
392            Self::Property => "PropertyAffordance",
393            Self::Action => "ActionAffordance",
394            Self::Event => "EventAffordance",
395        };
396
397        f.write_str(s)
398    }
399}
400
401impl From<AffordanceType> for FormContext {
402    fn from(ty: AffordanceType) -> Self {
403        match ty {
404            AffordanceType::Property => Self::Property,
405            AffordanceType::Action => Self::Action,
406            AffordanceType::Event => Self::Event,
407        }
408    }
409}
410
411/// The possible affordance types
412#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
413pub enum AffordanceType {
414    /// A property affordance
415    Property,
416
417    /// An action affordance
418    Action,
419
420    /// An event affordance
421    Event,
422}
423
424impl fmt::Display for AffordanceType {
425    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
426        let s = match self {
427            Self::Property => "property",
428            Self::Action => "action",
429            Self::Event => "event",
430        };
431
432        f.write_str(s)
433    }
434}
435
436impl<Other: ExtendableThing> ThingBuilder<Other, ToExtend> {
437    /// Create a new default builder with a specified title, using a default extension
438    pub fn new(title: impl Into<String>) -> Self
439    where
440        Other: Default,
441    {
442        let title = title.into();
443        let context = vec![Context::Simple(TD_CONTEXT_11.to_string())];
444
445        Self {
446            context,
447            id: Default::default(),
448            attype: Default::default(),
449            title,
450            titles: Default::default(),
451            description: Default::default(),
452            descriptions: Default::default(),
453            version: Default::default(),
454            created: Default::default(),
455            modified: Default::default(),
456            support: Default::default(),
457            base: Default::default(),
458            properties: Default::default(),
459            actions: Default::default(),
460            events: Default::default(),
461            links: Default::default(),
462            forms: Default::default(),
463            security: Default::default(),
464            security_definitions: Default::default(),
465            uri_variables: Default::default(),
466            profile: Default::default(),
467            schema_definitions: Default::default(),
468            other: Default::default(),
469            _marker: PhantomData,
470        }
471    }
472
473    /// Create a new default builder with a specified title, using an empty extension
474    pub fn new_empty(title: impl Into<String>) -> ThingBuilder<Other::Empty, ToExtend>
475    where
476        Other: Extendable,
477        Other::Empty: ExtendableThing,
478    {
479        let title = title.into();
480        let context = vec![Context::Simple(TD_CONTEXT_11.to_string())];
481
482        ThingBuilder {
483            context,
484            id: Default::default(),
485            attype: Default::default(),
486            title,
487            titles: Default::default(),
488            description: Default::default(),
489            descriptions: Default::default(),
490            version: Default::default(),
491            created: Default::default(),
492            modified: Default::default(),
493            support: Default::default(),
494            base: Default::default(),
495            properties: Default::default(),
496            actions: Default::default(),
497            events: Default::default(),
498            links: Default::default(),
499            forms: Default::default(),
500            security: Default::default(),
501            security_definitions: Default::default(),
502            uri_variables: Default::default(),
503            profile: Default::default(),
504            schema_definitions: Default::default(),
505            other: Other::empty(),
506            _marker: PhantomData,
507        }
508    }
509
510    /// Finalize the set of extensions that must be populated
511    ///
512    /// Moves the builder status from [ToExtend] to [Extended].
513    /// From this point is not possible to add further extensions to the builder.
514    ///
515    /// See [ThingBuilder::ext].
516    ///
517    /// # Example
518    ///
519    /// ```
520    /// # use wot_td::{builder::data_schema::SpecializableDataSchema, thing::Thing};
521    /// #
522    /// let thing = Thing::builder("Thing name")
523    ///     .id("thing-id-1234")
524    ///     .finish_extend()
525    ///     .property("first-property", |prop_builder| {
526    ///         prop_builder
527    ///             .finish_extend_data_schema()
528    ///             .observable(true)
529    ///             .bool()
530    ///     })
531    ///     .build()
532    ///     .unwrap();
533    /// # drop(thing);
534    /// ```
535    pub fn finish_extend(self) -> ThingBuilder<Other, Extended> {
536        let Self {
537            context,
538            id,
539            attype,
540            title,
541            titles,
542            description,
543            descriptions,
544            version,
545            created,
546            modified,
547            support,
548            base,
549            properties,
550            actions,
551            events,
552            links,
553            forms,
554            uri_variables,
555            security,
556            security_definitions,
557            profile,
558            schema_definitions,
559            other,
560            _marker: _,
561        } = self;
562
563        ThingBuilder {
564            context,
565            id,
566            attype,
567            title,
568            titles,
569            description,
570            descriptions,
571            version,
572            created,
573            modified,
574            support,
575            base,
576            properties,
577            actions,
578            events,
579            links,
580            forms,
581            uri_variables,
582            security,
583            security_definitions,
584            profile,
585            schema_definitions,
586            other,
587            _marker: PhantomData,
588        }
589    }
590
591    /// Extends the Thing, passing a closure that returns `T`.
592    ///
593    /// # Example
594    ///
595    /// ```
596    /// # use serde::{Deserialize, Serialize};
597    /// # use serde_json::json;
598    /// # use wot_td::{extend::ExtendableThing, thing::Thing};
599    /// #
600    /// # #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
601    /// # struct ThingExtension {
602    /// #     a_field: String,
603    /// #     another_field: u32,
604    /// # }
605    /// #
606    /// # impl ExtendableThing for ThingExtension {
607    /// #     type InteractionAffordance = ();
608    /// #     type PropertyAffordance = ();
609    /// #     type ActionAffordance = ();
610    /// #     type EventAffordance = ();
611    /// #     type Form = ();
612    /// #     type ExpectedResponse = ();
613    /// #     type DataSchema = ();
614    /// #     type ObjectSchema = ();
615    /// #     type ArraySchema = ();
616    /// # }
617    /// #
618    /// let thing = Thing::builder("Thing name")
619    ///     .ext_with(|| ThingExtension {
620    ///         a_field: "hello world".to_string(),
621    ///         another_field: 42,
622    ///     })
623    ///     .finish_extend()
624    ///     .build()
625    ///     .unwrap();
626    ///
627    /// assert_eq!(
628    ///     serde_json::to_value(thing).unwrap(),
629    ///     json!({
630    ///         "@context": "https://www.w3.org/2022/wot/td/v1.1",
631    ///         "title": "Thing name",
632    ///         "a_field": "hello world",
633    ///         "another_field": 42,
634    ///         "security": [],
635    ///         "securityDefinitions": {},
636    ///     })
637    /// );
638    /// ```
639    pub fn ext_with<F, T>(self, f: F) -> ThingBuilder<Other::Target, ToExtend>
640    where
641        F: FnOnce() -> T,
642        Other: Extend<T>,
643        Other::Target: ExtendableThing,
644    {
645        let Self {
646            context,
647            id,
648            attype,
649            title,
650            titles,
651            description,
652            descriptions,
653            version,
654            created,
655            modified,
656            support,
657            base,
658            properties: _,
659            actions: _,
660            events: _,
661            links,
662            forms: _,
663            uri_variables: _,
664            security,
665            security_definitions,
666            profile,
667            schema_definitions: _,
668            other,
669            _marker,
670        } = self;
671
672        let other = other.ext_with(f);
673        ThingBuilder {
674            context,
675            id,
676            attype,
677            title,
678            titles,
679            description,
680            descriptions,
681            version,
682            created,
683            modified,
684            support,
685            base,
686            properties: Default::default(),
687            actions: Default::default(),
688            events: Default::default(),
689            links,
690            forms: Default::default(),
691            uri_variables: Default::default(),
692            security,
693            security_definitions,
694            profile,
695            schema_definitions: Default::default(),
696            other,
697            _marker,
698        }
699    }
700
701    /// Extend the [ThingBuilder] with a [ExtendableThing]
702    ///
703    /// # Example
704    ///
705    /// ```
706    /// # use serde::{Deserialize, Serialize};
707    /// # use serde_json::json;
708    /// # use wot_td::{extend::ExtendableThing, thing::Thing};
709    /// #
710    /// # #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
711    /// # struct ThingExtension {
712    /// #     a_field: String,
713    /// #     another_field: u32,
714    /// # }
715    /// #
716    /// # impl ExtendableThing for ThingExtension {
717    /// #     type InteractionAffordance = ();
718    /// #     type PropertyAffordance = ();
719    /// #     type ActionAffordance = ();
720    /// #     type EventAffordance = ();
721    /// #     type Form = ();
722    /// #     type ExpectedResponse = ();
723    /// #     type DataSchema = ();
724    /// #     type ObjectSchema = ();
725    /// #     type ArraySchema = ();
726    /// # }
727    /// #
728    /// let thing = Thing::builder("Thing name")
729    ///     .ext(ThingExtension {
730    ///         a_field: "hello world".to_string(),
731    ///         another_field: 42,
732    ///     })
733    ///     .finish_extend()
734    ///     .build()
735    ///     .unwrap();
736    ///
737    /// assert_eq!(
738    ///     serde_json::to_value(thing).unwrap(),
739    ///     json!({
740    ///         "@context": "https://www.w3.org/2022/wot/td/v1.1",
741    ///         "title": "Thing name",
742    ///         "a_field": "hello world",
743    ///         "another_field": 42,
744    ///         "security": [],
745    ///         "securityDefinitions": {},
746    ///     })
747    /// );
748    /// ```
749    #[inline]
750    pub fn ext<T>(self, t: T) -> ThingBuilder<Other::Target, ToExtend>
751    where
752        Other: Extend<T>,
753        Other::Target: ExtendableThing,
754    {
755        self.ext_with(|| t)
756    }
757}
758
759impl<Other: ExtendableThing, Status> ThingBuilder<Other, Status> {
760    /// Consume the builder to produce the configured Thing
761    ///
762    /// This step will perform the final validation of the builder state.
763    pub fn build(self) -> Result<Thing<Other>, Error> {
764        let Self {
765            context,
766            id,
767            attype,
768            title,
769            titles,
770            description,
771            descriptions,
772            version,
773            created,
774            modified,
775            support,
776            base,
777            properties,
778            actions,
779            events,
780            links,
781            forms,
782            security,
783            security_definitions: security_definitions_vec,
784            uri_variables,
785            profile,
786            schema_definitions,
787            other,
788            _marker: _,
789        } = self;
790
791        let mut security_definitions = HashMap::with_capacity(security_definitions_vec.len());
792        for (name, scheme) in security_definitions_vec {
793            let scheme: SecurityScheme = scheme.try_into()?;
794
795            match security_definitions.entry(name) {
796                Entry::Vacant(entry) => {
797                    entry.insert(scheme);
798                }
799                Entry::Occupied(entry) => {
800                    return Err(Error::DuplicatedSecurityDefinition(entry.remove_entry().0));
801                }
802            }
803        }
804        let security_definitions = security_definitions;
805        security_definitions
806            .values()
807            .filter_map(|security| match &security.subtype {
808                SecuritySchemeSubtype::Known(KnownSecuritySchemeSubtype::Combo(combo)) => {
809                    Some(combo)
810                }
811                _ => None,
812            })
813            .flat_map(|combo| match combo {
814                ComboSecurityScheme::OneOf(names) => names.as_slice(),
815                ComboSecurityScheme::AllOf(names) => names.as_slice(),
816            })
817            .try_for_each(|security_name| {
818                security_definitions
819                    .contains_key(security_name)
820                    .then_some(())
821                    .ok_or_else(|| Error::MissingSchemaDefinition(security_name.to_string()))
822            })?;
823        let schema_definitions = schema_definitions
824            .into_iter()
825            .map(|(key, value)| value.try_into().map(|value| (key, value)))
826            .collect::<Result<_, _>>()?;
827
828        let profile = profile.is_empty().not().then_some(profile);
829
830        let forms = forms
831            .map(|forms| {
832                forms
833                    .into_iter()
834                    .map(|form_builder| {
835                        Self::build_form_from_builder(
836                            form_builder,
837                            &security_definitions,
838                            &schema_definitions,
839                        )
840                    })
841                    .collect::<Result<Vec<_>, _>>()
842            })
843            .transpose()?;
844
845        let schema_definitions = schema_definitions
846            .is_empty()
847            .not()
848            .then_some(schema_definitions);
849
850        let context = {
851            // TODO: improve this
852            if context.len() == 1 {
853                Value::String(context.into_iter().next().unwrap().into_simple().unwrap())
854            } else {
855                context
856                    .into_iter()
857                    .map(|context| match context {
858                        Context::Simple(s) => Value::from(s),
859                        Context::Map(map) => {
860                            let map = map.into_iter().map(|(k, v)| (k, Value::from(v))).collect();
861                            Value::Object(map)
862                        }
863                    })
864                    .collect()
865            }
866        };
867
868        let invalid_uri_variables = uri_variables
869            .as_ref()
870            .map(uri_variables_contains_arrays_objects::<Other>)
871            .unwrap_or(false);
872        if invalid_uri_variables {
873            return Err(Error::InvalidUriVariables);
874        }
875
876        let uri_variables = uri_variables
877            .map(|uri_variables| {
878                uri_variables
879                    .into_iter()
880                    .map(|(key, value)| value.try_into().map(|value| (key, value)))
881                    .collect()
882            })
883            .transpose()?;
884
885        let properties = try_build_affordance(
886            properties,
887            AffordanceType::Property,
888            |property| &property.interaction,
889            |property| [Some(&property.data_schema)],
890            |op| {
891                matches!(
892                    op,
893                    FormOperation::ReadProperty
894                        | FormOperation::WriteProperty
895                        | FormOperation::ObserveProperty
896                        | FormOperation::UnobserveProperty
897                )
898            },
899            &security_definitions,
900        )?;
901        let actions = try_build_affordance(
902            actions,
903            AffordanceType::Action,
904            |action| &action.interaction,
905            |action| [action.input.as_ref(), action.output.as_ref()],
906            |op| {
907                matches!(
908                    op,
909                    FormOperation::InvokeAction
910                        | FormOperation::QueryAction
911                        | FormOperation::CancelAction
912                )
913            },
914            &security_definitions,
915        )?;
916        let events = try_build_affordance(
917            events,
918            AffordanceType::Event,
919            |event| &event.interaction,
920            |event| {
921                [
922                    event.subscription.as_ref(),
923                    event.data.as_ref(),
924                    event.cancellation.as_ref(),
925                ]
926            },
927            |op| {
928                matches!(
929                    op,
930                    FormOperation::SubscribeEvent | FormOperation::UnsubscribeEvent
931                )
932            },
933            &security_definitions,
934        )?;
935        let links = links
936            .map(|links| links.into_iter().map(TryInto::try_into).collect())
937            .transpose()?;
938
939        let titles = titles.map(|titles| titles.build()).transpose()?;
940        let descriptions = descriptions
941            .map(|descriptions| descriptions.build())
942            .transpose()?;
943
944        Ok(Thing {
945            context,
946            id,
947            attype,
948            title,
949            titles,
950            description,
951            descriptions,
952            version,
953            created,
954            modified,
955            support,
956            base,
957            properties,
958            actions,
959            events,
960            links,
961            forms,
962            security,
963            security_definitions,
964            uri_variables,
965            profile,
966            schema_definitions,
967            other,
968        })
969    }
970
971    fn build_form_from_builder(
972        form_builder: FormBuilder<Other, String, Other::Form>,
973        security_definitions: &HashMap<String, SecurityScheme>,
974        schema_definitions: &HashMap<String, DataSchemaFromOther<Other>>,
975    ) -> Result<Form<Other>, Error> {
976        use DefaultedFormOperations::*;
977        use FormOperation::*;
978
979        let FormBuilder {
980            op,
981            href,
982            content_type,
983            content_coding,
984            subprotocol,
985            mut security,
986            scopes,
987            response,
988            additional_responses,
989            other,
990            _marker: _,
991        } = form_builder;
992
993        security
994            .as_mut()
995            .map(|security| {
996                security.iter_mut().try_for_each(|security| {
997                    if security_definitions.contains_key(security) {
998                        Ok(())
999                    } else {
1000                        Err(Error::UndefinedSecurity(core::mem::take(security)))
1001                    }
1002                })
1003            })
1004            .transpose()?;
1005
1006        match &op {
1007            Default => return Err(Error::MissingOpInForm),
1008            Custom(operations) => {
1009                let wrong_op = operations
1010                    .iter()
1011                    .find(|op| {
1012                        matches!(
1013                            op,
1014                            ReadAllProperties
1015                                | WriteAllProperties
1016                                | ReadMultipleProperties
1017                                | WriteMultipleProperties
1018                                | ObserveAllProperties
1019                                | UnobserveAllProperties
1020                                | SubscribeAllEvents
1021                                | UnsubscribeAllEvents
1022                                | QueryAllActions
1023                        )
1024                        .not()
1025                    })
1026                    .copied();
1027
1028                if let Some(operation) = wrong_op {
1029                    return Err(Error::InvalidOpInForm {
1030                        context: FormContext::Thing,
1031                        operation,
1032                    });
1033                }
1034            }
1035        }
1036
1037        additional_responses
1038            .iter()
1039            .flat_map(|additional_response| additional_response.schema.as_ref())
1040            .try_for_each(|schema| {
1041                schema_definitions
1042                    .contains_key(schema)
1043                    .then_some(())
1044                    .ok_or_else(|| Error::MissingSchemaDefinition(schema.clone()))
1045            })?;
1046        let additional_responses = additional_responses
1047            .is_empty()
1048            .not()
1049            .then_some(additional_responses);
1050
1051        Ok(Form {
1052            op,
1053            href,
1054            content_type,
1055            content_coding,
1056            subprotocol,
1057            security,
1058            scopes,
1059            response,
1060            additional_responses,
1061            other,
1062        })
1063    }
1064
1065    opt_field_builder!(
1066        id: String,
1067        description: String,
1068        version: VersionInfo,
1069        created: OffsetDateTime,
1070        modified: OffsetDateTime,
1071        support: String,
1072        base: String,
1073    );
1074
1075    /// Add a new JSON-LD @context in the default namespace
1076    pub fn context<S>(mut self, value: S) -> Self
1077    where
1078        S: Into<String> + AsRef<str>,
1079    {
1080        if value.as_ref() == TD_CONTEXT_11 {
1081            return self;
1082        }
1083
1084        let context = Context::Simple(value.into());
1085        self.context.push(context);
1086        self
1087    }
1088
1089    /// Add a new JSON-LD @context with a custom namespace
1090    ///
1091    /// # Example
1092    /// ```
1093    /// # use serde_json::json;
1094    /// # use wot_td::thing::Thing;
1095    /// #
1096    /// let thing = Thing::builder("Thing name")
1097    ///     .context_map(|builder| {
1098    ///         builder
1099    ///             .context("custom_context1", "hello")
1100    ///             .context("custom_context2", "world")
1101    ///     })
1102    ///     .build()
1103    ///     .unwrap();
1104    ///
1105    /// assert_eq!(
1106    ///     serde_json::to_value(thing).unwrap(),
1107    ///     json!({
1108    ///         "title": "Thing name",
1109    ///         "@context": [
1110    ///             "https://www.w3.org/2022/wot/td/v1.1",
1111    ///             {
1112    ///                 "custom_context1": "hello",
1113    ///                 "custom_context2": "world",
1114    ///             }
1115    ///         ],
1116    ///         "security": [],
1117    ///         "securityDefinitions": {},
1118    ///     }),
1119    /// );
1120    /// ```
1121    pub fn context_map<F>(mut self, f: F) -> Self
1122    where
1123        F: FnOnce(&mut ContextMapBuilder) -> &mut ContextMapBuilder,
1124    {
1125        let mut context_map = ContextMapBuilder(Default::default());
1126        f(&mut context_map);
1127
1128        self.context.push(Context::Map(context_map.0));
1129        self
1130    }
1131
1132    /// Add a JSON-LD @type to the thing
1133    pub fn attype(mut self, value: impl Into<String>) -> Self {
1134        self.attype
1135            .get_or_insert_with(Default::default)
1136            .push(value.into());
1137        self
1138    }
1139
1140    /// Set multi-language titles
1141    ///
1142    /// # Examples
1143    ///
1144    /// ```
1145    /// # use serde_json::json;
1146    /// # use wot_td::thing::Thing;
1147    /// #
1148    /// let thing = Thing::builder("Thing name")
1149    ///     .titles(|builder| {
1150    ///         builder
1151    ///             .add("en", "English title")
1152    ///             .add("it", "Italian title")
1153    ///     })
1154    ///     .build()
1155    ///     .unwrap();
1156    ///
1157    /// assert_eq!(
1158    ///     serde_json::to_value(thing).unwrap(),
1159    ///     json!({
1160    ///         "title": "Thing name",
1161    ///         "@context": "https://www.w3.org/2022/wot/td/v1.1",
1162    ///         "titles": {
1163    ///             "en": "English title",
1164    ///             "it": "Italian title",
1165    ///         },
1166    ///         "security": [],
1167    ///         "securityDefinitions": {},
1168    ///     })
1169    /// );
1170    /// ```
1171    ///
1172    /// Creating a title using an invalid language tag is going to return an error when
1173    /// `Thing::build` is called:
1174    ///
1175    /// ```
1176    /// # use wot_td::{builder::Error, thing::Thing};
1177    /// #
1178    /// let error = Thing::builder("Thing name")
1179    ///     .titles(|builder| builder.add("e!n", "Invalid title"))
1180    ///     .build()
1181    ///     .unwrap_err();
1182    ///
1183    /// assert_eq!(error, Error::InvalidLanguageTag("e!n".to_string()));
1184    /// ```
1185    pub fn titles<F>(mut self, f: F) -> Self
1186    where
1187        F: FnOnce(&mut MultiLanguageBuilder<String>) -> &mut MultiLanguageBuilder<String>,
1188    {
1189        let mut builder = MultiLanguageBuilder::default();
1190        f(&mut builder);
1191
1192        self.titles = Some(builder);
1193        self
1194    }
1195
1196    /// Set multi-language descriptions
1197    ///
1198    /// See [`ThingBuilder::titles`] for examples.
1199    pub fn descriptions<F>(mut self, f: F) -> Self
1200    where
1201        F: FnOnce(&mut MultiLanguageBuilder<String>) -> &mut MultiLanguageBuilder<String>,
1202    {
1203        let mut builder = MultiLanguageBuilder::default();
1204        f(&mut builder);
1205        self.descriptions = Some(builder);
1206        self
1207    }
1208
1209    /// Add an additional link to the Thing Description
1210    pub fn link(mut self, href: impl Into<String>) -> Self {
1211        let href = href.into();
1212
1213        let link = UncheckedLink {
1214            href,
1215            ty: Default::default(),
1216            rel: Default::default(),
1217            anchor: Default::default(),
1218            sizes: Default::default(),
1219            hreflang: Default::default(),
1220        };
1221
1222        self.links.get_or_insert_with(Default::default).push(link);
1223        self
1224    }
1225
1226    /// Add an additional link to the Thing Description, with specified optional fields.
1227    ///
1228    /// # Example
1229    ///
1230    /// ```
1231    /// # use serde_json::json;
1232    /// # use wot_td::thing::Thing;
1233    /// #
1234    /// let thing = Thing::builder("Thing name")
1235    ///     .link_with(|builder| {
1236    ///         builder
1237    ///             .href("https://localhost")
1238    ///             .rel("icon")
1239    ///             .sizes("16x16 24x24 32x32")
1240    ///     })
1241    ///     .build()
1242    ///     .unwrap();
1243    ///
1244    /// assert_eq!(
1245    ///     serde_json::to_value(thing).unwrap(),
1246    ///     json!({
1247    ///         "title": "Thing name",
1248    ///         "@context": "https://www.w3.org/2022/wot/td/v1.1",
1249    ///         "links": [{
1250    ///             "href": "https://localhost",
1251    ///             "rel": "icon",
1252    ///             "sizes": "16x16 24x24 32x32",
1253    ///         }],
1254    ///         "security": [],
1255    ///         "securityDefinitions": {},
1256    ///     })
1257    /// );
1258    /// ```
1259    pub fn link_with<F>(mut self, f: F) -> Self
1260    where
1261        F: FnOnce(LinkBuilder<()>) -> LinkBuilder<String>,
1262    {
1263        let LinkBuilder {
1264            href,
1265            ty,
1266            rel,
1267            anchor,
1268            sizes,
1269            hreflang,
1270        } = f(LinkBuilder::new());
1271
1272        let link = UncheckedLink {
1273            href,
1274            ty,
1275            rel,
1276            anchor,
1277            sizes,
1278            hreflang,
1279        };
1280
1281        self.links.get_or_insert_with(Default::default).push(link);
1282        self
1283    }
1284
1285    /// Add a security definition and, eventually, a required security
1286    ///
1287    /// # Example
1288    ///
1289    /// ```
1290    /// # use serde_json::json;
1291    /// # use wot_td::thing::Thing;
1292    /// #
1293    /// let thing = Thing::builder("Thing name")
1294    ///     .security(|builder| {
1295    ///         builder
1296    ///             .basic()
1297    ///             .with_key("my_basic_sec")
1298    ///             .name("basic_sec_name")
1299    ///     })
1300    ///     .security(|builder| builder.apikey())
1301    ///     .build()
1302    ///     .unwrap();
1303    ///
1304    /// assert_eq!(
1305    ///     serde_json::to_value(thing).unwrap(),
1306    ///     json!({
1307    ///         "title": "Thing name",
1308    ///         "@context": "https://www.w3.org/2022/wot/td/v1.1",
1309    ///         "security": [],
1310    ///         "securityDefinitions": {
1311    ///             "my_basic_sec": {
1312    ///                 "scheme": "basic",
1313    ///                 "name": "basic_sec_name",
1314    ///                 "in": "header",
1315    ///             },
1316    ///             "apikey": {
1317    ///                 "scheme": "apikey",
1318    ///                 "in": "query",
1319    ///             }
1320    ///         },
1321    ///     })
1322    /// );
1323    /// ```
1324    pub fn security<F, T>(mut self, f: F) -> Self
1325    where
1326        F: FnOnce(SecuritySchemeBuilder<()>) -> SecuritySchemeBuilder<T>,
1327        T: BuildableSecuritySchemeSubtype,
1328    {
1329        use SecuritySchemeSubtype::*;
1330
1331        let builder = SecuritySchemeBuilder {
1332            attype: Default::default(),
1333            description: Default::default(),
1334            descriptions: Default::default(),
1335            proxy: Default::default(),
1336            name: Default::default(),
1337            subtype: Default::default(),
1338            required: false,
1339        };
1340
1341        let SecuritySchemeBuilder {
1342            attype,
1343            description,
1344            descriptions,
1345            proxy,
1346            name,
1347            subtype,
1348            required,
1349        } = f(builder);
1350
1351        let subtype = subtype.build();
1352        let security_scheme = UncheckedSecurityScheme {
1353            attype,
1354            description,
1355            descriptions,
1356            proxy,
1357            subtype,
1358        };
1359
1360        let name = name.unwrap_or_else(|| {
1361            match &security_scheme.subtype {
1362                Known(KnownSecuritySchemeSubtype::NoSec) => "nosec",
1363                Known(KnownSecuritySchemeSubtype::Auto) => "auto",
1364                Known(KnownSecuritySchemeSubtype::Combo(_)) => "combo",
1365                Known(KnownSecuritySchemeSubtype::Basic(_)) => "basic",
1366                Known(KnownSecuritySchemeSubtype::Digest(_)) => "digest",
1367                Known(KnownSecuritySchemeSubtype::Bearer(_)) => "bearer",
1368                Known(KnownSecuritySchemeSubtype::Psk(_)) => "psk",
1369                Known(KnownSecuritySchemeSubtype::OAuth2(_)) => "oauth2",
1370                Known(KnownSecuritySchemeSubtype::ApiKey(_)) => "apikey",
1371                Unknown(UnknownSecuritySchemeSubtype { scheme, .. }) => scheme.as_str(),
1372            }
1373            .to_string()
1374        });
1375
1376        if required {
1377            self.security.push(name.clone());
1378        }
1379        self.security_definitions.push((name, security_scheme));
1380
1381        self
1382    }
1383
1384    /// Adds a new item to the `profile` field.
1385    pub fn profile(mut self, value: impl Into<String>) -> Self {
1386        self.profile.push(value.into());
1387        self
1388    }
1389}
1390
1391impl<Other> ThingBuilder<Other, Extended>
1392where
1393    Other: ExtendableThing,
1394    Other::Form: Extendable,
1395{
1396    /// Add a Thing-level form
1397    ///
1398    /// # Notes
1399    ///
1400    /// - It must explicitly state its operation
1401    /// - It must use an `all` operation
1402    ///
1403    /// # Examples
1404    ///
1405    /// ```
1406    /// # use serde_json::json;
1407    /// # use wot_td::thing::{FormOperation, Thing};
1408    /// #
1409    /// let thing = Thing::builder("Thing name")
1410    ///     // Needs to _finish_ extending the thing before calling `.form`
1411    ///     .finish_extend()
1412    ///     .form(|builder| {
1413    ///         builder
1414    ///             .href("form_href")
1415    ///             .op(FormOperation::ReadAllProperties)
1416    ///     })
1417    ///     .build()
1418    ///     .unwrap();
1419    ///
1420    /// assert_eq!(
1421    ///     serde_json::to_value(thing).unwrap(),
1422    ///     json!({
1423    ///         "title": "Thing name",
1424    ///         "@context": "https://www.w3.org/2022/wot/td/v1.1",
1425    ///         "forms": [
1426    ///             {
1427    ///                 "href": "form_href",
1428    ///                 "op": ["readallproperties"],
1429    ///             }
1430    ///         ],
1431    ///         "security": [],
1432    ///         "securityDefinitions": {},
1433    ///     })
1434    /// );
1435    /// ```
1436    ///
1437    /// Thing-level forms must explicitly specify the operation, otherwise `ThingBuilder::build`
1438    /// returns an error:
1439    ///
1440    /// ```
1441    /// # use wot_td::{builder::Error, thing::Thing};
1442    /// #
1443    /// let error = Thing::builder("Thing name")
1444    ///     .finish_extend()
1445    ///     .form(|builder| builder.href("form_href"))
1446    ///     .build()
1447    ///     .unwrap_err();
1448    ///
1449    /// assert_eq!(error, Error::MissingOpInForm);
1450    /// ```
1451    ///
1452    /// Furthermore, Thing-level form operations must be one or more of the following
1453    /// [`FormOperation`]s:
1454    ///
1455    /// - `ReadAllProperties`
1456    /// - `WriteAllProperties`
1457    /// - `ObserveAllProperties`
1458    /// - `UnobserveAllProperties`
1459    /// - `SubscribeAllEvents`
1460    /// - `UnsubscribeAllEvents`
1461    /// - `QueryAllActions`
1462    ///
1463    /// If any other `FormOperation` is specified, `ThingBuilder::build` returns an error:
1464    ///
1465    /// ```
1466    /// # use wot_td::{
1467    /// #     builder::{Error, FormContext},
1468    /// #     thing::{FormOperation, Thing},
1469    /// # };
1470    /// #
1471    /// let error = Thing::builder("Thing name")
1472    ///     .finish_extend()
1473    ///     .form(|builder| builder.href("form_href").op(FormOperation::ReadProperty))
1474    ///     .build()
1475    ///     .unwrap_err();
1476    ///
1477    /// assert_eq!(
1478    ///     error,
1479    ///     Error::InvalidOpInForm {
1480    ///         context: FormContext::Thing,
1481    ///         operation: FormOperation::ReadProperty,
1482    ///     }
1483    /// );
1484    /// ```
1485    pub fn form<F, R>(mut self, f: F) -> Self
1486    where
1487        F: FnOnce(FormBuilder<Other, (), <Other::Form as Extendable>::Empty>) -> R,
1488        R: Into<FormBuilder<Other, String, Other::Form>>,
1489    {
1490        self.forms
1491            .get_or_insert_with(Default::default)
1492            .push(f(FormBuilder::new()).into());
1493        self
1494    }
1495}
1496
1497impl<Other> ThingBuilder<Other, Extended>
1498where
1499    Other: ExtendableThing,
1500{
1501    /// Adds a new _URI variable_.
1502    ///
1503    /// It takes a function that accepts a `DataSchema` builder and must return a
1504    /// type convertible into a `DataSchema`.
1505    ///
1506    /// See [`DataSchemaBuilder`] for more information about how the underlying builder works.
1507    ///
1508    /// # Examples
1509    ///
1510    /// ```
1511    /// # use serde_json::json;
1512    /// # use wot_td::{builder::data_schema::SpecializableDataSchema, thing::Thing};
1513    /// #
1514    /// let thing = Thing::builder("Thing name")
1515    ///     .finish_extend()
1516    ///     .uri_variable("var", |builder| builder.finish_extend().number())
1517    ///     .build()
1518    ///     .unwrap();
1519    ///
1520    /// assert_eq!(
1521    ///     serde_json::to_value(thing).unwrap(),
1522    ///     json!({
1523    ///         "title": "Thing name",
1524    ///         "@context": "https://www.w3.org/2022/wot/td/v1.1",
1525    ///         "uriVariables": {
1526    ///             "var": {
1527    ///                 "type": "number",
1528    ///                 "readOnly": false,
1529    ///                 "writeOnly": false,
1530    ///             },
1531    ///         },
1532    ///         "security": [],
1533    ///         "securityDefinitions": {},
1534    ///     })
1535    /// );
1536    /// ```
1537    pub fn uri_variable<F, T>(mut self, name: impl Into<String>, f: F) -> Self
1538    where
1539        F: FnOnce(
1540            DataSchemaBuilder<
1541                <Other::DataSchema as Extendable>::Empty,
1542                Other::ArraySchema,
1543                Other::ObjectSchema,
1544                ToExtend,
1545            >,
1546        ) -> T,
1547        T: Into<UncheckedDataSchemaFromOther<Other>>,
1548        Other::DataSchema: Extendable,
1549    {
1550        self.uri_variables
1551            .get_or_insert_with(Default::default)
1552            .insert(
1553                name.into(),
1554                f(DataSchemaBuilder::<Other::DataSchema, _, _, _>::empty()).into(),
1555            );
1556        self
1557    }
1558
1559    /// Adds a new property affordance.
1560    ///
1561    /// It takes a function that accepts a `PropertyAffordance` builder and must return a type
1562    /// convertible into an _usable_ `PropertyAffordance` builder.
1563    ///
1564    /// See [`PropertyAffordanceBuilder`] for more information about how the underlying builder
1565    /// works.
1566    pub fn property<F, T>(mut self, name: impl Into<String>, f: F) -> Self
1567    where
1568        F: FnOnce(
1569            PropertyAffordanceBuilder<
1570                Other,
1571                PartialDataSchemaBuilder<
1572                    <Other::DataSchema as Extendable>::Empty,
1573                    Other::ArraySchema,
1574                    Other::ObjectSchema,
1575                    ToExtend,
1576                >,
1577                <Other::InteractionAffordance as Extendable>::Empty,
1578                <Other::PropertyAffordance as Extendable>::Empty,
1579            >,
1580        ) -> T,
1581        T: IntoUsable<UsablePropertyAffordanceBuilder<Other>>,
1582        Other::DataSchema: Extendable,
1583        Other::InteractionAffordance: Extendable,
1584        Other::PropertyAffordance: Extendable,
1585    {
1586        let affordance = f(PropertyAffordanceBuilder::empty()).into_usable();
1587        let affordance_builder = AffordanceBuilder {
1588            name: name.into(),
1589            affordance,
1590        };
1591        self.properties.push(affordance_builder);
1592        self
1593    }
1594
1595    /// Adds a new action affordance.
1596    ///
1597    /// It takes a function that accepts a `ActionAffordance` builder and must return a type
1598    /// convertible into an _usable_ `ActionAffordance` builder.
1599    ///
1600    /// See [`ActionAffordanceBuilder`] for more information about how the underlying builder
1601    /// works.
1602    pub fn action<F, T>(mut self, name: impl Into<String>, f: F) -> Self
1603    where
1604        F: FnOnce(
1605            ActionAffordanceBuilder<
1606                Other,
1607                <Other::InteractionAffordance as Extendable>::Empty,
1608                <Other::ActionAffordance as Extendable>::Empty,
1609            >,
1610        ) -> T,
1611        Other::InteractionAffordance: Extendable,
1612        Other::ActionAffordance: Extendable,
1613        T: IntoUsable<
1614            ActionAffordanceBuilder<Other, Other::InteractionAffordance, Other::ActionAffordance>,
1615        >,
1616    {
1617        let affordance = f(ActionAffordanceBuilder::empty()).into_usable();
1618        let affordance_builder = AffordanceBuilder {
1619            name: name.into(),
1620            affordance,
1621        };
1622        self.actions.push(affordance_builder);
1623        self
1624    }
1625
1626    /// Adds a new event affordance.
1627    ///
1628    /// It takes a function that accepts an `EventAffordance` builder and must return a type
1629    /// convertible into an _usable_ `EventAffordance` builder.
1630    ///
1631    /// See [`EventAffordanceBuilder`] for more information about how the underlying builder works.
1632    pub fn event<F, T>(mut self, name: impl Into<String>, f: F) -> Self
1633    where
1634        F: FnOnce(
1635            EventAffordanceBuilder<
1636                Other,
1637                <Other::InteractionAffordance as Extendable>::Empty,
1638                <Other::EventAffordance as Extendable>::Empty,
1639            >,
1640        ) -> T,
1641        Other::InteractionAffordance: Extendable,
1642        Other::EventAffordance: Extendable,
1643        T: IntoUsable<
1644            EventAffordanceBuilder<Other, Other::InteractionAffordance, Other::EventAffordance>,
1645        >,
1646    {
1647        let affordance = f(EventAffordanceBuilder::empty()).into_usable();
1648        let affordance_builder = AffordanceBuilder {
1649            name: name.into(),
1650            affordance,
1651        };
1652        self.events.push(affordance_builder);
1653        self
1654    }
1655
1656    /// Adds a new schema definition.
1657    ///
1658    /// It takes the name for the schema definition and a function that takes a `DataSchema`
1659    /// builder and must return a type convertible into an _unchecked_ `DataSchema`.
1660    ///
1661    /// See [`DataSchemaBuilder`] for more information about how the underlying builder works.
1662    pub fn schema_definition<F, T>(mut self, name: impl Into<String>, f: F) -> Self
1663    where
1664        F: FnOnce(
1665            DataSchemaBuilder<
1666                <Other::DataSchema as Extendable>::Empty,
1667                Other::ArraySchema,
1668                Other::ObjectSchema,
1669                ToExtend,
1670            >,
1671        ) -> T,
1672        T: Into<UncheckedDataSchemaFromOther<Other>>,
1673        Other::DataSchema: Extendable,
1674    {
1675        self.schema_definitions.insert(
1676            name.into(),
1677            f(DataSchemaBuilder::<Other::DataSchema, _, _, _>::empty()).into(),
1678        );
1679        self
1680    }
1681}
1682
1683fn try_build_affordance<A, F, IA, G, DS, T, H, const N: usize>(
1684    affordances: Vec<AffordanceBuilder<A>>,
1685    affordance_type: AffordanceType,
1686    mut get_interaction: F,
1687    mut get_data_schemas: G,
1688    is_allowed_op: H,
1689    security_definitions: &HashMap<String, SecurityScheme>,
1690) -> Result<Option<HashMap<String, T>>, Error>
1691where
1692    F: FnMut(&A) -> &IA,
1693    IA: CheckableInteractionAffordanceBuilder,
1694    G: FnMut(&A) -> [Option<&DS>; N],
1695    DS: CheckableDataSchema,
1696    A: BuildableAffordance<Target = T>,
1697    H: Fn(FormOperation) -> bool,
1698{
1699    affordances
1700        .is_empty()
1701        .not()
1702        .then(|| {
1703            let new_affordances = HashMap::with_capacity(affordances.len());
1704            affordances
1705                .into_iter()
1706                .try_fold(new_affordances, |mut affordances, affordance| {
1707                    let AffordanceBuilder { name, affordance } = affordance;
1708
1709                    get_interaction(&affordance).check(
1710                        security_definitions,
1711                        affordance_type,
1712                        &is_allowed_op,
1713                    )?;
1714                    get_data_schemas(&affordance)
1715                        .into_iter()
1716                        .flatten()
1717                        .try_for_each(CheckableDataSchema::check)?;
1718
1719                    match affordances.entry(name) {
1720                        Entry::Vacant(entry) => {
1721                            entry.insert(affordance.build()?);
1722                            Ok(affordances)
1723                        }
1724                        Entry::Occupied(entry) => {
1725                            let name = entry.key().to_owned();
1726                            Err(Error::DuplicatedAffordance {
1727                                ty: affordance_type,
1728                                name,
1729                            })
1730                        }
1731                    }
1732                })
1733        })
1734        .transpose()
1735}
1736
1737enum Context {
1738    Simple(String),
1739    Map(HashMap<String, String>),
1740}
1741
1742impl Context {
1743    fn into_simple(self) -> Option<String> {
1744        match self {
1745            Self::Simple(s) => Some(s),
1746            _ => None,
1747        }
1748    }
1749}
1750
1751/// Builder to create a structured JSON-LD @context with multiple namespaces
1752///
1753/// It is instantiated by [`ThingBuilder::context_map`]
1754#[must_use]
1755pub struct ContextMapBuilder(HashMap<String, String>);
1756
1757impl ContextMapBuilder {
1758    /// Add a JSON-LD @context entry with a specific namespace
1759    pub fn context(&mut self, name: impl Into<String>, value: impl Into<String>) -> &mut Self {
1760        self.0.insert(name.into(), value.into());
1761        self
1762    }
1763}
1764
1765/// Builder for language-specific variants of a field (e.g. titles, descriptions)
1766#[derive(Clone, Debug, Default, PartialEq, Eq)]
1767pub struct MultiLanguageBuilder<T> {
1768    values: HashMap<String, T>,
1769}
1770
1771impl<T> MultiLanguageBuilder<T> {
1772    /// Add the language-specific variant
1773    ///
1774    /// NOTE: The language key is currently free-form
1775    pub fn add(&mut self, language: impl Into<String>, value: impl Into<T>) -> &mut Self {
1776        self.values.insert(language.into(), value.into());
1777        self
1778    }
1779
1780    pub(crate) fn build(self) -> Result<HashMap<LanguageTag<String>, T>, Error> {
1781        self.values
1782            .into_iter()
1783            .map(|(k, v)| {
1784                // See https://github.com/oxigraph/oxilangtag/issues/4 for the reason of this,
1785                // which unnecessarily allocate.
1786                k.parse()
1787                    .map(|k| (k, v))
1788                    .map_err(|_| Error::InvalidLanguageTag(k))
1789            })
1790            .collect()
1791    }
1792}
1793
1794/// Builder for Thing Description Links
1795pub struct LinkBuilder<Href> {
1796    href: Href,
1797    ty: Option<String>,
1798    rel: Option<String>,
1799    anchor: Option<String>,
1800    sizes: Option<String>,
1801    hreflang: Vec<String>,
1802}
1803
1804impl LinkBuilder<()> {
1805    const fn new() -> Self {
1806        Self {
1807            href: (),
1808            ty: None,
1809            rel: None,
1810            anchor: None,
1811            sizes: None,
1812            hreflang: Vec::new(),
1813        }
1814    }
1815
1816    /// Create a builder with the defined href
1817    pub fn href(self, value: impl Into<String>) -> LinkBuilder<String> {
1818        let Self {
1819            href: (),
1820            ty,
1821            rel,
1822            anchor,
1823            sizes,
1824            hreflang,
1825        } = self;
1826
1827        let href = value.into();
1828        LinkBuilder {
1829            href,
1830            ty,
1831            rel,
1832            anchor,
1833            sizes,
1834            hreflang,
1835        }
1836    }
1837}
1838
1839impl<T> LinkBuilder<T> {
1840    opt_field_builder!(ty: String, rel: String, anchor: String, sizes: String);
1841
1842    /// Appends an hreflang parameter that will be checked in the call to [`ThingBuilder::build`].
1843    pub fn hreflang(mut self, value: impl Into<String>) -> Self {
1844        self.hreflang.push(value.into());
1845        self
1846    }
1847}
1848
1849/// The builder elements related to security
1850pub mod security {
1851    use alloc::{borrow::Cow, string::*, vec::Vec};
1852
1853    use serde_json::Value;
1854
1855    use crate::thing::{
1856        ApiKeySecurityScheme, BasicSecurityScheme, BearerSecurityScheme, ComboSecurityScheme,
1857        DigestSecurityScheme, KnownSecuritySchemeSubtype, OAuth2SecurityScheme, PskSecurityScheme,
1858        QualityOfProtection, SecurityAuthenticationLocation, SecuritySchemeSubtype,
1859        UnknownSecuritySchemeSubtype,
1860    };
1861
1862    use crate::builder::MultiLanguageBuilder;
1863
1864    /// Builder for the Security Scheme
1865    pub struct SecuritySchemeBuilder<S> {
1866        pub(crate) attype: Option<Vec<String>>,
1867        pub(crate) description: Option<String>,
1868        pub(crate) descriptions: Option<MultiLanguageBuilder<String>>,
1869        pub(crate) proxy: Option<String>,
1870        pub(crate) name: Option<String>,
1871        pub(crate) subtype: S,
1872        pub(crate) required: bool,
1873    }
1874
1875    /// Placeholder Type for the NoSecurity Scheme
1876    pub struct SecuritySchemeNoSecTag;
1877
1878    /// Placeholder Type for the Auto Security Scheme
1879    pub struct SecuritySchemeAutoTag;
1880
1881    /// Placeholder Type for the Combo Security Scheme without `allOf` or `oneOf`
1882    pub struct EmptyComboSecuritySchemeTag;
1883
1884    /// Placeholder Type for the Combo Security Scheme using `allOf`
1885    #[derive(Debug, Default, PartialEq, Eq)]
1886    pub struct AllOfComboSecuritySchemeTag;
1887
1888    /// Placeholder Type for the Combo Security Scheme using `oneOf`
1889    #[derive(Debug, Default, PartialEq, Eq)]
1890    pub struct OneOfComboSecuritySchemeTag;
1891
1892    /// Builder for the Security Scheme Subtype
1893    pub trait BuildableSecuritySchemeSubtype {
1894        /// Consume the builder and produce the SecuritySchemeSubtype
1895        fn build(self) -> SecuritySchemeSubtype;
1896    }
1897
1898    impl SecuritySchemeBuilder<()> {
1899        /// Default no-security scheme
1900        pub fn no_sec(self) -> SecuritySchemeBuilder<SecuritySchemeNoSecTag> {
1901            let Self {
1902                attype,
1903                description,
1904                descriptions,
1905                proxy,
1906                name,
1907                subtype: _,
1908                required,
1909            } = self;
1910
1911            SecuritySchemeBuilder {
1912                attype,
1913                description,
1914                descriptions,
1915                proxy,
1916                name,
1917                subtype: SecuritySchemeNoSecTag,
1918                required,
1919            }
1920        }
1921
1922        /// Auto security scheme
1923        pub fn auto(self) -> SecuritySchemeBuilder<SecuritySchemeAutoTag> {
1924            let Self {
1925                attype,
1926                description,
1927                descriptions,
1928                proxy,
1929                name,
1930                subtype: _,
1931                required,
1932            } = self;
1933
1934            SecuritySchemeBuilder {
1935                attype,
1936                description,
1937                descriptions,
1938                proxy,
1939                name,
1940                subtype: SecuritySchemeAutoTag,
1941                required,
1942            }
1943        }
1944
1945        /// Combo security scheme
1946        pub fn combo(self) -> SecuritySchemeBuilder<EmptyComboSecuritySchemeTag> {
1947            let Self {
1948                attype,
1949                description,
1950                descriptions,
1951                proxy,
1952                name,
1953                subtype: _,
1954                required,
1955            } = self;
1956
1957            SecuritySchemeBuilder {
1958                attype,
1959                description,
1960                descriptions,
1961                proxy,
1962                name,
1963                subtype: EmptyComboSecuritySchemeTag,
1964                required,
1965            }
1966        }
1967
1968        /// Basic Authentication RFC7617
1969        pub fn basic(self) -> SecuritySchemeBuilder<BasicSecurityScheme> {
1970            let Self {
1971                attype,
1972                description,
1973                descriptions,
1974                proxy,
1975                name,
1976                subtype: _,
1977                required,
1978            } = self;
1979
1980            SecuritySchemeBuilder {
1981                attype,
1982                description,
1983                descriptions,
1984                proxy,
1985                name,
1986                subtype: BasicSecurityScheme::default(),
1987                required,
1988            }
1989        }
1990
1991        /// Digest Assess Authentication RFC7616
1992        pub fn digest(self) -> SecuritySchemeBuilder<DigestSecurityScheme> {
1993            let Self {
1994                attype,
1995                description,
1996                descriptions,
1997                proxy,
1998                name,
1999                subtype: _,
2000                required,
2001            } = self;
2002
2003            SecuritySchemeBuilder {
2004                attype,
2005                description,
2006                descriptions,
2007                proxy,
2008                name,
2009                subtype: DigestSecurityScheme::default(),
2010                required,
2011            }
2012        }
2013
2014        /// Bearer Token RFC6750
2015        pub fn bearer(self) -> SecuritySchemeBuilder<BearerSecurityScheme> {
2016            let Self {
2017                attype,
2018                description,
2019                descriptions,
2020                proxy,
2021                name,
2022                subtype: _,
2023                required,
2024            } = self;
2025
2026            SecuritySchemeBuilder {
2027                attype,
2028                description,
2029                descriptions,
2030                proxy,
2031                name,
2032                subtype: BearerSecurityScheme::default(),
2033                required,
2034            }
2035        }
2036
2037        /// Pre-shared key authentication
2038        pub fn psk(self) -> SecuritySchemeBuilder<PskSecurityScheme> {
2039            let Self {
2040                attype,
2041                description,
2042                descriptions,
2043                proxy,
2044                name,
2045                subtype: _,
2046                required,
2047            } = self;
2048
2049            SecuritySchemeBuilder {
2050                attype,
2051                description,
2052                descriptions,
2053                proxy,
2054                name,
2055                subtype: PskSecurityScheme::default(),
2056                required,
2057            }
2058        }
2059
2060        /// OAuth2 authentication RFC6749 and RFC8252
2061        pub fn oauth2(
2062            self,
2063            flow: impl Into<String>,
2064        ) -> SecuritySchemeBuilder<OAuth2SecurityScheme> {
2065            let Self {
2066                attype,
2067                description,
2068                descriptions,
2069                proxy,
2070                name,
2071                subtype: _,
2072                required,
2073            } = self;
2074
2075            SecuritySchemeBuilder {
2076                attype,
2077                description,
2078                descriptions,
2079                proxy,
2080                name,
2081                subtype: OAuth2SecurityScheme::new(flow),
2082                required,
2083            }
2084        }
2085
2086        /// API key authentication
2087        pub fn apikey(self) -> SecuritySchemeBuilder<ApiKeySecurityScheme> {
2088            let Self {
2089                attype,
2090                description,
2091                descriptions,
2092                proxy,
2093                name,
2094                subtype: _,
2095                required,
2096            } = self;
2097
2098            SecuritySchemeBuilder {
2099                attype,
2100                description,
2101                descriptions,
2102                proxy,
2103                name,
2104                subtype: ApiKeySecurityScheme::default(),
2105                required,
2106            }
2107        }
2108
2109        /// Security scheme defined by an additional Vocabulary
2110        ///
2111        /// NOTE: Its definition MUST be in the Thing @context.
2112        pub fn custom(
2113            self,
2114            scheme: impl Into<String>,
2115        ) -> SecuritySchemeBuilder<UnknownSecuritySchemeSubtype> {
2116            let Self {
2117                attype,
2118                description,
2119                descriptions,
2120                proxy,
2121                name,
2122                subtype: _,
2123                required,
2124            } = self;
2125
2126            let scheme = scheme.into();
2127            SecuritySchemeBuilder {
2128                attype,
2129                description,
2130                descriptions,
2131                proxy,
2132                name,
2133                subtype: UnknownSecuritySchemeSubtype {
2134                    scheme,
2135                    data: Value::Null,
2136                },
2137                required,
2138            }
2139        }
2140    }
2141
2142    impl<T> SecuritySchemeBuilder<T> {
2143        opt_field_builder!(description: String, proxy: String);
2144
2145        /// JSON-LD @type
2146        pub fn attype(mut self, ty: impl Into<String>) -> Self {
2147            self.attype
2148                .get_or_insert_with(Default::default)
2149                .push(ty.into());
2150            self
2151        }
2152
2153        /// Multi-language descriptions
2154        ///
2155        /// See [`ThingBuilder::titles`] for examples.
2156        ///
2157        /// [`ThingBuilder::titles`]: crate::builder::ThingBuilder::titles
2158        pub fn descriptions<F>(mut self, f: F) -> Self
2159        where
2160            F: FnOnce(&mut MultiLanguageBuilder<String>) -> &mut MultiLanguageBuilder<String>,
2161        {
2162            let mut builder = MultiLanguageBuilder::default();
2163            f(&mut builder);
2164            self.descriptions = Some(builder);
2165            self
2166        }
2167
2168        /// Sets the key to be used for referring to the security scheme in the [`Thing::security`] and
2169        /// [`Form::security`] fields.
2170        ///
2171        /// [`Thing::security`]: crate::thing::Thing::security
2172        /// [`Form::security`]: crate::thing::Form::security
2173        pub fn with_key(mut self, name: impl Into<String>) -> Self {
2174            self.name = Some(name.into());
2175            self
2176        }
2177
2178        /// Makes the security scheme required.
2179        pub fn required(mut self) -> Self {
2180            self.required = true;
2181            self
2182        }
2183    }
2184
2185    macro_rules! impl_buildable_known_security_scheme_subtype {
2186    ($($variant:ident => $ty:ty),* $(,)?) => {
2187        $(
2188            impl BuildableSecuritySchemeSubtype for $ty {
2189                fn build(self) -> SecuritySchemeSubtype {
2190                    SecuritySchemeSubtype::Known(KnownSecuritySchemeSubtype::$variant(self))
2191                }
2192            }
2193        )*
2194    };
2195}
2196
2197    impl_buildable_known_security_scheme_subtype! (
2198        Basic => BasicSecurityScheme,
2199        Digest => DigestSecurityScheme,
2200        Bearer => BearerSecurityScheme,
2201        Psk => PskSecurityScheme,
2202        OAuth2 => OAuth2SecurityScheme,
2203        ApiKey => ApiKeySecurityScheme,
2204    );
2205
2206    impl BuildableSecuritySchemeSubtype for SecuritySchemeNoSecTag {
2207        fn build(self) -> SecuritySchemeSubtype {
2208            SecuritySchemeSubtype::Known(KnownSecuritySchemeSubtype::NoSec)
2209        }
2210    }
2211
2212    impl BuildableSecuritySchemeSubtype for SecuritySchemeAutoTag {
2213        fn build(self) -> SecuritySchemeSubtype {
2214            SecuritySchemeSubtype::Known(KnownSecuritySchemeSubtype::Auto)
2215        }
2216    }
2217
2218    impl BuildableSecuritySchemeSubtype for (AllOfComboSecuritySchemeTag, Vec<String>) {
2219        fn build(self) -> SecuritySchemeSubtype {
2220            SecuritySchemeSubtype::Known(KnownSecuritySchemeSubtype::Combo(
2221                ComboSecurityScheme::AllOf(self.1),
2222            ))
2223        }
2224    }
2225
2226    impl BuildableSecuritySchemeSubtype for (OneOfComboSecuritySchemeTag, Vec<String>) {
2227        fn build(self) -> SecuritySchemeSubtype {
2228            SecuritySchemeSubtype::Known(KnownSecuritySchemeSubtype::Combo(
2229                ComboSecurityScheme::OneOf(self.1),
2230            ))
2231        }
2232    }
2233
2234    impl BuildableSecuritySchemeSubtype for UnknownSecuritySchemeSubtype {
2235        fn build(self) -> SecuritySchemeSubtype {
2236            SecuritySchemeSubtype::Unknown(self)
2237        }
2238    }
2239
2240    /// Accessor for the Name and Location fields
2241    ///
2242    /// All the Security Schemes but NoSec have the `name` and location (`in`) fields.
2243    pub trait HasNameLocation {
2244        /// Specifies the location of security authentication information
2245        fn location_mut(&mut self) -> &mut SecurityAuthenticationLocation;
2246        /// Name for query, header, or cookie parameters
2247        fn name_mut(&mut self) -> &mut Option<String>;
2248    }
2249
2250    impl HasNameLocation for BasicSecurityScheme {
2251        fn location_mut(&mut self) -> &mut SecurityAuthenticationLocation {
2252            &mut self.location
2253        }
2254
2255        fn name_mut(&mut self) -> &mut Option<String> {
2256            &mut self.name
2257        }
2258    }
2259
2260    impl HasNameLocation for DigestSecurityScheme {
2261        fn location_mut(&mut self) -> &mut SecurityAuthenticationLocation {
2262            &mut self.location
2263        }
2264
2265        fn name_mut(&mut self) -> &mut Option<String> {
2266            &mut self.name
2267        }
2268    }
2269
2270    impl HasNameLocation for ApiKeySecurityScheme {
2271        fn location_mut(&mut self) -> &mut SecurityAuthenticationLocation {
2272            &mut self.location
2273        }
2274
2275        fn name_mut(&mut self) -> &mut Option<String> {
2276            &mut self.name
2277        }
2278    }
2279
2280    impl HasNameLocation for BearerSecurityScheme {
2281        fn location_mut(&mut self) -> &mut SecurityAuthenticationLocation {
2282            &mut self.location
2283        }
2284
2285        fn name_mut(&mut self) -> &mut Option<String> {
2286            &mut self.name
2287        }
2288    }
2289
2290    impl<T> SecuritySchemeBuilder<T>
2291    where
2292        T: HasNameLocation,
2293    {
2294        /// Name for query, header or cookie parameter
2295        pub fn name(mut self, value: impl Into<String>) -> Self {
2296            *self.subtype.name_mut() = Some(value.into());
2297            self
2298        }
2299
2300        /// Location of the security authentication information
2301        pub fn location(mut self, value: SecurityAuthenticationLocation) -> Self {
2302            *self.subtype.location_mut() = value;
2303            self
2304        }
2305    }
2306
2307    impl SecuritySchemeBuilder<EmptyComboSecuritySchemeTag> {
2308        /// Require all the specified schema definitions for the security combo.
2309        ///
2310        /// # Examples
2311        /// ```
2312        /// # use serde_json::json;
2313        /// # use wot_td::thing::Thing;
2314        /// #
2315        /// let thing = Thing::builder("Thing name")
2316        ///     .finish_extend()
2317        ///     .security(|builder| builder.combo().all_of(["basic", "nosec"]))
2318        ///     .security(|builder| builder.basic())
2319        ///     .security(|builder| builder.no_sec())
2320        ///     .build()
2321        ///     .unwrap();
2322        ///
2323        /// assert_eq!(
2324        ///     serde_json::to_value(thing).unwrap(),
2325        ///     json!({
2326        ///         "title": "Thing name",
2327        ///         "@context": "https://www.w3.org/2022/wot/td/v1.1",
2328        ///         "security": [],
2329        ///         "securityDefinitions": {
2330        ///             "combo": {
2331        ///                 "scheme": "combo",
2332        ///                 "allOf": ["basic", "nosec"],
2333        ///             },
2334        ///             "basic": {
2335        ///                 "scheme": "basic",
2336        ///                 "in": "header",
2337        ///             },
2338        ///             "nosec": {
2339        ///                 "scheme": "nosec",
2340        ///             },
2341        ///         },
2342        ///     })
2343        /// );
2344        /// ```
2345        ///
2346        /// If one the _security identifier_ specified in `.all_of` does not exist across other
2347        /// security definitions, `Thing::build` returns an error:
2348        ///
2349        /// ```
2350        /// # use wot_td::{builder::Error, thing::Thing};
2351        /// #
2352        /// let error = Thing::builder("Thing name")
2353        ///     .finish_extend()
2354        ///     .security(|builder| builder.combo().all_of(["basic", "nosec"]))
2355        ///     .security(|builder| builder.no_sec())
2356        ///     .build()
2357        ///     .unwrap_err();
2358        ///
2359        /// assert_eq!(error, Error::MissingSchemaDefinition("basic".to_string()));
2360        /// ```
2361        pub fn all_of<I, T>(
2362            self,
2363            iter: I,
2364        ) -> SecuritySchemeBuilder<(AllOfComboSecuritySchemeTag, Vec<String>)>
2365        where
2366            I: IntoIterator<Item = T>,
2367            T: Into<String>,
2368        {
2369            let Self {
2370                attype,
2371                description,
2372                descriptions,
2373                proxy,
2374                name,
2375                subtype: _,
2376                required,
2377            } = self;
2378            let subtype = (AllOfComboSecuritySchemeTag, Vec::new());
2379
2380            SecuritySchemeBuilder {
2381                attype,
2382                description,
2383                descriptions,
2384                proxy,
2385                name,
2386                subtype,
2387                required,
2388            }
2389            .extend(iter)
2390        }
2391
2392        /// Require one of the specified schema definitions for the security combo.
2393        ///
2394        /// # Examples
2395        /// ```
2396        /// # use serde_json::json;
2397        /// # use wot_td::thing::Thing;
2398        /// #
2399        /// let thing = Thing::builder("Thing name")
2400        ///     .finish_extend()
2401        ///     .security(|builder| builder.combo().one_of(["basic", "nosec"]))
2402        ///     .security(|builder| builder.basic())
2403        ///     .security(|builder| builder.no_sec())
2404        ///     .build()
2405        ///     .unwrap();
2406        ///
2407        /// assert_eq!(
2408        ///     serde_json::to_value(thing).unwrap(),
2409        ///     json!({
2410        ///         "title": "Thing name",
2411        ///         "@context": "https://www.w3.org/2022/wot/td/v1.1",
2412        ///         "security": [],
2413        ///         "securityDefinitions": {
2414        ///             "combo": {
2415        ///                 "scheme": "combo",
2416        ///                 "oneOf": ["basic", "nosec"],
2417        ///             },
2418        ///             "basic": {
2419        ///                 "scheme": "basic",
2420        ///                 "in": "header",
2421        ///             },
2422        ///             "nosec": {
2423        ///                 "scheme": "nosec",
2424        ///             },
2425        ///         },
2426        ///     })
2427        /// );
2428        /// ```
2429        ///
2430        /// If one the _security identifier_ specified in `.one_of` does not exist across other
2431        /// security definitions, `Thing::build` returns an error:
2432        ///
2433        /// ```
2434        /// # use wot_td::{builder::Error, thing::Thing};
2435        /// #
2436        /// let error = Thing::builder("Thing name")
2437        ///     .finish_extend()
2438        ///     .security(|builder| builder.combo().one_of(["basic", "nosec"]))
2439        ///     .security(|builder| builder.no_sec())
2440        ///     .build()
2441        ///     .unwrap_err();
2442        ///
2443        /// assert_eq!(error, Error::MissingSchemaDefinition("basic".to_string()));
2444        /// ```
2445        pub fn one_of<I, T>(
2446            self,
2447            iter: I,
2448        ) -> SecuritySchemeBuilder<(OneOfComboSecuritySchemeTag, Vec<String>)>
2449        where
2450            I: IntoIterator<Item = T>,
2451            T: Into<String>,
2452        {
2453            let Self {
2454                attype,
2455                description,
2456                descriptions,
2457                proxy,
2458                name,
2459                subtype: _,
2460                required,
2461            } = self;
2462            let subtype = (OneOfComboSecuritySchemeTag, Vec::new());
2463
2464            SecuritySchemeBuilder {
2465                attype,
2466                description,
2467                descriptions,
2468                proxy,
2469                name,
2470                subtype,
2471                required,
2472            }
2473            .extend(iter)
2474        }
2475
2476        /// Name for query, header or cookie parameter
2477        pub fn name(mut self, value: impl Into<String>) -> Self {
2478            self.name = Some(value.into());
2479            self
2480        }
2481    }
2482
2483    impl<T> SecuritySchemeBuilder<(T, Vec<String>)> {
2484        /// Extends the security scheme subtype with a variable amount of items.
2485        ///
2486        /// This is useful for _combo_ security schemes, which require a set of names as references.
2487        pub fn extend<I, U>(mut self, iter: I) -> Self
2488        where
2489            I: IntoIterator<Item = U>,
2490            U: Into<String>,
2491        {
2492            self.subtype.1.extend(iter.into_iter().map(Into::into));
2493            self
2494        }
2495
2496        /// Extends the security scheme subtype with an items.
2497        ///
2498        /// This is useful for _combo_ security schemes, which require a set of names as references.
2499        pub fn push(mut self, security_name: impl Into<String>) -> Self {
2500            self.subtype.1.push(security_name.into());
2501            self
2502        }
2503
2504        /// Name for query, header or cookie parameter
2505        pub fn name(mut self, value: impl Into<String>) -> Self {
2506            self.name = Some(value.into());
2507            self
2508        }
2509    }
2510
2511    impl SecuritySchemeBuilder<DigestSecurityScheme> {
2512        /// Quality of protection
2513        pub fn qop(mut self, value: QualityOfProtection) -> Self {
2514            self.subtype.qop = value;
2515            self
2516        }
2517    }
2518
2519    impl SecuritySchemeBuilder<BearerSecurityScheme> {
2520        /// URI of the authorization server
2521        pub fn authorization(mut self, value: impl Into<String>) -> Self {
2522            self.subtype.authorization = Some(value.into());
2523            self
2524        }
2525
2526        /// Encoding, encryption or digest algorithm
2527        pub fn alg(mut self, value: impl Into<Cow<'static, str>>) -> Self {
2528            self.subtype.alg = value.into();
2529            self
2530        }
2531
2532        /// Format of the security authentication information
2533        pub fn format(mut self, value: impl Into<Cow<'static, str>>) -> Self {
2534            self.subtype.format = value.into();
2535            self
2536        }
2537    }
2538
2539    impl SecuritySchemeBuilder<OAuth2SecurityScheme> {
2540        /// URI of the authorization server
2541        pub fn authorization(mut self, value: impl Into<String>) -> Self {
2542            self.subtype.authorization = Some(value.into());
2543            self
2544        }
2545
2546        /// URI of the token server
2547        pub fn token(mut self, value: impl Into<String>) -> Self {
2548            self.subtype.token = Some(value.into());
2549            self
2550        }
2551
2552        /// URI of the refresh server
2553        pub fn refresh(mut self, value: impl Into<String>) -> Self {
2554            self.subtype.refresh = Some(value.into());
2555            self
2556        }
2557
2558        /// Authorization scope identifier
2559        pub fn scope(mut self, value: impl Into<String>) -> Self {
2560            self.subtype
2561                .scopes
2562                .get_or_insert_with(Default::default)
2563                .push(value.into());
2564            self
2565        }
2566    }
2567
2568    impl SecuritySchemeBuilder<UnknownSecuritySchemeSubtype> {
2569        /// JSON Value to be merged into the Scheme
2570        pub fn data(mut self, value: impl Into<Value>) -> Self {
2571            self.subtype.data = value.into();
2572            self
2573        }
2574    }
2575}
2576
2577pub use self::security::*;
2578
2579/// Builder for the Form
2580pub struct FormBuilder<Other: ExtendableThing, Href, OtherForm> {
2581    op: DefaultedFormOperations,
2582    href: Href,
2583    content_type: Option<String>,
2584    content_coding: Option<String>,
2585    subprotocol: Option<String>,
2586    security: Option<Vec<String>>,
2587    scopes: Option<Vec<String>>,
2588    response: Option<ExpectedResponse<Other::ExpectedResponse>>,
2589    additional_responses: Vec<AdditionalExpectedResponse>,
2590
2591    /// Form builder extension.
2592    pub other: OtherForm,
2593    _marker: PhantomData<fn() -> Other>,
2594}
2595
2596impl<Other> FormBuilder<Other, (), <Other::Form as Extendable>::Empty>
2597where
2598    Other: ExtendableThing,
2599    Other::Form: Extendable,
2600{
2601    fn new() -> Self {
2602        let other = <Other::Form as Extendable>::empty();
2603
2604        Self {
2605            op: Default::default(),
2606            href: (),
2607            content_type: Default::default(),
2608            content_coding: Default::default(),
2609            subprotocol: Default::default(),
2610            security: Default::default(),
2611            scopes: Default::default(),
2612            response: Default::default(),
2613            additional_responses: Default::default(),
2614            other,
2615            _marker: PhantomData,
2616        }
2617    }
2618}
2619
2620impl<Other, OtherForm> FormBuilder<Other, (), OtherForm>
2621where
2622    Other: ExtendableThing,
2623{
2624    /// Create a new builder with the specified Href
2625    pub fn href(self, value: impl Into<String>) -> FormBuilder<Other, String, OtherForm> {
2626        let Self {
2627            op,
2628            href: (),
2629            content_type,
2630            content_coding,
2631            subprotocol,
2632            security,
2633            scopes,
2634            response,
2635            additional_responses,
2636            other,
2637            _marker,
2638        } = self;
2639
2640        let href = value.into();
2641        FormBuilder {
2642            op,
2643            href,
2644            content_type,
2645            content_coding,
2646            subprotocol,
2647            security,
2648            scopes,
2649            response,
2650            additional_responses,
2651            other,
2652            _marker,
2653        }
2654    }
2655}
2656
2657impl<Other, Href, OtherForm> FormBuilder<Other, Href, OtherForm>
2658where
2659    Other: ExtendableThing,
2660{
2661    opt_field_builder!(
2662        content_type: String,
2663        content_coding: String,
2664        subprotocol: String,
2665    );
2666
2667    /// Set the form intended operation
2668    ///
2669    /// Depending on its parent the form may have a Default operation
2670    /// or it must be explicitly set.
2671    pub fn op(mut self, new_op: FormOperation) -> Self {
2672        match &mut self.op {
2673            ops @ DefaultedFormOperations::Default => {
2674                *ops = DefaultedFormOperations::Custom(vec![new_op])
2675            }
2676            DefaultedFormOperations::Custom(ops) => ops.push(new_op),
2677        }
2678
2679        self
2680    }
2681
2682    /// Set the security definitions that must be satisfied to access the resource
2683    ///
2684    /// They must be set beforehand by [Thing::security].
2685    pub fn security(mut self, value: impl Into<String>) -> Self {
2686        self.security
2687            .get_or_insert_with(Default::default)
2688            .push(value.into());
2689        self
2690    }
2691
2692    /// Set the authorization scope identifiers
2693    ///
2694    /// It requires an OAuth2 Security Scheme
2695    pub fn scope(mut self, value: impl Into<String>) -> Self {
2696        self.scopes
2697            .get_or_insert_with(Default::default)
2698            .push(value.into());
2699        self
2700    }
2701
2702    /// Adds an additional response to the form builder.
2703    ///
2704    /// It takes a function that takes and returns a mutable reference to a builder for additional
2705    /// expected response.
2706    ///
2707    /// # Example
2708    /// ```
2709    /// # use serde_json::json;
2710    /// # use wot_td::{
2711    /// #     builder::data_schema::SpecializableDataSchema,
2712    /// #     thing::{FormOperation, Thing},
2713    /// # };
2714    /// #
2715    /// let thing = Thing::builder("Thing name")
2716    ///     .finish_extend()
2717    ///     .form(|form_builder| {
2718    ///         form_builder
2719    ///             .href("form_href")
2720    ///             .op(FormOperation::ReadAllProperties)
2721    ///             .additional_response(|builder| {
2722    ///                 builder
2723    ///                     .content_type("application/xml")
2724    ///                     .success()
2725    ///                     .schema("xml_response")
2726    ///             })
2727    ///     })
2728    ///     .schema_definition("xml_response", |b| b.finish_extend().object())
2729    ///     .build()
2730    ///     .unwrap();
2731    ///
2732    /// assert_eq!(
2733    ///     serde_json::to_value(thing).unwrap(),
2734    ///     json!({
2735    ///         "title": "Thing name",
2736    ///         "@context": "https://www.w3.org/2022/wot/td/v1.1",
2737    ///         "forms": [{
2738    ///             "href": "form_href",
2739    ///             "op": ["readallproperties"],
2740    ///             "additionalResponses": {
2741    ///                 "contentType": "application/xml",
2742    ///                 "success": true,
2743    ///                 "schema": "xml_response",
2744    ///             }
2745    ///         }],
2746    ///         "schemaDefinitions": {
2747    ///             "xml_response": {
2748    ///                 "type": "object",
2749    ///                 "readOnly": false,
2750    ///                 "writeOnly": false,
2751    ///             }
2752    ///         },
2753    ///         "security": [],
2754    ///         "securityDefinitions": {},
2755    ///     })
2756    /// );
2757    /// ```
2758    pub fn additional_response<F>(mut self, f: F) -> Self
2759    where
2760        F: FnOnce(&mut AdditionalExpectedResponseBuilder) -> &mut AdditionalExpectedResponseBuilder,
2761    {
2762        let mut builder = AdditionalExpectedResponseBuilder::new();
2763        f(&mut builder);
2764
2765        let AdditionalExpectedResponseBuilder {
2766            success,
2767            content_type,
2768            schema,
2769        } = builder;
2770        let additional_response = AdditionalExpectedResponse {
2771            success,
2772            content_type,
2773            schema,
2774        };
2775
2776        self.additional_responses.push(additional_response);
2777        self
2778    }
2779
2780    /// Extends the form, passing a closure that returns `T`.
2781    ///
2782    /// See module level documentation of [`builder`] for more information.
2783    ///
2784    /// [`builder`]: crate::builder
2785    pub fn ext_with<F, T>(self, f: F) -> FormBuilder<Other, Href, OtherForm::Target>
2786    where
2787        OtherForm: Extend<T>,
2788        F: FnOnce() -> T,
2789    {
2790        let Self {
2791            op,
2792            href,
2793            content_type,
2794            content_coding,
2795            subprotocol,
2796            security,
2797            scopes,
2798            response,
2799            additional_responses,
2800            other,
2801            _marker,
2802        } = self;
2803        let other = other.ext_with(f);
2804        FormBuilder {
2805            op,
2806            href,
2807            content_type,
2808            content_coding,
2809            subprotocol,
2810            security,
2811            scopes,
2812            response,
2813            additional_responses,
2814            other,
2815            _marker,
2816        }
2817    }
2818
2819    /// Extends the form with an additional element.
2820    ///
2821    /// See module level documentation of [`builder`] for more information.
2822    ///
2823    /// [`builder`]: crate::builder
2824    #[inline]
2825    pub fn ext<T>(self, t: T) -> FormBuilder<Other, Href, OtherForm::Target>
2826    where
2827        OtherForm: Extend<T>,
2828    {
2829        self.ext_with(move || t)
2830    }
2831}
2832
2833impl<Other, T, OtherForm> FormBuilder<Other, T, OtherForm>
2834where
2835    Other: ExtendableThing,
2836    Other::ExpectedResponse: Default,
2837{
2838    /// Set the expected response metadata
2839    ///
2840    /// It is optional if the input and output metadata are the same, e.g. the content_type
2841    /// matches.
2842    pub fn response_default_ext(mut self, content_type: impl Into<String>) -> Self {
2843        self.response = Some(ExpectedResponse {
2844            content_type: content_type.into(),
2845            other: Default::default(),
2846        });
2847        self
2848    }
2849}
2850
2851impl<Other, T, OtherForm> FormBuilder<Other, T, OtherForm>
2852where
2853    Other: ExtendableThing,
2854    Other::ExpectedResponse: Extendable,
2855{
2856    /// Set the expected response metadata building the type from ground up
2857    ///
2858    /// It is optional if the input and output metadata are the same, e.g. the content_type
2859    /// matches.
2860    pub fn response<F>(mut self, content_type: impl Into<String>, f: F) -> Self
2861    where
2862        F: FnOnce(<Other::ExpectedResponse as Extendable>::Empty) -> Other::ExpectedResponse,
2863    {
2864        self.response = Some(ExpectedResponse {
2865            content_type: content_type.into(),
2866            other: f(Other::ExpectedResponse::empty()),
2867        });
2868        self
2869    }
2870}
2871
2872impl<Other> From<FormBuilder<Other, String, Other::Form>> for Form<Other>
2873where
2874    Other: ExtendableThing,
2875{
2876    fn from(builder: FormBuilder<Other, String, Other::Form>) -> Self {
2877        let FormBuilder {
2878            op,
2879            href,
2880            content_type,
2881            content_coding,
2882            subprotocol,
2883            security,
2884            scopes,
2885            response,
2886            additional_responses,
2887            other,
2888            _marker: _,
2889        } = builder;
2890
2891        let additional_responses = additional_responses
2892            .is_empty()
2893            .not()
2894            .then_some(additional_responses);
2895
2896        Self {
2897            op,
2898            href,
2899            content_type,
2900            content_coding,
2901            subprotocol,
2902            security,
2903            scopes,
2904            response,
2905            additional_responses,
2906            other,
2907        }
2908    }
2909}
2910
2911/// Builder for the AdditionalExpectedResponse
2912#[derive(Clone, Debug, PartialEq, Eq)]
2913pub struct AdditionalExpectedResponseBuilder {
2914    success: bool,
2915    content_type: Option<String>,
2916    schema: Option<String>,
2917}
2918
2919impl AdditionalExpectedResponseBuilder {
2920    const fn new() -> Self {
2921        Self {
2922            success: false,
2923            content_type: None,
2924            schema: None,
2925        }
2926    }
2927
2928    /// Sets the `success` field to `true`.
2929    pub fn success(&mut self) -> &mut Self {
2930        self.success = true;
2931        self
2932    }
2933
2934    /// Sets the `content_type` field.
2935    pub fn content_type(&mut self, value: impl Into<String>) -> &mut Self {
2936        self.content_type = Some(value.into());
2937        self
2938    }
2939
2940    /// Sets the `schema` field.
2941    pub fn schema(&mut self, value: impl Into<String>) -> &mut Self {
2942        self.schema = Some(value.into());
2943        self
2944    }
2945}
2946
2947pub(crate) struct UncheckedSecurityScheme {
2948    attype: Option<Vec<String>>,
2949    description: Option<String>,
2950    descriptions: Option<MultiLanguageBuilder<String>>,
2951    proxy: Option<String>,
2952    subtype: SecuritySchemeSubtype,
2953}
2954
2955impl TryFrom<UncheckedSecurityScheme> for SecurityScheme {
2956    type Error = Error;
2957
2958    fn try_from(scheme: UncheckedSecurityScheme) -> Result<Self, Self::Error> {
2959        let UncheckedSecurityScheme {
2960            attype,
2961            description,
2962            descriptions,
2963            proxy,
2964            subtype,
2965        } = scheme;
2966
2967        let descriptions = descriptions
2968            .map(|descriptions| descriptions.build())
2969            .transpose()?;
2970
2971        Ok(Self {
2972            attype,
2973            description,
2974            descriptions,
2975            proxy,
2976            subtype,
2977        })
2978    }
2979}
2980
2981/// The _unchecked_ variant of [`Link`](crate::thing::Link).
2982///
2983/// The type needs to be _try-converted_ into `Link` in order to being used inside a
2984/// [`Thing`](crate::thing::Thing).
2985pub struct UncheckedLink {
2986    href: String,
2987    ty: Option<String>,
2988    rel: Option<String>,
2989    anchor: Option<String>,
2990    sizes: Option<String>,
2991    hreflang: Vec<String>,
2992}
2993
2994impl TryFrom<UncheckedLink> for Link {
2995    type Error = Error;
2996
2997    fn try_from(link: UncheckedLink) -> Result<Self, Self::Error> {
2998        let UncheckedLink {
2999            href,
3000            ty,
3001            rel,
3002            anchor,
3003            sizes,
3004            hreflang,
3005        } = link;
3006
3007        if sizes.is_some() && rel.as_deref() != Some("icon") {
3008            return Err(Error::SizesWithRelNotIcon);
3009        }
3010
3011        let hreflang = hreflang
3012            .into_iter()
3013            .map(|lang| lang.parse().map_err(|_| Error::InvalidLanguageTag(lang)))
3014            .collect::<Result<Vec<_>, _>>()?;
3015        let hreflang = hreflang.is_empty().not().then_some(hreflang);
3016
3017        Ok(Self {
3018            href,
3019            ty,
3020            rel,
3021            anchor,
3022            sizes,
3023            hreflang,
3024        })
3025    }
3026}
3027
3028#[cfg(test)]
3029mod tests {
3030    use alloc::borrow::Cow;
3031
3032    use serde::{Deserialize, Serialize};
3033    use serde_json::json;
3034    use time::macros::datetime;
3035
3036    use crate::{
3037        builder::{
3038            affordance::BuildableInteractionAffordance,
3039            data_schema::{NumberDataSchemaBuilderLike, SpecializableDataSchema},
3040            human_readable_info::BuildableHumanReadableInfo,
3041        },
3042        hlist::{Cons, Nil},
3043        thing::{
3044            ActionAffordance, ApiKeySecurityScheme, BasicSecurityScheme, BearerSecurityScheme,
3045            DataSchema, DataSchemaSubtype, DigestSecurityScheme, EventAffordance,
3046            InteractionAffordance, Maximum, Minimum, NumberSchema, OAuth2SecurityScheme,
3047            ObjectSchema, PropertyAffordance, PskSecurityScheme, QualityOfProtection,
3048            SecurityAuthenticationLocation, SecurityScheme, StringSchema,
3049        },
3050    };
3051
3052    use super::*;
3053
3054    macro_rules! test_opt_string_field_builder {
3055        ($($field:ident),* $(,)?) => {
3056            $(
3057                #[test]
3058                pub fn $field() {
3059                    let thing = ThingBuilder::<Nil, _>::new("MyLampThing").finish_extend().$field("test").build().unwrap();
3060
3061                    assert_eq!(
3062                        thing,
3063                        Thing {
3064                            context: TD_CONTEXT_11.into(),
3065                            title: "MyLampThing".to_string(),
3066                            $field: Some("test".into()),
3067                            ..Default::default()
3068                        }
3069                    );
3070                }
3071            )*
3072        };
3073    }
3074
3075    #[test]
3076    fn default_context() {
3077        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
3078            .finish_extend()
3079            .build()
3080            .unwrap();
3081        assert_eq!(
3082            thing,
3083            Thing {
3084                context: TD_CONTEXT_11.into(),
3085                title: "MyLampThing".to_string(),
3086                ..Default::default()
3087            }
3088        )
3089    }
3090
3091    #[test]
3092    fn redundant_default_context() {
3093        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
3094            .context(TD_CONTEXT_11)
3095            .build()
3096            .unwrap();
3097
3098        assert_eq!(
3099            thing,
3100            Thing {
3101                context: TD_CONTEXT_11.into(),
3102                title: "MyLampThing".to_string(),
3103                ..Default::default()
3104            }
3105        )
3106    }
3107
3108    #[test]
3109    fn simple_contexts() {
3110        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
3111            .context("test")
3112            .context("another_test")
3113            .build()
3114            .unwrap();
3115
3116        assert_eq!(
3117            thing,
3118            Thing {
3119                context: json! {[
3120                    TD_CONTEXT_11,
3121                    "test",
3122                    "another_test",
3123                ]},
3124                title: "MyLampThing".to_string(),
3125                ..Default::default()
3126            }
3127        )
3128    }
3129
3130    #[test]
3131    fn map_contexts() {
3132        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
3133            .context_map(|b| b.context("hello", "world").context("all", "fine"))
3134            .context("simple")
3135            .build()
3136            .unwrap();
3137
3138        assert_eq!(
3139            thing,
3140            Thing {
3141                context: json! {[
3142                    TD_CONTEXT_11,
3143                    {
3144                        "hello": "world",
3145                        "all": "fine",
3146                    },
3147                    "simple",
3148                ]},
3149                title: "MyLampThing".to_string(),
3150                ..Default::default()
3151            }
3152        )
3153    }
3154
3155    test_opt_string_field_builder!(id, description, version, support, base);
3156
3157    #[test]
3158    fn attype() {
3159        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
3160            .attype("test")
3161            .build()
3162            .unwrap();
3163
3164        assert_eq!(
3165            thing,
3166            Thing {
3167                context: TD_CONTEXT_11.into(),
3168                title: "MyLampThing".to_string(),
3169                attype: Some(vec!["test".to_string()]),
3170                ..Default::default()
3171            }
3172        );
3173
3174        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
3175            .attype("test1")
3176            .attype("test2")
3177            .build()
3178            .unwrap();
3179
3180        assert_eq!(
3181            thing,
3182            Thing {
3183                context: TD_CONTEXT_11.into(),
3184                title: "MyLampThing".to_string(),
3185                attype: Some(vec!["test1".to_string(), "test2".to_string()]),
3186                ..Default::default()
3187            }
3188        );
3189    }
3190
3191    #[test]
3192    fn titles() {
3193        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
3194            .titles(|ml| ml.add("en", "My lamp").add("it", "La mia lampada"))
3195            .build()
3196            .unwrap();
3197
3198        assert_eq!(
3199            thing,
3200            Thing {
3201                context: TD_CONTEXT_11.into(),
3202                title: "MyLampThing".to_string(),
3203                titles: Some(
3204                    [("en", "My lamp"), ("it", "La mia lampada")]
3205                        .into_iter()
3206                        .map(|(k, v)| (k.parse().unwrap(), v.to_string()))
3207                        .collect()
3208                ),
3209                ..Default::default()
3210            }
3211        );
3212    }
3213
3214    #[test]
3215    fn descriptions() {
3216        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
3217            .description("My Lamp")
3218            .descriptions(|ml| ml.add("en", "My lamp").add("it", "La mia lampada"))
3219            .build()
3220            .unwrap();
3221
3222        assert_eq!(
3223            thing,
3224            Thing {
3225                context: TD_CONTEXT_11.into(),
3226                title: "MyLampThing".to_string(),
3227                description: Some("My Lamp".to_string()),
3228                descriptions: Some(
3229                    [("en", "My lamp"), ("it", "La mia lampada")]
3230                        .into_iter()
3231                        .map(|(k, v)| (k.parse().unwrap(), v.to_string()))
3232                        .collect()
3233                ),
3234                ..Default::default()
3235            }
3236        );
3237    }
3238
3239    #[test]
3240    fn created() {
3241        const DATETIME: OffsetDateTime = datetime!(2022-05-01 12:13:14.567 +01:00);
3242        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
3243            .created(DATETIME)
3244            .build()
3245            .unwrap();
3246
3247        assert_eq!(
3248            thing,
3249            Thing {
3250                context: TD_CONTEXT_11.into(),
3251                title: "MyLampThing".to_string(),
3252                created: Some(DATETIME),
3253                ..Default::default()
3254            }
3255        );
3256    }
3257
3258    #[test]
3259    fn modified() {
3260        const DATETIME: OffsetDateTime = datetime!(2022-05-01 12:13:14.567 +01:00);
3261        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
3262            .modified(DATETIME)
3263            .build()
3264            .unwrap();
3265
3266        assert_eq!(
3267            thing,
3268            Thing {
3269                context: TD_CONTEXT_11.into(),
3270                title: "MyLampThing".to_string(),
3271                modified: Some(DATETIME),
3272                ..Default::default()
3273            }
3274        );
3275    }
3276
3277    #[test]
3278    fn link_simple() {
3279        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
3280            .link("href1")
3281            .link("href2")
3282            .build()
3283            .unwrap();
3284
3285        assert_eq!(
3286            thing,
3287            Thing {
3288                context: TD_CONTEXT_11.into(),
3289                title: "MyLampThing".to_string(),
3290                links: Some(vec![
3291                    Link {
3292                        href: "href1".to_string(),
3293                        ty: Default::default(),
3294                        rel: Default::default(),
3295                        anchor: Default::default(),
3296                        sizes: Default::default(),
3297                        hreflang: Default::default(),
3298                    },
3299                    Link {
3300                        href: "href2".to_string(),
3301                        ty: Default::default(),
3302                        rel: Default::default(),
3303                        anchor: Default::default(),
3304                        sizes: Default::default(),
3305                        hreflang: Default::default(),
3306                    }
3307                ]),
3308                ..Default::default()
3309            }
3310        );
3311    }
3312
3313    #[test]
3314    fn link_with() {
3315        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
3316            .link_with(|link| {
3317                link.href("href1")
3318                    .ty("ty")
3319                    .rel("icon")
3320                    .anchor("anchor")
3321                    .sizes("10x20 30x50")
3322                    .hreflang("it")
3323                    .hreflang("en")
3324            })
3325            .link_with(|link| link.href("href2"))
3326            .build()
3327            .unwrap();
3328
3329        assert_eq!(
3330            thing,
3331            Thing {
3332                context: TD_CONTEXT_11.into(),
3333                title: "MyLampThing".to_string(),
3334                links: Some(vec![
3335                    Link {
3336                        href: "href1".to_string(),
3337                        ty: Some("ty".to_string()),
3338                        rel: Some("icon".to_string()),
3339                        anchor: Some("anchor".to_string()),
3340                        sizes: Some("10x20 30x50".to_string()),
3341                        hreflang: Some(vec!["it".parse().unwrap(), "en".parse().unwrap()]),
3342                    },
3343                    Link {
3344                        href: "href2".to_string(),
3345                        ty: Default::default(),
3346                        rel: Default::default(),
3347                        anchor: Default::default(),
3348                        sizes: Default::default(),
3349                        hreflang: Default::default(),
3350                    }
3351                ]),
3352                ..Default::default()
3353            }
3354        );
3355    }
3356
3357    #[test]
3358    fn invalid_link_sizes_without_type_icon() {
3359        let error = ThingBuilder::<Nil, _>::new("MyLampThing")
3360            .link_with(|link| link.href("href1").rel("other").sizes("10x20 30x50"))
3361            .build()
3362            .unwrap_err();
3363
3364        assert_eq!(error, Error::SizesWithRelNotIcon);
3365    }
3366
3367    #[test]
3368    fn link_with_invalid_hreflangs() {
3369        let error = ThingBuilder::<Nil, _>::new("MyLampThing")
3370            .link_with(|link| {
3371                link.href("href1")
3372                    .hreflang("it")
3373                    .hreflang("i18")
3374                    .hreflang("en")
3375            })
3376            .build()
3377            .unwrap_err();
3378
3379        assert_eq!(error, Error::InvalidLanguageTag("i18".to_string()));
3380    }
3381
3382    #[test]
3383    fn nosec_security() {
3384        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
3385            .security(|b| {
3386                b.no_sec()
3387                    .attype("ty1")
3388                    .attype("ty2")
3389                    .description("desc")
3390                    .descriptions(|ml| ml.add("en", "desc_en").add("it", "desc_it"))
3391                    .proxy("proxy")
3392                    .required()
3393            })
3394            .build()
3395            .unwrap();
3396
3397        assert_eq!(
3398            thing,
3399            Thing {
3400                context: TD_CONTEXT_11.into(),
3401                title: "MyLampThing".to_string(),
3402                security: vec!["nosec".to_string()],
3403                security_definitions: [(
3404                    "nosec".to_string(),
3405                    SecurityScheme {
3406                        attype: Some(vec!["ty1".to_string(), "ty2".to_string()]),
3407                        description: Some("desc".to_string()),
3408                        descriptions: Some(
3409                            [
3410                                ("en".parse().unwrap(), "desc_en".to_string()),
3411                                ("it".parse().unwrap(), "desc_it".to_string()),
3412                            ]
3413                            .into_iter()
3414                            .collect()
3415                        ),
3416                        proxy: Some("proxy".to_string()),
3417                        subtype: SecuritySchemeSubtype::Known(KnownSecuritySchemeSubtype::NoSec)
3418                    }
3419                )]
3420                .into_iter()
3421                .collect(),
3422                ..Default::default()
3423            }
3424        );
3425    }
3426
3427    #[test]
3428    fn auto_security() {
3429        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
3430            .security(|b| {
3431                b.auto()
3432                    .attype("ty1")
3433                    .attype("ty2")
3434                    .description("desc")
3435                    .descriptions(|ml| ml.add("en", "desc_en").add("it", "desc_it"))
3436                    .proxy("proxy")
3437                    .required()
3438            })
3439            .build()
3440            .unwrap();
3441
3442        assert_eq!(
3443            thing,
3444            Thing {
3445                context: TD_CONTEXT_11.into(),
3446                title: "MyLampThing".to_string(),
3447                security: vec!["auto".to_string()],
3448                security_definitions: [(
3449                    "auto".to_string(),
3450                    SecurityScheme {
3451                        attype: Some(vec!["ty1".to_string(), "ty2".to_string()]),
3452                        description: Some("desc".to_string()),
3453                        descriptions: Some(
3454                            [
3455                                ("en".parse().unwrap(), "desc_en".to_string()),
3456                                ("it".parse().unwrap(), "desc_it".to_string()),
3457                            ]
3458                            .into_iter()
3459                            .collect()
3460                        ),
3461                        proxy: Some("proxy".to_string()),
3462                        subtype: SecuritySchemeSubtype::Known(KnownSecuritySchemeSubtype::Auto)
3463                    }
3464                )]
3465                .into_iter()
3466                .collect(),
3467                ..Default::default()
3468            }
3469        );
3470    }
3471
3472    #[test]
3473    fn basic_security() {
3474        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
3475            .security(|b| {
3476                b.basic()
3477                    .name("name")
3478                    .location(SecurityAuthenticationLocation::Cookie)
3479                    .attype("ty1")
3480                    .attype("ty2")
3481                    .description("desc")
3482                    .descriptions(|ml| ml.add("en", "desc_en").add("it", "desc_it"))
3483                    .proxy("proxy")
3484                    .required()
3485            })
3486            .build()
3487            .unwrap();
3488
3489        assert_eq!(
3490            thing,
3491            Thing {
3492                context: TD_CONTEXT_11.into(),
3493                title: "MyLampThing".to_string(),
3494                security: vec!["basic".to_string()],
3495                security_definitions: [(
3496                    "basic".to_string(),
3497                    SecurityScheme {
3498                        attype: Some(vec!["ty1".to_string(), "ty2".to_string()]),
3499                        description: Some("desc".to_string()),
3500                        descriptions: Some(
3501                            [
3502                                ("en".parse().unwrap(), "desc_en".to_string()),
3503                                ("it".parse().unwrap(), "desc_it".to_string()),
3504                            ]
3505                            .into_iter()
3506                            .collect()
3507                        ),
3508                        proxy: Some("proxy".to_string()),
3509                        subtype: SecuritySchemeSubtype::Known(KnownSecuritySchemeSubtype::Basic(
3510                            BasicSecurityScheme {
3511                                location: SecurityAuthenticationLocation::Cookie,
3512                                name: Some("name".to_string())
3513                            }
3514                        ))
3515                    }
3516                )]
3517                .into_iter()
3518                .collect(),
3519                ..Default::default()
3520            }
3521        );
3522    }
3523
3524    #[test]
3525    fn digest_security() {
3526        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
3527            .security(|b| {
3528                b.digest()
3529                    .name("name")
3530                    .location(SecurityAuthenticationLocation::Cookie)
3531                    .qop(QualityOfProtection::AuthInt)
3532                    .attype("ty1")
3533                    .attype("ty2")
3534                    .description("desc")
3535                    .descriptions(|ml| ml.add("en", "desc_en").add("it", "desc_it"))
3536                    .proxy("proxy")
3537                    .required()
3538            })
3539            .build()
3540            .unwrap();
3541
3542        assert_eq!(
3543            thing,
3544            Thing {
3545                context: TD_CONTEXT_11.into(),
3546                title: "MyLampThing".to_string(),
3547                security: vec!["digest".to_string()],
3548                security_definitions: [(
3549                    "digest".to_string(),
3550                    SecurityScheme {
3551                        attype: Some(vec!["ty1".to_string(), "ty2".to_string()]),
3552                        description: Some("desc".to_string()),
3553                        descriptions: Some(
3554                            [
3555                                ("en".parse().unwrap(), "desc_en".to_string()),
3556                                ("it".parse().unwrap(), "desc_it".to_string()),
3557                            ]
3558                            .into_iter()
3559                            .collect()
3560                        ),
3561                        proxy: Some("proxy".to_string()),
3562                        subtype: SecuritySchemeSubtype::Known(KnownSecuritySchemeSubtype::Digest(
3563                            DigestSecurityScheme {
3564                                location: SecurityAuthenticationLocation::Cookie,
3565                                name: Some("name".to_string()),
3566                                qop: QualityOfProtection::AuthInt,
3567                            }
3568                        ))
3569                    }
3570                )]
3571                .into_iter()
3572                .collect(),
3573                ..Default::default()
3574            }
3575        );
3576    }
3577
3578    #[test]
3579    fn apikey_security() {
3580        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
3581            .security(|b| {
3582                b.apikey()
3583                    .name("name")
3584                    .location(SecurityAuthenticationLocation::Cookie)
3585                    .attype("ty1")
3586                    .attype("ty2")
3587                    .description("desc")
3588                    .descriptions(|ml| ml.add("en", "desc_en").add("it", "desc_it"))
3589                    .proxy("proxy")
3590                    .required()
3591            })
3592            .build()
3593            .unwrap();
3594
3595        assert_eq!(
3596            thing,
3597            Thing {
3598                context: TD_CONTEXT_11.into(),
3599                title: "MyLampThing".to_string(),
3600                security: vec!["apikey".to_string()],
3601                security_definitions: [(
3602                    "apikey".to_string(),
3603                    SecurityScheme {
3604                        attype: Some(vec!["ty1".to_string(), "ty2".to_string()]),
3605                        description: Some("desc".to_string()),
3606                        descriptions: Some(
3607                            [
3608                                ("en".parse().unwrap(), "desc_en".to_string()),
3609                                ("it".parse().unwrap(), "desc_it".to_string()),
3610                            ]
3611                            .into_iter()
3612                            .collect()
3613                        ),
3614                        proxy: Some("proxy".to_string()),
3615                        subtype: SecuritySchemeSubtype::Known(KnownSecuritySchemeSubtype::ApiKey(
3616                            ApiKeySecurityScheme {
3617                                location: SecurityAuthenticationLocation::Cookie,
3618                                name: Some("name".to_string()),
3619                            }
3620                        ))
3621                    }
3622                )]
3623                .into_iter()
3624                .collect(),
3625                ..Default::default()
3626            }
3627        );
3628    }
3629
3630    #[test]
3631    fn bearer_security() {
3632        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
3633            .security(|b| {
3634                b.bearer()
3635                    .name("name")
3636                    .location(SecurityAuthenticationLocation::Cookie)
3637                    .authorization("authorization")
3638                    .alg("alg")
3639                    .format("format".to_string())
3640                    .attype("ty1")
3641                    .attype("ty2")
3642                    .description("desc")
3643                    .descriptions(|ml| ml.add("en", "desc_en").add("it", "desc_it"))
3644                    .proxy("proxy")
3645                    .required()
3646            })
3647            .build()
3648            .unwrap();
3649
3650        assert_eq!(
3651            thing,
3652            Thing {
3653                context: TD_CONTEXT_11.into(),
3654                title: "MyLampThing".to_string(),
3655                security: vec!["bearer".to_string()],
3656                security_definitions: [(
3657                    "bearer".to_string(),
3658                    SecurityScheme {
3659                        attype: Some(vec!["ty1".to_string(), "ty2".to_string()]),
3660                        description: Some("desc".to_string()),
3661                        descriptions: Some(
3662                            [
3663                                ("en".parse().unwrap(), "desc_en".to_string()),
3664                                ("it".parse().unwrap(), "desc_it".to_string()),
3665                            ]
3666                            .into_iter()
3667                            .collect()
3668                        ),
3669                        proxy: Some("proxy".to_string()),
3670                        subtype: SecuritySchemeSubtype::Known(KnownSecuritySchemeSubtype::Bearer(
3671                            BearerSecurityScheme {
3672                                location: SecurityAuthenticationLocation::Cookie,
3673                                name: Some("name".to_string()),
3674                                authorization: Some("authorization".to_string()),
3675                                alg: Cow::Borrowed("alg"),
3676                                format: Cow::Borrowed("format"),
3677                            }
3678                        ))
3679                    }
3680                )]
3681                .into_iter()
3682                .collect(),
3683                ..Default::default()
3684            }
3685        );
3686    }
3687
3688    #[test]
3689    fn oauth2_security() {
3690        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
3691            .security(|b| {
3692                b.oauth2("flow")
3693                    .authorization("authorization")
3694                    .token("token")
3695                    .refresh("refresh")
3696                    .scope("scope1")
3697                    .scope("scope2")
3698                    .attype("ty1")
3699                    .attype("ty2")
3700                    .description("desc")
3701                    .descriptions(|ml| ml.add("en", "desc_en").add("it", "desc_it"))
3702                    .proxy("proxy")
3703                    .required()
3704            })
3705            .build()
3706            .unwrap();
3707
3708        assert_eq!(
3709            thing,
3710            Thing {
3711                context: TD_CONTEXT_11.into(),
3712                title: "MyLampThing".to_string(),
3713                security: vec!["oauth2".to_string()],
3714                security_definitions: [(
3715                    "oauth2".to_string(),
3716                    SecurityScheme {
3717                        attype: Some(vec!["ty1".to_string(), "ty2".to_string()]),
3718                        description: Some("desc".to_string()),
3719                        descriptions: Some(
3720                            [
3721                                ("en".parse().unwrap(), "desc_en".to_string()),
3722                                ("it".parse().unwrap(), "desc_it".to_string()),
3723                            ]
3724                            .into_iter()
3725                            .collect()
3726                        ),
3727                        proxy: Some("proxy".to_string()),
3728                        subtype: SecuritySchemeSubtype::Known(KnownSecuritySchemeSubtype::OAuth2(
3729                            OAuth2SecurityScheme {
3730                                authorization: Some("authorization".to_string()),
3731                                token: Some("token".to_string()),
3732                                refresh: Some("refresh".to_string()),
3733                                scopes: Some(vec!["scope1".to_string(), "scope2".to_string()]),
3734                                flow: "flow".to_string(),
3735                            }
3736                        ))
3737                    }
3738                )]
3739                .into_iter()
3740                .collect(),
3741                ..Default::default()
3742            }
3743        );
3744    }
3745
3746    #[test]
3747    fn custom_security() {
3748        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
3749            .security(|b| {
3750                b.custom("mysec")
3751                    .data(json! ({
3752                        "hello": ["world", "mondo"],
3753                        "test": 1,
3754                    }))
3755                    .attype("ty1")
3756                    .attype("ty2")
3757                    .description("desc")
3758                    .descriptions(|ml| ml.add("en", "desc_en").add("it", "desc_it"))
3759                    .proxy("proxy")
3760                    .required()
3761            })
3762            .build()
3763            .unwrap();
3764
3765        assert_eq!(
3766            thing,
3767            Thing {
3768                context: TD_CONTEXT_11.into(),
3769                title: "MyLampThing".to_string(),
3770                security: vec!["mysec".to_string()],
3771                security_definitions: [(
3772                    "mysec".to_string(),
3773                    SecurityScheme {
3774                        attype: Some(vec!["ty1".to_string(), "ty2".to_string()]),
3775                        description: Some("desc".to_string()),
3776                        descriptions: Some(
3777                            [
3778                                ("en".parse().unwrap(), "desc_en".to_string()),
3779                                ("it".parse().unwrap(), "desc_it".to_string()),
3780                            ]
3781                            .into_iter()
3782                            .collect()
3783                        ),
3784                        proxy: Some("proxy".to_string()),
3785                        subtype: SecuritySchemeSubtype::Unknown(UnknownSecuritySchemeSubtype {
3786                            scheme: "mysec".to_string(),
3787                            data: json!({
3788                                "hello": ["world", "mondo"],
3789                                "test": 1,
3790                            })
3791                        })
3792                    }
3793                )]
3794                .into_iter()
3795                .collect(),
3796                ..Default::default()
3797            }
3798        );
3799    }
3800
3801    #[test]
3802    fn named_security() {
3803        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
3804            .security(|b| b.no_sec().with_key("test_sec1").required())
3805            .security(|b| b.no_sec().with_key("test_sec2").required())
3806            .build()
3807            .unwrap();
3808
3809        assert_eq!(
3810            thing,
3811            Thing {
3812                context: TD_CONTEXT_11.into(),
3813                title: "MyLampThing".to_string(),
3814                security: vec!["test_sec1".to_string(), "test_sec2".to_string()],
3815                security_definitions: [
3816                    (
3817                        "test_sec1".to_string(),
3818                        SecurityScheme {
3819                            attype: Default::default(),
3820                            description: Default::default(),
3821                            descriptions: Default::default(),
3822                            proxy: Default::default(),
3823                            subtype: SecuritySchemeSubtype::Known(
3824                                KnownSecuritySchemeSubtype::NoSec
3825                            )
3826                        }
3827                    ),
3828                    (
3829                        "test_sec2".to_string(),
3830                        SecurityScheme {
3831                            attype: Default::default(),
3832                            description: Default::default(),
3833                            descriptions: Default::default(),
3834                            proxy: Default::default(),
3835                            subtype: SecuritySchemeSubtype::Known(
3836                                KnownSecuritySchemeSubtype::NoSec
3837                            )
3838                        }
3839                    )
3840                ]
3841                .into_iter()
3842                .collect(),
3843                ..Default::default()
3844            }
3845        );
3846    }
3847
3848    #[test]
3849    fn mixed_security() {
3850        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
3851            .security(|b| b.digest().with_key("sec1"))
3852            .security(|b| b.basic().with_key("sec2").required())
3853            .build()
3854            .unwrap();
3855
3856        assert_eq!(
3857            thing,
3858            Thing {
3859                context: TD_CONTEXT_11.into(),
3860                title: "MyLampThing".to_string(),
3861                security: vec!["sec2".to_string()],
3862                security_definitions: [
3863                    (
3864                        "sec1".to_string(),
3865                        SecurityScheme {
3866                            attype: Default::default(),
3867                            description: Default::default(),
3868                            descriptions: Default::default(),
3869                            proxy: Default::default(),
3870                            subtype: SecuritySchemeSubtype::Known(
3871                                KnownSecuritySchemeSubtype::Digest(DigestSecurityScheme::default())
3872                            )
3873                        }
3874                    ),
3875                    (
3876                        "sec2".to_string(),
3877                        SecurityScheme {
3878                            attype: Default::default(),
3879                            description: Default::default(),
3880                            descriptions: Default::default(),
3881                            proxy: Default::default(),
3882                            subtype: SecuritySchemeSubtype::Known(
3883                                KnownSecuritySchemeSubtype::Basic(BasicSecurityScheme::default())
3884                            )
3885                        }
3886                    ),
3887                ]
3888                .into_iter()
3889                .collect(),
3890                ..Default::default()
3891            }
3892        );
3893
3894        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
3895            .security(|b| b.digest())
3896            .security(|b| b.basic().required())
3897            .build()
3898            .unwrap();
3899
3900        assert_eq!(
3901            thing,
3902            Thing {
3903                context: TD_CONTEXT_11.into(),
3904                title: "MyLampThing".to_string(),
3905                security: vec!["basic".to_string()],
3906                security_definitions: [
3907                    (
3908                        "digest".to_string(),
3909                        SecurityScheme {
3910                            attype: Default::default(),
3911                            description: Default::default(),
3912                            descriptions: Default::default(),
3913                            proxy: Default::default(),
3914                            subtype: SecuritySchemeSubtype::Known(
3915                                KnownSecuritySchemeSubtype::Digest(DigestSecurityScheme::default())
3916                            )
3917                        }
3918                    ),
3919                    (
3920                        "basic".to_string(),
3921                        SecurityScheme {
3922                            attype: Default::default(),
3923                            description: Default::default(),
3924                            descriptions: Default::default(),
3925                            proxy: Default::default(),
3926                            subtype: SecuritySchemeSubtype::Known(
3927                                KnownSecuritySchemeSubtype::Basic(BasicSecurityScheme::default())
3928                            )
3929                        }
3930                    ),
3931                ]
3932                .into_iter()
3933                .collect(),
3934                ..Default::default()
3935            }
3936        );
3937    }
3938
3939    #[test]
3940    fn colliding_security_names() {
3941        let err = ThingBuilder::<Nil, _>::new("MyLampThing")
3942            .security(|b| b.basic())
3943            .security(|b| b.basic().required())
3944            .build()
3945            .unwrap_err();
3946
3947        assert_eq!(
3948            err,
3949            Error::DuplicatedSecurityDefinition("basic".to_string())
3950        );
3951    }
3952
3953    #[test]
3954    fn simple_form() {
3955        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
3956            .finish_extend()
3957            .form(|form| form.href("href").op(FormOperation::ReadAllProperties))
3958            .build()
3959            .unwrap();
3960
3961        assert_eq!(
3962            thing,
3963            Thing {
3964                context: TD_CONTEXT_11.into(),
3965                title: "MyLampThing".to_string(),
3966                forms: Some(vec![Form {
3967                    op: DefaultedFormOperations::Custom(vec![FormOperation::ReadAllProperties]),
3968                    href: "href".to_string(),
3969                    ..Default::default()
3970                }]),
3971                ..Default::default()
3972            }
3973        );
3974    }
3975
3976    #[test]
3977    fn simple_into_form() {
3978        struct FormBuilderEx<Other: ExtendableThing, Href, OtherForm>(
3979            FormBuilder<Other, Href, OtherForm>,
3980        );
3981        impl<Other: ExtendableThing, Href, OtherForm> From<FormBuilderEx<Other, Href, OtherForm>>
3982            for FormBuilder<Other, Href, OtherForm>
3983        {
3984            fn from(value: FormBuilderEx<Other, Href, OtherForm>) -> Self {
3985                value.0
3986            }
3987        }
3988
3989        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
3990            .finish_extend()
3991            .form(|form| FormBuilderEx(form.href("href").op(FormOperation::ReadAllProperties)))
3992            .build()
3993            .unwrap();
3994
3995        assert_eq!(
3996            thing,
3997            Thing {
3998                context: TD_CONTEXT_11.into(),
3999                title: "MyLampThing".to_string(),
4000                forms: Some(vec![Form {
4001                    op: DefaultedFormOperations::Custom(vec![FormOperation::ReadAllProperties]),
4002                    href: "href".to_string(),
4003                    ..Default::default()
4004                }]),
4005                ..Default::default()
4006            }
4007        );
4008    }
4009
4010    #[test]
4011    fn simple_form_with_uri_variables() {
4012        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
4013            .finish_extend()
4014            .form(|form| form.href("href/{foo}").op(FormOperation::ReadAllProperties))
4015            .uri_variable("foo", |v| v.finish_extend().integer())
4016            .build()
4017            .unwrap();
4018
4019        assert_eq!(
4020            thing,
4021            Thing {
4022                context: TD_CONTEXT_11.into(),
4023                title: "MyLampThing".to_string(),
4024                forms: Some(vec![Form {
4025                    op: DefaultedFormOperations::Custom(vec![FormOperation::ReadAllProperties]),
4026                    href: "href/{foo}".to_string(),
4027                    ..Default::default()
4028                }]),
4029                uri_variables: Some(
4030                    [(
4031                        "foo".to_string(),
4032                        DataSchema {
4033                            subtype: Some(DataSchemaSubtype::Integer(Default::default())),
4034                            ..Default::default()
4035                        }
4036                    )]
4037                    .into_iter()
4038                    .collect()
4039                ),
4040                ..Default::default()
4041            }
4042        );
4043    }
4044    #[test]
4045    fn complete_form() {
4046        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
4047            .finish_extend()
4048            .form(|form| {
4049                form.href("href")
4050                    .op(FormOperation::ReadAllProperties)
4051                    .content_type("text/plain")
4052                    .content_coding("coding")
4053                    .subprotocol("subprotocol")
4054                    .security("digest")
4055                    .security("basic")
4056                    .scope("scope1")
4057                    .scope("scope2")
4058                    .response_default_ext("application/json")
4059                    .additional_response(|b| b.content_type("application/xml").schema("schema1"))
4060                    .additional_response(|b| b.success().schema("schema2"))
4061                    .additional_response(|b| b)
4062            })
4063            .security(|b| b.digest())
4064            .security(|b| b.basic())
4065            .schema_definition("schema1", |b| b.finish_extend().bool())
4066            .schema_definition("schema2", |b| b.finish_extend().null())
4067            .build()
4068            .unwrap();
4069
4070        assert_eq!(
4071            thing,
4072            Thing {
4073                context: TD_CONTEXT_11.into(),
4074                title: "MyLampThing".to_string(),
4075                forms: Some(vec![Form {
4076                    op: DefaultedFormOperations::Custom(vec![FormOperation::ReadAllProperties]),
4077                    href: "href".to_string(),
4078                    content_type: Some("text/plain".into()),
4079                    content_coding: Some("coding".to_string()),
4080                    subprotocol: Some("subprotocol".to_string()),
4081                    security: Some(vec!["digest".to_string(), "basic".to_string()]),
4082                    scopes: Some(vec!["scope1".to_string(), "scope2".to_string()]),
4083                    response: Some(ExpectedResponse {
4084                        content_type: "application/json".to_string(),
4085                        other: Nil,
4086                    }),
4087                    additional_responses: Some(vec![
4088                        AdditionalExpectedResponse {
4089                            success: false,
4090                            content_type: Some("application/xml".to_string()),
4091                            schema: Some("schema1".to_string()),
4092                        },
4093                        AdditionalExpectedResponse {
4094                            success: true,
4095                            content_type: None,
4096                            schema: Some("schema2".to_string()),
4097                        },
4098                        AdditionalExpectedResponse::default(),
4099                    ]),
4100                    other: Nil,
4101                }]),
4102                schema_definitions: Some(
4103                    [
4104                        (
4105                            "schema1".to_string(),
4106                            DataSchema {
4107                                subtype: Some(DataSchemaSubtype::Boolean),
4108                                ..Default::default()
4109                            }
4110                        ),
4111                        (
4112                            "schema2".to_string(),
4113                            DataSchema {
4114                                subtype: Some(DataSchemaSubtype::Null),
4115                                ..Default::default()
4116                            }
4117                        ),
4118                    ]
4119                    .into_iter()
4120                    .collect()
4121                ),
4122                security_definitions: [
4123                    (
4124                        "digest".to_string(),
4125                        SecurityScheme {
4126                            attype: Default::default(),
4127                            description: Default::default(),
4128                            descriptions: Default::default(),
4129                            proxy: Default::default(),
4130                            subtype: SecuritySchemeSubtype::Known(
4131                                KnownSecuritySchemeSubtype::Digest(DigestSecurityScheme::default())
4132                            )
4133                        }
4134                    ),
4135                    (
4136                        "basic".to_string(),
4137                        SecurityScheme {
4138                            attype: Default::default(),
4139                            description: Default::default(),
4140                            descriptions: Default::default(),
4141                            proxy: Default::default(),
4142                            subtype: SecuritySchemeSubtype::Known(
4143                                KnownSecuritySchemeSubtype::Basic(BasicSecurityScheme::default())
4144                            )
4145                        }
4146                    ),
4147                ]
4148                .into_iter()
4149                .collect(),
4150                ..Default::default()
4151            }
4152        );
4153    }
4154
4155    #[test]
4156    fn form_with_multiple_ops() {
4157        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
4158            .finish_extend()
4159            .form(|form| {
4160                form.href("href")
4161                    .op(FormOperation::ReadAllProperties)
4162                    .op(FormOperation::ReadMultipleProperties)
4163            })
4164            .build()
4165            .unwrap();
4166
4167        assert_eq!(
4168            thing,
4169            Thing {
4170                context: TD_CONTEXT_11.into(),
4171                title: "MyLampThing".to_string(),
4172                forms: Some(vec![Form {
4173                    op: DefaultedFormOperations::Custom(vec![
4174                        FormOperation::ReadAllProperties,
4175                        FormOperation::ReadMultipleProperties
4176                    ]),
4177                    href: "href".to_string(),
4178                    ..Default::default()
4179                }]),
4180                ..Default::default()
4181            }
4182        );
4183    }
4184
4185    #[test]
4186    fn invalid_form_without_op() {
4187        let err = ThingBuilder::<Nil, _>::new("MyLampThing")
4188            .finish_extend()
4189            .form(|form| form.href("href"))
4190            .build()
4191            .unwrap_err();
4192
4193        assert_eq!(err, Error::MissingOpInForm);
4194    }
4195
4196    #[test]
4197    fn invalid_form_with_invalid_op() {
4198        let err = ThingBuilder::<Nil, _>::new("MyLampThing")
4199            .finish_extend()
4200            .form(|form| form.href("href").op(FormOperation::ReadProperty))
4201            .build()
4202            .unwrap_err();
4203
4204        assert_eq!(
4205            err,
4206            Error::InvalidOpInForm {
4207                context: FormContext::Thing,
4208                operation: FormOperation::ReadProperty
4209            }
4210        );
4211    }
4212
4213    #[test]
4214    fn invalid_form_with_missing_security() {
4215        let err = ThingBuilder::<Nil, _>::new("MyLampThing")
4216            .finish_extend()
4217            .form(|form| {
4218                form.href("href")
4219                    .op(FormOperation::ReadAllProperties)
4220                    .security("basic")
4221            })
4222            .build()
4223            .unwrap_err();
4224
4225        assert_eq!(err, Error::UndefinedSecurity("basic".to_string()));
4226    }
4227
4228    #[test]
4229    fn with_property_affordance() {
4230        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
4231            .finish_extend()
4232            .property("on", |b| {
4233                b.finish_extend_data_schema()
4234                    .bool()
4235                    .observable(true)
4236                    .title("title")
4237            })
4238            .property("prop", |b| b.finish_extend_data_schema().null())
4239            .build()
4240            .unwrap();
4241
4242        assert_eq!(
4243            thing,
4244            Thing {
4245                context: TD_CONTEXT_11.into(),
4246                title: "MyLampThing".to_string(),
4247                properties: Some(
4248                    [
4249                        (
4250                            "on".to_owned(),
4251                            PropertyAffordance {
4252                                interaction: InteractionAffordance {
4253                                    attype: None,
4254                                    title: Some("title".to_owned()),
4255                                    titles: None,
4256                                    description: None,
4257                                    descriptions: None,
4258                                    forms: vec![],
4259                                    uri_variables: None,
4260                                    other: Nil,
4261                                },
4262                                data_schema: DataSchema {
4263                                    attype: None,
4264                                    title: Some("title".to_owned()),
4265                                    titles: None,
4266                                    description: None,
4267                                    descriptions: None,
4268                                    constant: None,
4269                                    default: None,
4270                                    unit: None,
4271                                    one_of: None,
4272                                    enumeration: None,
4273                                    read_only: false,
4274                                    write_only: false,
4275                                    format: None,
4276                                    subtype: Some(DataSchemaSubtype::Boolean),
4277                                    other: Nil,
4278                                },
4279                                observable: Some(true),
4280                                other: Nil,
4281                            }
4282                        ),
4283                        (
4284                            "prop".to_owned(),
4285                            PropertyAffordance {
4286                                interaction: InteractionAffordance {
4287                                    attype: None,
4288                                    title: None,
4289                                    titles: None,
4290                                    description: None,
4291                                    descriptions: None,
4292                                    forms: vec![],
4293                                    uri_variables: None,
4294                                    other: Nil,
4295                                },
4296                                data_schema: DataSchema {
4297                                    attype: None,
4298                                    title: None,
4299                                    titles: None,
4300                                    description: None,
4301                                    descriptions: None,
4302                                    constant: None,
4303                                    default: None,
4304                                    unit: None,
4305                                    one_of: None,
4306                                    enumeration: None,
4307                                    read_only: false,
4308                                    write_only: false,
4309                                    format: None,
4310                                    subtype: Some(DataSchemaSubtype::Null),
4311                                    other: Nil,
4312                                },
4313                                observable: None,
4314                                other: Nil,
4315                            }
4316                        ),
4317                    ]
4318                    .into_iter()
4319                    .collect()
4320                ),
4321                ..Default::default()
4322            }
4323        );
4324    }
4325
4326    #[test]
4327    fn with_action_affordance() {
4328        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
4329            .finish_extend()
4330            .action("fade", |b| b)
4331            .action("action", |b| {
4332                b.title("title")
4333                    .idempotent()
4334                    .input(|b| b.finish_extend().null())
4335            })
4336            .build()
4337            .unwrap();
4338
4339        assert_eq!(
4340            thing,
4341            Thing {
4342                context: TD_CONTEXT_11.into(),
4343                title: "MyLampThing".to_string(),
4344                actions: Some(
4345                    [
4346                        (
4347                            "fade".to_owned(),
4348                            ActionAffordance {
4349                                interaction: InteractionAffordance {
4350                                    attype: None,
4351                                    title: None,
4352                                    titles: None,
4353                                    description: None,
4354                                    descriptions: None,
4355                                    forms: vec![],
4356                                    uri_variables: None,
4357                                    other: Nil,
4358                                },
4359                                input: None,
4360                                output: None,
4361                                safe: false,
4362                                idempotent: false,
4363                                synchronous: None,
4364                                other: Nil,
4365                            }
4366                        ),
4367                        (
4368                            "action".to_owned(),
4369                            ActionAffordance {
4370                                interaction: InteractionAffordance {
4371                                    attype: None,
4372                                    title: Some("title".to_owned()),
4373                                    titles: None,
4374                                    description: None,
4375                                    descriptions: None,
4376                                    forms: vec![],
4377                                    uri_variables: None,
4378                                    other: Nil,
4379                                },
4380                                input: Some(DataSchema {
4381                                    attype: None,
4382                                    title: None,
4383                                    titles: None,
4384                                    description: None,
4385                                    descriptions: None,
4386                                    constant: None,
4387                                    default: None,
4388                                    unit: None,
4389                                    one_of: None,
4390                                    enumeration: None,
4391                                    read_only: false,
4392                                    write_only: false,
4393                                    format: None,
4394                                    subtype: Some(DataSchemaSubtype::Null),
4395                                    other: Nil,
4396                                }),
4397                                output: None,
4398                                safe: false,
4399                                idempotent: true,
4400                                synchronous: None,
4401                                other: Nil,
4402                            }
4403                        ),
4404                    ]
4405                    .into_iter()
4406                    .collect()
4407                ),
4408                ..Default::default()
4409            }
4410        );
4411    }
4412
4413    #[test]
4414    fn with_event_affordance() {
4415        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
4416            .finish_extend()
4417            .event("overheat", |b| b)
4418            .event("event", |b| {
4419                b.title("title").cancellation(|b| b.finish_extend().null())
4420            })
4421            .build()
4422            .unwrap();
4423
4424        assert_eq!(
4425            thing,
4426            Thing {
4427                context: TD_CONTEXT_11.into(),
4428                title: "MyLampThing".to_string(),
4429                events: Some(
4430                    [
4431                        (
4432                            "overheat".to_owned(),
4433                            EventAffordance {
4434                                interaction: InteractionAffordance {
4435                                    attype: None,
4436                                    title: None,
4437                                    titles: None,
4438                                    description: None,
4439                                    descriptions: None,
4440                                    forms: vec![],
4441                                    uri_variables: None,
4442                                    other: Nil,
4443                                },
4444                                subscription: None,
4445                                data: None,
4446                                cancellation: None,
4447                                data_response: None,
4448                                other: Nil,
4449                            }
4450                        ),
4451                        (
4452                            "event".to_owned(),
4453                            EventAffordance {
4454                                interaction: InteractionAffordance {
4455                                    attype: None,
4456                                    title: Some("title".to_owned()),
4457                                    titles: None,
4458                                    description: None,
4459                                    descriptions: None,
4460                                    forms: vec![],
4461                                    uri_variables: None,
4462                                    other: Nil,
4463                                },
4464                                subscription: None,
4465                                data: None,
4466                                cancellation: Some(DataSchema {
4467                                    attype: None,
4468                                    title: None,
4469                                    titles: None,
4470                                    description: None,
4471                                    descriptions: None,
4472                                    constant: None,
4473                                    default: None,
4474                                    unit: None,
4475                                    one_of: None,
4476                                    enumeration: None,
4477                                    read_only: false,
4478                                    write_only: false,
4479                                    format: None,
4480                                    subtype: Some(DataSchemaSubtype::Null),
4481                                    other: Nil,
4482                                }),
4483                                data_response: None,
4484                                other: Nil,
4485                            }
4486                        ),
4487                    ]
4488                    .into_iter()
4489                    .collect()
4490                ),
4491                ..Default::default()
4492            }
4493        );
4494    }
4495
4496    #[test]
4497    fn valid_affordance_security() {
4498        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
4499            .finish_extend()
4500            .property("on", |b| {
4501                b.finish_extend_data_schema()
4502                    .bool()
4503                    .form(|b| b.security("basic").href("href"))
4504            })
4505            .security(|b| b.basic())
4506            .build()
4507            .unwrap();
4508
4509        assert_eq!(
4510            thing,
4511            Thing {
4512                context: TD_CONTEXT_11.into(),
4513                title: "MyLampThing".to_string(),
4514                properties: Some(
4515                    [(
4516                        "on".to_owned(),
4517                        PropertyAffordance {
4518                            interaction: InteractionAffordance {
4519                                forms: vec![Form {
4520                                    op: DefaultedFormOperations::Default,
4521                                    href: "href".to_owned(),
4522                                    security: Some(vec!["basic".to_owned()]),
4523                                    ..Default::default()
4524                                }],
4525                                ..Default::default()
4526                            },
4527                            data_schema: DataSchema {
4528                                subtype: Some(DataSchemaSubtype::Boolean),
4529                                ..Default::default()
4530                            },
4531                            ..Default::default()
4532                        }
4533                    ),]
4534                    .into_iter()
4535                    .collect()
4536                ),
4537                security_definitions: [(
4538                    "basic".to_owned(),
4539                    SecurityScheme {
4540                        subtype: SecuritySchemeSubtype::Known(KnownSecuritySchemeSubtype::Basic(
4541                            BasicSecurityScheme {
4542                                location: SecurityAuthenticationLocation::Header,
4543                                name: None,
4544                            }
4545                        )),
4546                        ..Default::default()
4547                    }
4548                )]
4549                .into_iter()
4550                .collect(),
4551                ..Default::default()
4552            }
4553        );
4554    }
4555
4556    #[test]
4557    fn invalid_affordance_security() {
4558        let error = ThingBuilder::<Nil, _>::new("MyLampThing")
4559            .finish_extend()
4560            .property("on", |b| {
4561                b.finish_extend_data_schema()
4562                    .bool()
4563                    .form(|b| b.security("oauth2").href("href"))
4564            })
4565            .security(|b| b.basic())
4566            .build()
4567            .unwrap_err();
4568
4569        assert_eq!(error, Error::UndefinedSecurity("oauth2".to_owned()));
4570    }
4571
4572    #[test]
4573    fn profile() {
4574        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
4575            .profile("profile")
4576            .build()
4577            .unwrap();
4578
4579        assert_eq!(
4580            thing,
4581            Thing {
4582                context: TD_CONTEXT_11.into(),
4583                title: "MyLampThing".to_string(),
4584                profile: Some(vec!["profile".to_string()]),
4585                ..Default::default()
4586            }
4587        );
4588
4589        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
4590            .profile("profile1")
4591            .profile("profile2")
4592            .build()
4593            .unwrap();
4594
4595        assert_eq!(
4596            thing,
4597            Thing {
4598                context: TD_CONTEXT_11.into(),
4599                title: "MyLampThing".to_string(),
4600                profile: Some(vec!["profile1".to_string(), "profile2".to_string()]),
4601                ..Default::default()
4602            }
4603        );
4604    }
4605
4606    #[test]
4607    fn schema_definitions() {
4608        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
4609            .finish_extend()
4610            .schema_definition("schema1", |b| b.finish_extend().null())
4611            .schema_definition("schema2", |b| b.finish_extend().number().minimum(5.))
4612            .build()
4613            .unwrap();
4614
4615        assert_eq!(
4616            thing,
4617            Thing {
4618                context: TD_CONTEXT_11.into(),
4619                title: "MyLampThing".to_string(),
4620                schema_definitions: Some(
4621                    [
4622                        (
4623                            "schema1".to_string(),
4624                            DataSchema {
4625                                subtype: Some(DataSchemaSubtype::Null),
4626                                ..Default::default()
4627                            },
4628                        ),
4629                        (
4630                            "schema2".to_string(),
4631                            DataSchema {
4632                                subtype: Some(DataSchemaSubtype::Number(NumberSchema {
4633                                    minimum: Some(Minimum::Inclusive(5.)),
4634                                    ..Default::default()
4635                                })),
4636                                ..Default::default()
4637                            },
4638                        ),
4639                    ]
4640                    .into_iter()
4641                    .collect()
4642                ),
4643                ..Default::default()
4644            }
4645        );
4646    }
4647
4648    #[test]
4649    fn extend_thing_with_form_builder() {
4650        #[derive(Debug, Default, PartialEq, Serialize, Deserialize)]
4651        struct ThingA {}
4652
4653        #[derive(Debug, Default, PartialEq, Serialize, Deserialize)]
4654        struct ThingB {}
4655
4656        #[derive(Debug, Serialize, PartialEq, Deserialize)]
4657        struct FormExtA {
4658            a: String,
4659        }
4660
4661        #[derive(Debug, Serialize, PartialEq, Deserialize)]
4662        struct B(i32);
4663
4664        #[derive(Debug, Serialize, PartialEq, Deserialize)]
4665        struct FormExtB {
4666            b: B,
4667        }
4668
4669        impl ExtendableThing for ThingA {
4670            type InteractionAffordance = ();
4671            type PropertyAffordance = ();
4672            type ActionAffordance = ();
4673            type EventAffordance = ();
4674            type Form = FormExtA;
4675            type ExpectedResponse = ();
4676            type DataSchema = ();
4677            type ObjectSchema = ();
4678            type ArraySchema = ();
4679        }
4680
4681        impl ExtendableThing for ThingB {
4682            type InteractionAffordance = ();
4683            type PropertyAffordance = ();
4684            type ActionAffordance = ();
4685            type EventAffordance = ();
4686            type Form = FormExtB;
4687            type ExpectedResponse = ();
4688            type DataSchema = ();
4689            type ObjectSchema = ();
4690            type ArraySchema = ();
4691        }
4692
4693        let thing: Thing<Cons<ThingB, Cons<ThingA, Nil>>> =
4694            ThingBuilder::<Cons<ThingB, Cons<ThingA, Nil>>, _>::new("MyLampThing")
4695                .finish_extend()
4696                .form(|form| {
4697                    form.ext_with(|| FormExtA {
4698                        a: String::from("test"),
4699                    })
4700                    .href("href")
4701                    .ext(FormExtB { b: B(42) })
4702                    .op(FormOperation::ReadAllProperties)
4703                })
4704                .build()
4705                .unwrap();
4706
4707        assert_eq!(
4708            thing,
4709            Thing {
4710                context: TD_CONTEXT_11.into(),
4711                title: "MyLampThing".to_string(),
4712                forms: Some(vec![Form {
4713                    op: DefaultedFormOperations::Custom(vec![FormOperation::ReadAllProperties]),
4714                    href: "href".to_string(),
4715                    other: Nil::cons(FormExtA {
4716                        a: "test".to_string()
4717                    })
4718                    .cons(FormExtB { b: B(42) }),
4719                    content_type: Default::default(),
4720                    content_coding: Default::default(),
4721                    subprotocol: Default::default(),
4722                    security: Default::default(),
4723                    scopes: Default::default(),
4724                    response: Default::default(),
4725                    additional_responses: Default::default(),
4726                }]),
4727                ..Default::default()
4728            }
4729        );
4730    }
4731
4732    #[test]
4733    fn extend_form_builder() {
4734        #[derive(Debug, PartialEq, Serialize, Deserialize)]
4735        struct ThingA {}
4736
4737        #[derive(Debug, PartialEq, Serialize, Deserialize)]
4738        struct ThingB {}
4739
4740        #[derive(Debug, PartialEq, Serialize, Deserialize)]
4741        struct A(String);
4742
4743        #[derive(Debug, PartialEq, Serialize, Deserialize)]
4744        struct FormExtA {
4745            a: A,
4746        }
4747
4748        #[derive(Debug, PartialEq, Serialize, Deserialize)]
4749        struct ExpectedResponseExtA {
4750            b: A,
4751        }
4752
4753        #[derive(Debug, PartialEq, Serialize, Deserialize)]
4754        struct B(i32);
4755
4756        #[derive(Debug, PartialEq, Serialize, Deserialize)]
4757        struct FormExtB {
4758            c: B,
4759        }
4760
4761        #[derive(Debug, PartialEq, Serialize, Deserialize)]
4762        struct ExpectedResponseExtB {
4763            d: B,
4764        }
4765
4766        impl ExtendableThing for ThingA {
4767            type InteractionAffordance = ();
4768            type PropertyAffordance = ();
4769            type ActionAffordance = ();
4770            type EventAffordance = ();
4771            type Form = FormExtA;
4772            type ExpectedResponse = ExpectedResponseExtA;
4773            type DataSchema = ();
4774            type ObjectSchema = ();
4775            type ArraySchema = ();
4776        }
4777
4778        impl ExtendableThing for ThingB {
4779            type InteractionAffordance = ();
4780            type PropertyAffordance = ();
4781            type ActionAffordance = ();
4782            type EventAffordance = ();
4783            type Form = FormExtB;
4784            type ExpectedResponse = ExpectedResponseExtB;
4785            type DataSchema = ();
4786            type ObjectSchema = ();
4787            type ArraySchema = ();
4788        }
4789
4790        let builder = FormBuilder::<Cons<ThingB, Cons<ThingA, Nil>>, _, _>::new()
4791            .href("href")
4792            .ext(FormExtA {
4793                a: A("a".to_string()),
4794            })
4795            .op(FormOperation::ReadProperty)
4796            .ext_with(|| FormExtB { c: B(1) })
4797            .response("application/json", |b| {
4798                b.ext(ExpectedResponseExtA {
4799                    b: A("b".to_string()),
4800                })
4801                .ext_with(|| ExpectedResponseExtB { d: B(2) })
4802            });
4803
4804        let form: Form<Cons<ThingB, Cons<ThingA, Nil>>> = builder.into();
4805        assert_eq!(
4806            form,
4807            Form {
4808                op: DefaultedFormOperations::Custom(vec![FormOperation::ReadProperty]),
4809                href: "href".to_string(),
4810                other: Nil::cons(FormExtA {
4811                    a: A("a".to_string())
4812                })
4813                .cons(FormExtB { c: B(1) }),
4814                response: Some(ExpectedResponse {
4815                    content_type: "application/json".to_string(),
4816                    other: Nil::cons(ExpectedResponseExtA {
4817                        b: A("b".to_string())
4818                    })
4819                    .cons(ExpectedResponseExtB { d: B(2) })
4820                }),
4821                content_type: Default::default(),
4822                content_coding: Default::default(),
4823                subprotocol: Default::default(),
4824                security: Default::default(),
4825                scopes: Default::default(),
4826                additional_responses: Default::default(),
4827            },
4828        );
4829    }
4830
4831    #[test]
4832    fn complete_extension() {
4833        #[derive(Debug, PartialEq, Serialize, Deserialize)]
4834        struct ThingA {
4835            a: u8,
4836            b: i32,
4837        }
4838
4839        #[derive(Debug, PartialEq, Serialize, Deserialize)]
4840        struct ThingB {}
4841
4842        #[derive(Debug, PartialEq, Serialize, Deserialize)]
4843        struct ThingC {
4844            c: u16,
4845        }
4846
4847        #[derive(Debug, PartialEq, Serialize, Deserialize)]
4848        struct InteractionAffordanceExtA {
4849            d: i16,
4850        }
4851
4852        #[derive(Debug, PartialEq, Serialize, Deserialize)]
4853        struct ActionAffordanceExtA {
4854            e: u64,
4855        }
4856
4857        #[derive(Debug, PartialEq, Serialize, Deserialize)]
4858        struct EventAffordanceExtA {
4859            f: u32,
4860        }
4861
4862        #[derive(Debug, PartialEq, Serialize, Deserialize)]
4863        struct ExpectedResponseExtA {
4864            g: i64,
4865        }
4866
4867        #[derive(Debug, PartialEq, Serialize, Deserialize)]
4868        struct DataSchemaExtA {
4869            h: isize,
4870        }
4871
4872        #[derive(Debug, PartialEq, Serialize, Deserialize)]
4873        struct ObjectSchemaExtA {
4874            i: usize,
4875        }
4876
4877        #[derive(Debug, PartialEq, Serialize, Deserialize)]
4878        struct InteractionAffordanceExtB {
4879            j: f32,
4880        }
4881
4882        #[derive(Debug, PartialEq, Serialize, Deserialize)]
4883        struct PropertyAffordanceExtB {
4884            k: f64,
4885        }
4886
4887        #[derive(Debug, PartialEq, Serialize, Deserialize)]
4888        struct EventAffordanceExtB {
4889            l: i8,
4890        }
4891
4892        #[derive(Debug, PartialEq, Serialize, Deserialize)]
4893        struct FormExtB {
4894            m: u8,
4895        }
4896
4897        #[derive(Debug, PartialEq, Serialize, Deserialize)]
4898        struct ExpectedResponseExtB {
4899            n: i16,
4900        }
4901
4902        #[derive(Debug, PartialEq, Serialize, Deserialize)]
4903        struct ObjectSchemaExtB {
4904            o: u16,
4905        }
4906
4907        #[derive(Debug, PartialEq, Serialize, Deserialize)]
4908        struct InteractionAffordanceExtC {
4909            p: u64,
4910        }
4911
4912        #[derive(Debug, PartialEq, Serialize, Deserialize)]
4913        struct PropertyAffordanceExtC {
4914            q: i8,
4915        }
4916
4917        #[derive(Debug, PartialEq, Serialize, Deserialize)]
4918        struct ActionAffordanceExtC {
4919            r: i32,
4920        }
4921
4922        #[derive(Debug, PartialEq, Serialize, Deserialize)]
4923        struct ExpectedResponseExtC {
4924            s: u8,
4925        }
4926
4927        #[derive(Debug, PartialEq, Serialize, Deserialize)]
4928        struct DataSchemaExtC {
4929            t: u32,
4930        }
4931
4932        #[derive(Debug, PartialEq, Serialize, Deserialize)]
4933        struct ObjectSchemaExtC {
4934            u: i32,
4935        }
4936
4937        impl ExtendableThing for ThingA {
4938            type InteractionAffordance = InteractionAffordanceExtA;
4939            type PropertyAffordance = ();
4940            type ActionAffordance = ActionAffordanceExtA;
4941            type EventAffordance = EventAffordanceExtA;
4942            type Form = ();
4943            type ExpectedResponse = ExpectedResponseExtA;
4944            type DataSchema = DataSchemaExtA;
4945            type ObjectSchema = ObjectSchemaExtA;
4946            type ArraySchema = ();
4947        }
4948
4949        impl ExtendableThing for ThingB {
4950            type InteractionAffordance = InteractionAffordanceExtB;
4951            type PropertyAffordance = PropertyAffordanceExtB;
4952            type ActionAffordance = ();
4953            type EventAffordance = EventAffordanceExtB;
4954            type Form = FormExtB;
4955            type ExpectedResponse = ExpectedResponseExtB;
4956            type DataSchema = ();
4957            type ObjectSchema = ObjectSchemaExtB;
4958            type ArraySchema = ();
4959        }
4960
4961        impl ExtendableThing for ThingC {
4962            type InteractionAffordance = InteractionAffordanceExtC;
4963            type PropertyAffordance = PropertyAffordanceExtC;
4964            type ActionAffordance = ActionAffordanceExtC;
4965            type EventAffordance = ();
4966            type Form = ();
4967            type ExpectedResponse = ExpectedResponseExtC;
4968            type DataSchema = DataSchemaExtC;
4969            type ObjectSchema = ObjectSchemaExtC;
4970            type ArraySchema = ();
4971        }
4972
4973        let thing = Thing::builder("thing title")
4974            .ext(ThingA { a: 1, b: 2 })
4975            .id("id")
4976            .ext(ThingB {})
4977            .ext_with(|| ThingC { c: 3 })
4978            .finish_extend()
4979            .description("description")
4980            .uri_variable("uri_variable", |b| {
4981                b.ext(DataSchemaExtA { h: 4 })
4982                    .ext(())
4983                    .ext(DataSchemaExtC { t: 5 })
4984                    .finish_extend()
4985                    .string()
4986            })
4987            .property("property", |b| {
4988                b.ext_interaction(InteractionAffordanceExtA { d: 6 })
4989                    .ext(())
4990                    .ext_data_schema(DataSchemaExtA { h: 7 })
4991                    .ext_data_schema(())
4992                    .ext_data_schema(DataSchemaExtC { t: 8 })
4993                    .finish_extend_data_schema()
4994                    .ext_interaction(InteractionAffordanceExtB { j: 9. })
4995                    .ext_interaction(InteractionAffordanceExtC { p: 10 })
4996                    .object_ext(|b| {
4997                        b.ext(ObjectSchemaExtA { i: 11 })
4998                            .ext(ObjectSchemaExtB { o: 12 })
4999                            .ext(ObjectSchemaExtC { u: 13 })
5000                    })
5001                    .ext(PropertyAffordanceExtB { k: 14. })
5002                    .ext(PropertyAffordanceExtC { q: 15 })
5003                    .form(|b| {
5004                        b.response("application/json", |b| {
5005                            b.ext(ExpectedResponseExtA { g: 16 })
5006                                .ext(ExpectedResponseExtB { n: 17 })
5007                                .ext(ExpectedResponseExtC { s: 18 })
5008                        })
5009                        .ext(())
5010                        .ext(FormExtB { m: 19 })
5011                        .ext(())
5012                        .href("href1")
5013                        .additional_response(|b| {
5014                            b.success().content_type("application/xml").schema("schema")
5015                        })
5016                    })
5017            })
5018            .action("action", |b| {
5019                b.ext(ActionAffordanceExtA { e: 20 })
5020                    .ext(())
5021                    .ext(ActionAffordanceExtC { r: 21 })
5022                    .ext_interaction(InteractionAffordanceExtA { d: 22 })
5023                    .ext_interaction(InteractionAffordanceExtB { j: 23. })
5024                    .ext_interaction(InteractionAffordanceExtC { p: 24 })
5025                    .input(|b| {
5026                        b.ext(DataSchemaExtA { h: 25 })
5027                            .ext(())
5028                            .ext(DataSchemaExtC { t: 26 })
5029                            .finish_extend()
5030                            .number()
5031                            .minimum(0.)
5032                            .maximum(5.)
5033                            .title("input")
5034                    })
5035                    .uri_variable("y", |b| {
5036                        b.ext(DataSchemaExtA { h: 27 })
5037                            .ext(())
5038                            .ext(DataSchemaExtC { t: 28 })
5039                            .finish_extend()
5040                            .string()
5041                    })
5042                    .title("action")
5043            })
5044            .event("event", |b| {
5045                b.ext(EventAffordanceExtA { f: 29 })
5046                    .ext(EventAffordanceExtB { l: 30 })
5047                    .ext(())
5048                    .ext_interaction(InteractionAffordanceExtA { d: 31 })
5049                    .ext_interaction(InteractionAffordanceExtB { j: 32. })
5050                    .ext_interaction(InteractionAffordanceExtC { p: 33 })
5051                    .data(|b| {
5052                        b.ext(DataSchemaExtA { h: 34 })
5053                            .ext(())
5054                            .ext(DataSchemaExtC { t: 35 })
5055                            .finish_extend()
5056                            .bool()
5057                    })
5058            })
5059            .form(|b| {
5060                b.ext(())
5061                    .ext(FormExtB { m: 36 })
5062                    .ext(())
5063                    .href("href2")
5064                    .response("test", |b| {
5065                        b.ext(ExpectedResponseExtA { g: 37 })
5066                            .ext(ExpectedResponseExtB { n: 38 })
5067                            .ext(ExpectedResponseExtC { s: 39 })
5068                    })
5069                    .op(FormOperation::ReadAllProperties)
5070            })
5071            .schema_definition("schema", |b| {
5072                b.ext(DataSchemaExtA { h: 40 })
5073                    .ext(())
5074                    .ext(DataSchemaExtC { t: 41 })
5075                    .finish_extend()
5076                    .null()
5077            })
5078            .build()
5079            .unwrap();
5080
5081        assert_eq!(
5082            thing,
5083            Thing {
5084                context: TD_CONTEXT_11.into(),
5085                title: "thing title".to_string(),
5086                other: Nil::cons(ThingA { a: 1, b: 2 })
5087                    .cons(ThingB {})
5088                    .cons(ThingC { c: 3 }),
5089                id: Some("id".to_string()),
5090                description: Some("description".to_string()),
5091                uri_variables: Some(
5092                    [(
5093                        "uri_variable".to_string(),
5094                        DataSchema {
5095                            subtype: Some(DataSchemaSubtype::String(StringSchema::default())),
5096                            other: Nil::cons(DataSchemaExtA { h: 4 })
5097                                .cons(())
5098                                .cons(DataSchemaExtC { t: 5 }),
5099                            attype: Default::default(),
5100                            title: Default::default(),
5101                            titles: Default::default(),
5102                            description: Default::default(),
5103                            descriptions: Default::default(),
5104                            constant: Default::default(),
5105                            default: Default::default(),
5106                            unit: Default::default(),
5107                            one_of: Default::default(),
5108                            enumeration: Default::default(),
5109                            read_only: Default::default(),
5110                            write_only: Default::default(),
5111                            format: Default::default(),
5112                        }
5113                    )]
5114                    .into_iter()
5115                    .collect()
5116                ),
5117                properties: Some(
5118                    [(
5119                        "property".to_string(),
5120                        PropertyAffordance {
5121                            interaction: InteractionAffordance {
5122                                other: Nil::cons(InteractionAffordanceExtA { d: 6 })
5123                                    .cons(InteractionAffordanceExtB { j: 9. })
5124                                    .cons(InteractionAffordanceExtC { p: 10 }),
5125                                attype: Default::default(),
5126                                title: Default::default(),
5127                                titles: Default::default(),
5128                                description: Default::default(),
5129                                descriptions: Default::default(),
5130                                forms: vec![Form {
5131                                    href: "href1".to_string(),
5132                                    response: Some(ExpectedResponse {
5133                                        content_type: "application/json".to_string(),
5134                                        other: Nil::cons(ExpectedResponseExtA { g: 16 })
5135                                            .cons(ExpectedResponseExtB { n: 17 })
5136                                            .cons(ExpectedResponseExtC { s: 18 })
5137                                    }),
5138                                    additional_responses: Some(vec![AdditionalExpectedResponse {
5139                                        success: true,
5140                                        content_type: Some("application/xml".to_string()),
5141                                        schema: Some("schema".to_string()),
5142                                    }]),
5143                                    other: Nil::cons(()).cons(FormExtB { m: 19 }).cons(()),
5144                                    op: Default::default(),
5145                                    content_type: Default::default(),
5146                                    content_coding: Default::default(),
5147                                    subprotocol: Default::default(),
5148                                    security: Default::default(),
5149                                    scopes: Default::default(),
5150                                }],
5151                                uri_variables: Default::default(),
5152                            },
5153                            data_schema: DataSchema {
5154                                subtype: Some(DataSchemaSubtype::Object(ObjectSchema {
5155                                    other: Nil::cons(ObjectSchemaExtA { i: 11 })
5156                                        .cons(ObjectSchemaExtB { o: 12 })
5157                                        .cons(ObjectSchemaExtC { u: 13 }),
5158                                    properties: Default::default(),
5159                                    required: Default::default(),
5160                                })),
5161                                other: Nil::cons(DataSchemaExtA { h: 7 })
5162                                    .cons(())
5163                                    .cons(DataSchemaExtC { t: 8 }),
5164                                attype: Default::default(),
5165                                title: Default::default(),
5166                                titles: Default::default(),
5167                                description: Default::default(),
5168                                descriptions: Default::default(),
5169                                constant: Default::default(),
5170                                default: Default::default(),
5171                                unit: Default::default(),
5172                                one_of: Default::default(),
5173                                enumeration: Default::default(),
5174                                read_only: Default::default(),
5175                                write_only: Default::default(),
5176                                format: Default::default(),
5177                            },
5178                            other: Nil::cons(())
5179                                .cons(PropertyAffordanceExtB { k: 14. })
5180                                .cons(PropertyAffordanceExtC { q: 15 }),
5181                            observable: Default::default(),
5182                        }
5183                    )]
5184                    .into_iter()
5185                    .collect()
5186                ),
5187                actions: Some(
5188                    [(
5189                        "action".to_string(),
5190                        ActionAffordance {
5191                            interaction: InteractionAffordance {
5192                                title: Some("action".to_string()),
5193                                uri_variables: Some(
5194                                    [(
5195                                        "y".to_string(),
5196                                        DataSchema {
5197                                            subtype: Some(DataSchemaSubtype::String(
5198                                                StringSchema::default()
5199                                            )),
5200                                            other: Nil::cons(DataSchemaExtA { h: 27 })
5201                                                .cons(())
5202                                                .cons(DataSchemaExtC { t: 28 }),
5203                                            attype: Default::default(),
5204                                            title: Default::default(),
5205                                            titles: Default::default(),
5206                                            description: Default::default(),
5207                                            descriptions: Default::default(),
5208                                            constant: Default::default(),
5209                                            default: Default::default(),
5210                                            unit: Default::default(),
5211                                            one_of: Default::default(),
5212                                            enumeration: Default::default(),
5213                                            read_only: Default::default(),
5214                                            write_only: Default::default(),
5215                                            format: Default::default(),
5216                                        }
5217                                    )]
5218                                    .into_iter()
5219                                    .collect()
5220                                ),
5221                                other: Nil::cons(InteractionAffordanceExtA { d: 22 })
5222                                    .cons(InteractionAffordanceExtB { j: 23. })
5223                                    .cons(InteractionAffordanceExtC { p: 24 }),
5224                                attype: Default::default(),
5225                                titles: Default::default(),
5226                                description: Default::default(),
5227                                descriptions: Default::default(),
5228                                forms: Default::default(),
5229                            },
5230                            input: Some(DataSchema {
5231                                title: Some("input".to_string()),
5232                                subtype: Some(DataSchemaSubtype::Number(NumberSchema {
5233                                    minimum: Some(Minimum::Inclusive(0.)),
5234                                    maximum: Some(Maximum::Inclusive(5.)),
5235                                    ..Default::default()
5236                                })),
5237                                other: Nil::cons(DataSchemaExtA { h: 25 })
5238                                    .cons(())
5239                                    .cons(DataSchemaExtC { t: 26 }),
5240                                attype: Default::default(),
5241                                titles: Default::default(),
5242                                description: Default::default(),
5243                                descriptions: Default::default(),
5244                                constant: Default::default(),
5245                                default: Default::default(),
5246                                unit: Default::default(),
5247                                one_of: Default::default(),
5248                                enumeration: Default::default(),
5249                                read_only: Default::default(),
5250                                write_only: Default::default(),
5251                                format: Default::default(),
5252                            }),
5253                            other: Nil::cons(ActionAffordanceExtA { e: 20 })
5254                                .cons(())
5255                                .cons(ActionAffordanceExtC { r: 21 }),
5256                            output: Default::default(),
5257                            safe: Default::default(),
5258                            idempotent: Default::default(),
5259                            synchronous: Default::default(),
5260                        }
5261                    )]
5262                    .into_iter()
5263                    .collect()
5264                ),
5265                events: Some(
5266                    [(
5267                        "event".to_string(),
5268                        EventAffordance {
5269                            interaction: InteractionAffordance {
5270                                other: Nil::cons(InteractionAffordanceExtA { d: 31 })
5271                                    .cons(InteractionAffordanceExtB { j: 32. })
5272                                    .cons(InteractionAffordanceExtC { p: 33 }),
5273                                attype: Default::default(),
5274                                title: Default::default(),
5275                                titles: Default::default(),
5276                                description: Default::default(),
5277                                descriptions: Default::default(),
5278                                forms: Default::default(),
5279                                uri_variables: Default::default(),
5280                            },
5281                            data: Some(DataSchema {
5282                                subtype: Some(DataSchemaSubtype::Boolean),
5283                                other: Nil::cons(DataSchemaExtA { h: 34 })
5284                                    .cons(())
5285                                    .cons(DataSchemaExtC { t: 35 }),
5286                                attype: Default::default(),
5287                                title: Default::default(),
5288                                titles: Default::default(),
5289                                description: Default::default(),
5290                                descriptions: Default::default(),
5291                                constant: Default::default(),
5292                                default: Default::default(),
5293                                unit: Default::default(),
5294                                one_of: Default::default(),
5295                                enumeration: Default::default(),
5296                                read_only: Default::default(),
5297                                write_only: Default::default(),
5298                                format: Default::default(),
5299                            }),
5300                            other: Nil::cons(EventAffordanceExtA { f: 29 })
5301                                .cons(EventAffordanceExtB { l: 30 })
5302                                .cons(()),
5303                            subscription: Default::default(),
5304                            data_response: Default::default(),
5305                            cancellation: Default::default(),
5306                        }
5307                    )]
5308                    .into_iter()
5309                    .collect()
5310                ),
5311                forms: Some(vec![Form {
5312                    href: "href2".to_string(),
5313                    response: Some(ExpectedResponse {
5314                        content_type: "test".to_string(),
5315                        other: Nil::cons(ExpectedResponseExtA { g: 37 })
5316                            .cons(ExpectedResponseExtB { n: 38 })
5317                            .cons(ExpectedResponseExtC { s: 39 })
5318                    }),
5319                    other: Nil::cons(()).cons(FormExtB { m: 36 }).cons(()),
5320                    op: DefaultedFormOperations::Custom(vec![FormOperation::ReadAllProperties]),
5321                    content_type: Default::default(),
5322                    content_coding: Default::default(),
5323                    subprotocol: Default::default(),
5324                    security: Default::default(),
5325                    scopes: Default::default(),
5326                    additional_responses: Default::default(),
5327                }]),
5328                schema_definitions: Some(
5329                    [(
5330                        "schema".to_string(),
5331                        DataSchema {
5332                            subtype: Some(DataSchemaSubtype::Null),
5333                            other: Nil::cons(DataSchemaExtA { h: 40 })
5334                                .cons(())
5335                                .cons(DataSchemaExtC { t: 41 }),
5336                            attype: Default::default(),
5337                            title: Default::default(),
5338                            titles: Default::default(),
5339                            description: Default::default(),
5340                            descriptions: Default::default(),
5341                            constant: Default::default(),
5342                            default: Default::default(),
5343                            unit: Default::default(),
5344                            one_of: Default::default(),
5345                            enumeration: Default::default(),
5346                            read_only: Default::default(),
5347                            write_only: Default::default(),
5348                            format: Default::default(),
5349                        }
5350                    )]
5351                    .into_iter()
5352                    .collect()
5353                ),
5354                attype: Default::default(),
5355                titles: Default::default(),
5356                descriptions: Default::default(),
5357                version: Default::default(),
5358                created: Default::default(),
5359                modified: Default::default(),
5360                support: Default::default(),
5361                base: Default::default(),
5362                links: Default::default(),
5363                security: Default::default(),
5364                security_definitions: Default::default(),
5365                profile: Default::default(),
5366            },
5367        );
5368    }
5369
5370    #[test]
5371    fn additional_response() {
5372        let mut builder = AdditionalExpectedResponseBuilder::new();
5373        assert_eq!(
5374            builder,
5375            AdditionalExpectedResponseBuilder {
5376                success: Default::default(),
5377                content_type: Default::default(),
5378                schema: Default::default(),
5379            },
5380        );
5381
5382        assert_eq!(
5383            *builder.clone().success(),
5384            AdditionalExpectedResponseBuilder {
5385                success: true,
5386                content_type: Default::default(),
5387                schema: Default::default(),
5388            },
5389        );
5390
5391        assert_eq!(
5392            *builder.clone().content_type("hello"),
5393            AdditionalExpectedResponseBuilder {
5394                success: Default::default(),
5395                content_type: Some("hello".to_string()),
5396                schema: Default::default(),
5397            },
5398        );
5399
5400        assert_eq!(
5401            *builder.schema("schema"),
5402            AdditionalExpectedResponseBuilder {
5403                success: Default::default(),
5404                content_type: Default::default(),
5405                schema: Some("schema".to_string()),
5406            },
5407        );
5408    }
5409
5410    #[test]
5411    fn additional_response_with_missing_schema() {
5412        let error = ThingBuilder::<Nil, _>::new("MyLampThing")
5413            .finish_extend()
5414            .schema_definition("schema1", |b| b.finish_extend().null())
5415            .schema_definition("schema2", |b| b.finish_extend().number().minimum(5.))
5416            .form(|b| {
5417                b.href("href")
5418                    .op(FormOperation::ReadAllProperties)
5419                    .additional_response(|b| b.schema("invalid_schema"))
5420            })
5421            .build()
5422            .unwrap_err();
5423
5424        assert_eq!(
5425            error,
5426            Error::MissingSchemaDefinition("invalid_schema".to_string())
5427        );
5428    }
5429
5430    #[test]
5431    fn invalid_thing_uri_variables() {
5432        let error = ThingBuilder::<Nil, _>::new("MyLampThing")
5433            .finish_extend()
5434            .uri_variable("uriVariable", |b| b.finish_extend().object())
5435            .build()
5436            .unwrap_err();
5437
5438        assert_eq!(error, Error::InvalidUriVariables);
5439
5440        let error = ThingBuilder::<Nil, _>::new("MyLampThing")
5441            .finish_extend()
5442            .uri_variable("uriVariable", |b| b.finish_extend().vec())
5443            .build()
5444            .unwrap_err();
5445
5446        assert_eq!(error, Error::InvalidUriVariables);
5447    }
5448
5449    #[test]
5450    fn invalid_interaction_uri_variables() {
5451        let error = ThingBuilder::<Nil, _>::new("MyLampThing")
5452            .finish_extend()
5453            .action("action", |b| {
5454                b.uri_variable("uriVariable", |b| b.finish_extend().object())
5455            })
5456            .build()
5457            .unwrap_err();
5458
5459        assert_eq!(error, Error::InvalidUriVariables);
5460
5461        let error = ThingBuilder::<Nil, _>::new("MyLampThing")
5462            .finish_extend()
5463            .property("property", |b| {
5464                b.finish_extend_data_schema()
5465                    .uri_variable("uriVariable", |b| b.finish_extend().vec())
5466                    .string()
5467            })
5468            .build()
5469            .unwrap_err();
5470
5471        assert_eq!(error, Error::InvalidUriVariables);
5472    }
5473
5474    #[test]
5475    fn combo_security_scheme_with_all_of() {
5476        let builder = SecuritySchemeBuilder {
5477            attype: Default::default(),
5478            description: Default::default(),
5479            descriptions: Default::default(),
5480            proxy: Default::default(),
5481            name: Default::default(),
5482            subtype: (),
5483            required: Default::default(),
5484        }
5485        .combo()
5486        .attype("attype")
5487        .all_of(["schema1", "schema2"])
5488        .extend(["schema3", "schema4"])
5489        .push("schema1")
5490        .description("description");
5491
5492        assert_eq!(builder.attype, Some(vec!["attype".to_string()]));
5493        assert_eq!(builder.description, Some("description".to_string()));
5494        assert_eq!(
5495            builder.subtype,
5496            (
5497                AllOfComboSecuritySchemeTag,
5498                ["schema1", "schema2", "schema3", "schema4", "schema1"]
5499                    .map(String::from)
5500                    .into(),
5501            ),
5502        );
5503
5504        let subtype = builder.subtype.build();
5505        assert_eq!(
5506            subtype,
5507            SecuritySchemeSubtype::Known(KnownSecuritySchemeSubtype::Combo(
5508                ComboSecurityScheme::AllOf(
5509                    ["schema1", "schema2", "schema3", "schema4", "schema1"]
5510                        .map(String::from)
5511                        .into()
5512                )
5513            ))
5514        );
5515    }
5516
5517    #[test]
5518    fn combo_security_scheme_with_one_of() {
5519        let builder = SecuritySchemeBuilder {
5520            attype: Default::default(),
5521            description: Default::default(),
5522            descriptions: Default::default(),
5523            proxy: Default::default(),
5524            name: Default::default(),
5525            subtype: (),
5526            required: Default::default(),
5527        }
5528        .combo()
5529        .attype("attype")
5530        .one_of(["schema1", "schema2"])
5531        .extend(["schema3", "schema4"])
5532        .push("schema1")
5533        .description("description");
5534
5535        assert_eq!(builder.attype, Some(vec!["attype".to_string()]));
5536        assert_eq!(builder.description, Some("description".to_string()));
5537        assert_eq!(
5538            builder.subtype,
5539            (
5540                OneOfComboSecuritySchemeTag,
5541                ["schema1", "schema2", "schema3", "schema4", "schema1"]
5542                    .map(String::from)
5543                    .into(),
5544            ),
5545        );
5546
5547        let subtype = builder.subtype.build();
5548        assert_eq!(
5549            subtype,
5550            SecuritySchemeSubtype::Known(KnownSecuritySchemeSubtype::Combo(
5551                ComboSecurityScheme::OneOf(
5552                    ["schema1", "schema2", "schema3", "schema4", "schema1"]
5553                        .map(String::from)
5554                        .into()
5555                )
5556            ))
5557        );
5558    }
5559
5560    #[test]
5561    fn valid_combo_security_scheme() {
5562        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
5563            .security(|b| b.basic())
5564            .security(|b| b.combo().one_of(["basic", "nosec"]))
5565            .security(|b| b.no_sec())
5566            .build()
5567            .unwrap();
5568
5569        assert_eq!(
5570            thing,
5571            Thing {
5572                context: TD_CONTEXT_11.into(),
5573                title: "MyLampThing".to_string(),
5574                security_definitions: [
5575                    (
5576                        "basic".to_string(),
5577                        SecurityScheme {
5578                            subtype: SecuritySchemeSubtype::Known(
5579                                KnownSecuritySchemeSubtype::Basic(Default::default())
5580                            ),
5581                            ..Default::default()
5582                        }
5583                    ),
5584                    (
5585                        "combo".to_string(),
5586                        SecurityScheme {
5587                            subtype: SecuritySchemeSubtype::Known(
5588                                KnownSecuritySchemeSubtype::Combo(ComboSecurityScheme::OneOf(
5589                                    vec!["basic".to_string(), "nosec".to_string()]
5590                                ))
5591                            ),
5592                            ..Default::default()
5593                        }
5594                    ),
5595                    (
5596                        "nosec".to_string(),
5597                        SecurityScheme {
5598                            subtype: SecuritySchemeSubtype::Known(
5599                                KnownSecuritySchemeSubtype::NoSec
5600                            ),
5601                            ..Default::default()
5602                        }
5603                    ),
5604                ]
5605                .into_iter()
5606                .collect(),
5607                ..Default::default()
5608            },
5609        );
5610    }
5611
5612    #[test]
5613    fn missing_combo_security_scheme() {
5614        let err = ThingBuilder::<Nil, _>::new("MyLampThing")
5615            .security(|b| b.combo().one_of(["basic", "nosec"]))
5616            .security(|b| b.no_sec())
5617            .build()
5618            .unwrap_err();
5619
5620        assert_eq!(err, Error::MissingSchemaDefinition("basic".to_string()));
5621    }
5622
5623    #[test]
5624    fn checked_op_in_form() {
5625        let thing = ThingBuilder::<Nil, _>::new("MyLampThing")
5626            .finish_extend()
5627            .form(|b| {
5628                b.op(FormOperation::ReadAllProperties)
5629                    .op(FormOperation::WriteAllProperties)
5630                    .op(FormOperation::ReadMultipleProperties)
5631                    .op(FormOperation::WriteMultipleProperties)
5632                    .op(FormOperation::ObserveAllProperties)
5633                    .op(FormOperation::UnobserveAllProperties)
5634                    .op(FormOperation::SubscribeAllEvents)
5635                    .op(FormOperation::UnsubscribeAllEvents)
5636                    .op(FormOperation::QueryAllActions)
5637                    .href("href")
5638            })
5639            .property("property", |b| {
5640                b.finish_extend_data_schema().null().form(|b| {
5641                    b.op(FormOperation::ReadProperty)
5642                        .op(FormOperation::WriteProperty)
5643                        .op(FormOperation::ObserveProperty)
5644                        .op(FormOperation::UnobserveProperty)
5645                        .href("href")
5646                })
5647            })
5648            .action("action", |b| {
5649                b.form(|b| {
5650                    b.op(FormOperation::InvokeAction)
5651                        .op(FormOperation::QueryAction)
5652                        .op(FormOperation::CancelAction)
5653                        .href("href")
5654                })
5655            })
5656            .event("event", |b| {
5657                b.form(|b| {
5658                    b.op(FormOperation::SubscribeEvent)
5659                        .op(FormOperation::UnsubscribeEvent)
5660                        .href("href")
5661                })
5662            })
5663            .build()
5664            .unwrap();
5665
5666        assert_eq!(
5667            thing,
5668            Thing {
5669                context: TD_CONTEXT_11.into(),
5670                title: "MyLampThing".to_string(),
5671                forms: Some(vec![Form {
5672                    op: DefaultedFormOperations::Custom(vec![
5673                        FormOperation::ReadAllProperties,
5674                        FormOperation::WriteAllProperties,
5675                        FormOperation::ReadMultipleProperties,
5676                        FormOperation::WriteMultipleProperties,
5677                        FormOperation::ObserveAllProperties,
5678                        FormOperation::UnobserveAllProperties,
5679                        FormOperation::SubscribeAllEvents,
5680                        FormOperation::UnsubscribeAllEvents,
5681                        FormOperation::QueryAllActions
5682                    ]),
5683                    href: "href".to_string(),
5684                    ..Default::default()
5685                }]),
5686                properties: Some(
5687                    [(
5688                        "property".to_string(),
5689                        PropertyAffordance {
5690                            interaction: InteractionAffordance {
5691                                forms: vec![Form {
5692                                    op: DefaultedFormOperations::Custom(vec![
5693                                        FormOperation::ReadProperty,
5694                                        FormOperation::WriteProperty,
5695                                        FormOperation::ObserveProperty,
5696                                        FormOperation::UnobserveProperty,
5697                                    ]),
5698                                    href: "href".to_string(),
5699                                    ..Default::default()
5700                                }],
5701                                ..Default::default()
5702                            },
5703                            data_schema: DataSchema {
5704                                subtype: Some(DataSchemaSubtype::Null),
5705                                ..Default::default()
5706                            },
5707                            ..Default::default()
5708                        }
5709                    )]
5710                    .into_iter()
5711                    .collect()
5712                ),
5713                actions: Some(
5714                    [(
5715                        "action".to_string(),
5716                        ActionAffordance {
5717                            interaction: InteractionAffordance {
5718                                forms: vec![Form {
5719                                    op: DefaultedFormOperations::Custom(vec![
5720                                        FormOperation::InvokeAction,
5721                                        FormOperation::QueryAction,
5722                                        FormOperation::CancelAction,
5723                                    ]),
5724                                    href: "href".to_string(),
5725                                    ..Default::default()
5726                                }],
5727                                ..Default::default()
5728                            },
5729                            ..Default::default()
5730                        }
5731                    )]
5732                    .into_iter()
5733                    .collect()
5734                ),
5735                events: Some(
5736                    [(
5737                        "event".to_string(),
5738                        EventAffordance {
5739                            interaction: InteractionAffordance {
5740                                forms: vec![Form {
5741                                    op: DefaultedFormOperations::Custom(vec![
5742                                        FormOperation::SubscribeEvent,
5743                                        FormOperation::UnsubscribeEvent,
5744                                    ]),
5745                                    href: "href".to_string(),
5746                                    ..Default::default()
5747                                }],
5748                                ..Default::default()
5749                            },
5750                            ..Default::default()
5751                        }
5752                    )]
5753                    .into_iter()
5754                    .collect()
5755                ),
5756                ..Default::default()
5757            },
5758        )
5759    }
5760
5761    #[test]
5762    fn invalid_form_with_invalid_op_in_property_affordance() {
5763        let err = ThingBuilder::<Nil, _>::new("MyLampThing")
5764            .finish_extend()
5765            .property("property", |b| {
5766                b.finish_extend_data_schema().null().form(|b| {
5767                    b.op(FormOperation::ReadProperty)
5768                        .op(FormOperation::ReadAllProperties)
5769                        .op(FormOperation::WriteAllProperties)
5770                        .href("href")
5771                })
5772            })
5773            .build()
5774            .unwrap_err();
5775
5776        assert_eq!(
5777            err,
5778            Error::InvalidOpInForm {
5779                context: FormContext::Property,
5780                operation: FormOperation::ReadAllProperties
5781            }
5782        );
5783    }
5784
5785    #[test]
5786    fn invalid_form_with_invalid_op_in_action_affordance() {
5787        let err = ThingBuilder::<Nil, _>::new("MyLampThing")
5788            .finish_extend()
5789            .action("action", |b| {
5790                b.form(|b| {
5791                    b.op(FormOperation::InvokeAction)
5792                        .op(FormOperation::WriteProperty)
5793                        .op(FormOperation::QueryAction)
5794                        .href("href")
5795                })
5796            })
5797            .build()
5798            .unwrap_err();
5799
5800        assert_eq!(
5801            err,
5802            Error::InvalidOpInForm {
5803                context: FormContext::Action,
5804                operation: FormOperation::WriteProperty
5805            }
5806        );
5807    }
5808
5809    #[test]
5810    fn invalid_form_with_invalid_op_in_event_affordance() {
5811        let err = ThingBuilder::<Nil, _>::new("MyLampThing")
5812            .finish_extend()
5813            .event("event", |b| {
5814                b.form(|b| {
5815                    b.op(FormOperation::SubscribeEvent)
5816                        .op(FormOperation::ReadProperty)
5817                        .op(FormOperation::UnsubscribeEvent)
5818                        .href("href")
5819                })
5820            })
5821            .build()
5822            .unwrap_err();
5823
5824        assert_eq!(
5825            err,
5826            Error::InvalidOpInForm {
5827                context: FormContext::Event,
5828                operation: FormOperation::ReadProperty
5829            }
5830        );
5831    }
5832
5833    #[test]
5834    fn form_operation_serialize_display_coherence() {
5835        const OPS: [FormOperation; 18] = [
5836            FormOperation::ReadProperty,
5837            FormOperation::WriteProperty,
5838            FormOperation::ObserveProperty,
5839            FormOperation::UnobserveProperty,
5840            FormOperation::InvokeAction,
5841            FormOperation::QueryAction,
5842            FormOperation::CancelAction,
5843            FormOperation::SubscribeEvent,
5844            FormOperation::UnsubscribeEvent,
5845            FormOperation::ReadAllProperties,
5846            FormOperation::WriteAllProperties,
5847            FormOperation::ReadMultipleProperties,
5848            FormOperation::WriteMultipleProperties,
5849            FormOperation::ObserveAllProperties,
5850            FormOperation::UnobserveAllProperties,
5851            FormOperation::SubscribeAllEvents,
5852            FormOperation::UnsubscribeAllEvents,
5853            FormOperation::QueryAllActions,
5854        ];
5855
5856        for op in OPS {
5857            assert_eq!(
5858                serde_json::to_value(op).unwrap(),
5859                Value::String(op.to_string())
5860            );
5861        }
5862    }
5863
5864    #[test]
5865    fn convert_valid_unchecked_security_schema() {
5866        let schema = UncheckedSecurityScheme {
5867            attype: Some(vec!["attype1".to_string(), "attype2".to_string()]),
5868            description: Some("description".to_string()),
5869            descriptions: Some({
5870                let mut multilang = MultiLanguageBuilder::default();
5871                multilang
5872                    .add("it", "description1")
5873                    .add("en", "description2");
5874                multilang
5875            }),
5876            proxy: Some("proxy".to_string()),
5877            subtype: SecuritySchemeSubtype::Known(KnownSecuritySchemeSubtype::Psk(
5878                PskSecurityScheme {
5879                    identity: Some("identity".to_string()),
5880                },
5881            )),
5882        };
5883
5884        assert_eq!(
5885            SecurityScheme::try_from(schema).unwrap(),
5886            SecurityScheme {
5887                attype: Some(vec!["attype1".to_string(), "attype2".to_string()]),
5888                description: Some("description".to_string()),
5889                descriptions: Some(
5890                    [
5891                        ("it".parse().unwrap(), "description1".to_string()),
5892                        ("en".parse().unwrap(), "description2".to_string())
5893                    ]
5894                    .into_iter()
5895                    .collect(),
5896                ),
5897                proxy: Some("proxy".to_string()),
5898                subtype: SecuritySchemeSubtype::Known(KnownSecuritySchemeSubtype::Psk(
5899                    PskSecurityScheme {
5900                        identity: Some("identity".to_string()),
5901                    },
5902                )),
5903            },
5904        );
5905    }
5906
5907    #[test]
5908    fn convert_invalid_unchecked_security_schema() {
5909        let schema = UncheckedSecurityScheme {
5910            attype: Some(vec!["attype1".to_string(), "attype2".to_string()]),
5911            description: Some("description".to_string()),
5912            descriptions: Some({
5913                let mut multilang = MultiLanguageBuilder::default();
5914                multilang
5915                    .add("it", "description1")
5916                    .add("e1n", "description2");
5917                multilang
5918            }),
5919            proxy: Some("proxy".to_string()),
5920            subtype: SecuritySchemeSubtype::Known(KnownSecuritySchemeSubtype::Psk(
5921                PskSecurityScheme {
5922                    identity: Some("identity".to_string()),
5923                },
5924            )),
5925        };
5926
5927        assert_eq!(
5928            SecurityScheme::try_from(schema).unwrap_err(),
5929            Error::InvalidLanguageTag("e1n".to_string()),
5930        );
5931    }
5932
5933    #[test]
5934    fn invalid_language_tag() {
5935        let err = ThingBuilder::<Nil, _>::new("MyLampThing")
5936            .security(|b| {
5937                b.auto()
5938                    .descriptions(|ml| ml.add("en", "desc_en").add("i1t", "desc_it"))
5939            })
5940            .build()
5941            .unwrap_err();
5942        assert_eq!(err, Error::InvalidLanguageTag("i1t".to_string()));
5943    }
5944}