1pub 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
242pub mod typetags {
244 #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
246 pub struct ToExtend;
247
248 #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
250 pub struct Extended;
251}
252
253pub use self::typetags::*;
254
255#[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 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#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)]
307pub enum Error {
308 #[error("Two security definitions use the name \"{0}\"")]
310 DuplicatedSecurityDefinition(String),
311
312 #[error("A Form directly placed in a Thing must contain at least one relevant operation")]
315 MissingOpInForm,
316
317 #[error("Invalid Form operation {operation} in {context} context")]
319 InvalidOpInForm {
320 context: FormContext,
322
323 operation: FormOperation,
325 },
326
327 #[error("Security \"{0}\" is not specified in Thing security definitions")]
329 UndefinedSecurity(String),
330
331 #[error("Min value greater than max value")]
333 InvalidMinMax,
334
335 #[error("Min or Max value is NaN")]
337 NanMinMax,
338
339 #[error("Two affordances of type {ty} use the name \"{name}\"")]
341 DuplicatedAffordance {
342 ty: AffordanceType,
344
345 name: String,
347 },
348
349 #[error("\"multipleOf\" field must be strictly greater than 0")]
351 InvalidMultipleOf,
352
353 #[error("Using the data schema \"{0}\", which is not declared in the schema definitions")]
355 MissingSchemaDefinition(String),
356
357 #[error("An uriVariable cannot be an ObjectSchema or ArraySchema")]
359 InvalidUriVariables,
360
361 #[error("Invalid language tag \"{0}\"")]
363 InvalidLanguageTag(String),
364
365 #[error("A sizes field can be used only when \"rel\" is \"icon\"")]
367 SizesWithRelNotIcon,
368}
369
370#[derive(Debug, Clone, PartialEq, Eq, Hash)]
374pub enum FormContext {
375 Thing,
377
378 Property,
380
381 Action,
383
384 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
413pub enum AffordanceType {
414 Property,
416
417 Action,
419
420 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 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 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 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 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 #[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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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#[must_use]
1755pub struct ContextMapBuilder(HashMap<String, String>);
1756
1757impl ContextMapBuilder {
1758 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#[derive(Clone, Debug, Default, PartialEq, Eq)]
1767pub struct MultiLanguageBuilder<T> {
1768 values: HashMap<String, T>,
1769}
1770
1771impl<T> MultiLanguageBuilder<T> {
1772 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 k.parse()
1787 .map(|k| (k, v))
1788 .map_err(|_| Error::InvalidLanguageTag(k))
1789 })
1790 .collect()
1791 }
1792}
1793
1794pub 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 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 pub fn hreflang(mut self, value: impl Into<String>) -> Self {
1844 self.hreflang.push(value.into());
1845 self
1846 }
1847}
1848
1849pub 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 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 pub struct SecuritySchemeNoSecTag;
1877
1878 pub struct SecuritySchemeAutoTag;
1880
1881 pub struct EmptyComboSecuritySchemeTag;
1883
1884 #[derive(Debug, Default, PartialEq, Eq)]
1886 pub struct AllOfComboSecuritySchemeTag;
1887
1888 #[derive(Debug, Default, PartialEq, Eq)]
1890 pub struct OneOfComboSecuritySchemeTag;
1891
1892 pub trait BuildableSecuritySchemeSubtype {
1894 fn build(self) -> SecuritySchemeSubtype;
1896 }
1897
1898 impl SecuritySchemeBuilder<()> {
1899 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 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 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 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 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 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 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 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 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 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 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 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 pub fn with_key(mut self, name: impl Into<String>) -> Self {
2174 self.name = Some(name.into());
2175 self
2176 }
2177
2178 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 pub trait HasNameLocation {
2244 fn location_mut(&mut self) -> &mut SecurityAuthenticationLocation;
2246 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 pub fn name(mut self, value: impl Into<String>) -> Self {
2296 *self.subtype.name_mut() = Some(value.into());
2297 self
2298 }
2299
2300 pub fn location(mut self, value: SecurityAuthenticationLocation) -> Self {
2302 *self.subtype.location_mut() = value;
2303 self
2304 }
2305 }
2306
2307 impl SecuritySchemeBuilder<EmptyComboSecuritySchemeTag> {
2308 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 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 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 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 pub fn push(mut self, security_name: impl Into<String>) -> Self {
2500 self.subtype.1.push(security_name.into());
2501 self
2502 }
2503
2504 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 pub fn qop(mut self, value: QualityOfProtection) -> Self {
2514 self.subtype.qop = value;
2515 self
2516 }
2517 }
2518
2519 impl SecuritySchemeBuilder<BearerSecurityScheme> {
2520 pub fn authorization(mut self, value: impl Into<String>) -> Self {
2522 self.subtype.authorization = Some(value.into());
2523 self
2524 }
2525
2526 pub fn alg(mut self, value: impl Into<Cow<'static, str>>) -> Self {
2528 self.subtype.alg = value.into();
2529 self
2530 }
2531
2532 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 pub fn authorization(mut self, value: impl Into<String>) -> Self {
2542 self.subtype.authorization = Some(value.into());
2543 self
2544 }
2545
2546 pub fn token(mut self, value: impl Into<String>) -> Self {
2548 self.subtype.token = Some(value.into());
2549 self
2550 }
2551
2552 pub fn refresh(mut self, value: impl Into<String>) -> Self {
2554 self.subtype.refresh = Some(value.into());
2555 self
2556 }
2557
2558 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 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
2579pub 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 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 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 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 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 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 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 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 #[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 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 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#[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 pub fn success(&mut self) -> &mut Self {
2930 self.success = true;
2931 self
2932 }
2933
2934 pub fn content_type(&mut self, value: impl Into<String>) -> &mut Self {
2936 self.content_type = Some(value.into());
2937 self
2938 }
2939
2940 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
2981pub 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}