xref: /webrtc/constraints/src/constraint.rs (revision c19675fc)
1 use std::ops::Deref;
2 
3 #[cfg(feature = "serde")]
4 use serde::{Deserialize, Serialize};
5 
6 use crate::MediaTrackSetting;
7 
8 pub use self::{
9     value::{ResolvedValueConstraint, ValueConstraint},
10     value_range::{ResolvedValueRangeConstraint, ValueRangeConstraint},
11     value_sequence::{ResolvedValueSequenceConstraint, ValueSequenceConstraint},
12 };
13 
14 mod value;
15 mod value_range;
16 mod value_sequence;
17 
18 /// An empty [constraint][media_track_constraints] value for a [`MediaStreamTrack`][media_stream_track] object.
19 ///
20 /// # W3C Spec Compliance
21 ///
22 /// There exists no corresponding type in the W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec.
23 ///
24 /// The purpose of this type is to reduce parsing ambiguity, since all constraint variant types
25 /// support serializing from an empty map, but an empty map isn't typed, really,
26 /// so parsing to a specifically typed constraint would be wrong, type-wise.
27 ///
28 /// [media_stream_track]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamtrack
29 /// [media_track_constraints]: https://www.w3.org/TR/mediacapture-streams/#dom-mediatrackconstraints
30 /// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams
31 #[derive(Debug, Clone, Eq, PartialEq)]
32 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
33 #[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
34 pub struct EmptyConstraint {}
35 
36 /// The strategy of a track [constraint][constraint].
37 ///
38 /// [constraint]: https://www.w3.org/TR/mediacapture-streams/#dfn-constraint
39 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
40 pub enum MediaTrackConstraintResolutionStrategy {
41     /// Resolve bare values to `ideal` constraints.
42     BareToIdeal,
43     /// Resolve bare values to `exact` constraints.
44     BareToExact,
45 }
46 
47 /// A single [constraint][media_track_constraints] value for a [`MediaStreamTrack`][media_stream_track] object.
48 ///
49 /// # W3C Spec Compliance
50 ///
51 /// There exists no corresponding type in the W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec.
52 ///
53 /// [media_stream_track]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamtrack
54 /// [media_track_constraints]: https://www.w3.org/TR/mediacapture-streams/#dom-mediatrackconstraints
55 /// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams
56 #[derive(Debug, Clone, PartialEq)]
57 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
58 #[cfg_attr(feature = "serde", serde(untagged))]
59 pub enum MediaTrackConstraint {
60     Empty(EmptyConstraint),
61     // `IntegerRange` must be ordered before `FloatRange(…)` in order for
62     // `serde` to decode the correct variant.
63     IntegerRange(ValueRangeConstraint<u64>),
64     FloatRange(ValueRangeConstraint<f64>),
65     // `Bool` must be ordered after `IntegerRange(…)`/`FloatRange(…)` in order for
66     // `serde` to decode the correct variant.
67     Bool(ValueConstraint<bool>),
68     // `StringSequence` must be ordered before `String(…)` in order for
69     // `serde` to decode the correct variant.
70     StringSequence(ValueSequenceConstraint<String>),
71     String(ValueConstraint<String>),
72 }
73 
74 impl Default for MediaTrackConstraint {
75     fn default() -> Self {
76         Self::Empty(EmptyConstraint {})
77     }
78 }
79 
80 // Bool constraint:
81 
82 impl From<bool> for MediaTrackConstraint {
83     fn from(bare: bool) -> Self {
84         Self::Bool(bare.into())
85     }
86 }
87 
88 impl From<ResolvedValueConstraint<bool>> for MediaTrackConstraint {
89     fn from(constraint: ResolvedValueConstraint<bool>) -> Self {
90         Self::Bool(constraint.into())
91     }
92 }
93 
94 impl From<ValueConstraint<bool>> for MediaTrackConstraint {
95     fn from(constraint: ValueConstraint<bool>) -> Self {
96         Self::Bool(constraint)
97     }
98 }
99 
100 // Unsigned integer range constraint:
101 
102 impl From<u64> for MediaTrackConstraint {
103     fn from(bare: u64) -> Self {
104         Self::IntegerRange(bare.into())
105     }
106 }
107 
108 impl From<ResolvedValueRangeConstraint<u64>> for MediaTrackConstraint {
109     fn from(constraint: ResolvedValueRangeConstraint<u64>) -> Self {
110         Self::IntegerRange(constraint.into())
111     }
112 }
113 
114 impl From<ValueRangeConstraint<u64>> for MediaTrackConstraint {
115     fn from(constraint: ValueRangeConstraint<u64>) -> Self {
116         Self::IntegerRange(constraint)
117     }
118 }
119 
120 // Floating-point range constraint:
121 
122 impl From<f64> for MediaTrackConstraint {
123     fn from(bare: f64) -> Self {
124         Self::FloatRange(bare.into())
125     }
126 }
127 
128 impl From<ResolvedValueRangeConstraint<f64>> for MediaTrackConstraint {
129     fn from(constraint: ResolvedValueRangeConstraint<f64>) -> Self {
130         Self::FloatRange(constraint.into())
131     }
132 }
133 
134 impl From<ValueRangeConstraint<f64>> for MediaTrackConstraint {
135     fn from(constraint: ValueRangeConstraint<f64>) -> Self {
136         Self::FloatRange(constraint)
137     }
138 }
139 
140 // String sequence constraint:
141 
142 impl From<Vec<String>> for MediaTrackConstraint {
143     fn from(bare: Vec<String>) -> Self {
144         Self::StringSequence(bare.into())
145     }
146 }
147 
148 impl From<Vec<&str>> for MediaTrackConstraint {
149     fn from(bare: Vec<&str>) -> Self {
150         let bare: Vec<String> = bare.into_iter().map(|c| c.to_owned()).collect();
151         Self::from(bare)
152     }
153 }
154 
155 impl From<ResolvedValueSequenceConstraint<String>> for MediaTrackConstraint {
156     fn from(constraint: ResolvedValueSequenceConstraint<String>) -> Self {
157         Self::StringSequence(constraint.into())
158     }
159 }
160 
161 impl From<ValueSequenceConstraint<String>> for MediaTrackConstraint {
162     fn from(constraint: ValueSequenceConstraint<String>) -> Self {
163         Self::StringSequence(constraint)
164     }
165 }
166 
167 // String constraint:
168 
169 impl From<String> for MediaTrackConstraint {
170     fn from(bare: String) -> Self {
171         Self::String(bare.into())
172     }
173 }
174 
175 impl<'a> From<&'a str> for MediaTrackConstraint {
176     fn from(bare: &'a str) -> Self {
177         let bare: String = bare.to_owned();
178         Self::from(bare)
179     }
180 }
181 
182 impl From<ResolvedValueConstraint<String>> for MediaTrackConstraint {
183     fn from(constraint: ResolvedValueConstraint<String>) -> Self {
184         Self::String(constraint.into())
185     }
186 }
187 
188 impl From<ValueConstraint<String>> for MediaTrackConstraint {
189     fn from(constraint: ValueConstraint<String>) -> Self {
190         Self::String(constraint)
191     }
192 }
193 
194 // Conversion from settings:
195 
196 impl From<MediaTrackSetting> for MediaTrackConstraint {
197     fn from(settings: MediaTrackSetting) -> Self {
198         match settings {
199             MediaTrackSetting::Bool(value) => Self::Bool(value.into()),
200             MediaTrackSetting::Integer(value) => {
201                 Self::IntegerRange((value.clamp(0, i64::MAX) as u64).into())
202             }
203             MediaTrackSetting::Float(value) => Self::FloatRange(value.into()),
204             MediaTrackSetting::String(value) => Self::String(value.into()),
205         }
206     }
207 }
208 
209 impl MediaTrackConstraint {
210     pub fn is_empty(&self) -> bool {
211         match self {
212             Self::Empty(_) => true,
213             Self::IntegerRange(constraint) => constraint.is_empty(),
214             Self::FloatRange(constraint) => constraint.is_empty(),
215             Self::Bool(constraint) => constraint.is_empty(),
216             Self::StringSequence(constraint) => constraint.is_empty(),
217             Self::String(constraint) => constraint.is_empty(),
218         }
219     }
220 
221     pub fn to_resolved(
222         &self,
223         strategy: MediaTrackConstraintResolutionStrategy,
224     ) -> ResolvedMediaTrackConstraint {
225         self.clone().into_resolved(strategy)
226     }
227 
228     pub fn into_resolved(
229         self,
230         strategy: MediaTrackConstraintResolutionStrategy,
231     ) -> ResolvedMediaTrackConstraint {
232         match self {
233             Self::Empty(constraint) => ResolvedMediaTrackConstraint::Empty(constraint),
234             Self::IntegerRange(constraint) => {
235                 ResolvedMediaTrackConstraint::IntegerRange(constraint.into_resolved(strategy))
236             }
237             Self::FloatRange(constraint) => {
238                 ResolvedMediaTrackConstraint::FloatRange(constraint.into_resolved(strategy))
239             }
240             Self::Bool(constraint) => {
241                 ResolvedMediaTrackConstraint::Bool(constraint.into_resolved(strategy))
242             }
243             Self::StringSequence(constraint) => {
244                 ResolvedMediaTrackConstraint::StringSequence(constraint.into_resolved(strategy))
245             }
246             Self::String(constraint) => {
247                 ResolvedMediaTrackConstraint::String(constraint.into_resolved(strategy))
248             }
249         }
250     }
251 }
252 
253 /// A single [constraint][media_track_constraints] value for a [`MediaStreamTrack`][media_stream_track] object
254 /// with its potential bare value either resolved to an `exact` or `ideal` constraint.
255 ///
256 /// # W3C Spec Compliance
257 ///
258 /// There exists no corresponding type in the W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec.
259 ///
260 /// [media_stream_track]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamtrack
261 /// [media_track_constraints]: https://www.w3.org/TR/mediacapture-streams/#dom-mediatrackconstraints
262 /// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams
263 #[derive(Debug, Clone, PartialEq)]
264 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
265 #[cfg_attr(feature = "serde", serde(untagged))]
266 pub enum ResolvedMediaTrackConstraint {
267     Empty(EmptyConstraint),
268     IntegerRange(ResolvedValueRangeConstraint<u64>),
269     FloatRange(ResolvedValueRangeConstraint<f64>),
270     Bool(ResolvedValueConstraint<bool>),
271     StringSequence(ResolvedValueSequenceConstraint<String>),
272     String(ResolvedValueConstraint<String>),
273 }
274 
275 impl Default for ResolvedMediaTrackConstraint {
276     fn default() -> Self {
277         Self::Empty(EmptyConstraint {})
278     }
279 }
280 
281 impl std::fmt::Display for ResolvedMediaTrackConstraint {
282     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
283         match self {
284             Self::Empty(_constraint) => "<empty>".fmt(f),
285             Self::IntegerRange(constraint) => constraint.fmt(f),
286             Self::FloatRange(constraint) => constraint.fmt(f),
287             Self::Bool(constraint) => constraint.fmt(f),
288             Self::StringSequence(constraint) => constraint.fmt(f),
289             Self::String(constraint) => constraint.fmt(f),
290         }
291     }
292 }
293 
294 // Bool constraint:
295 
296 impl From<ResolvedValueConstraint<bool>> for ResolvedMediaTrackConstraint {
297     fn from(constraint: ResolvedValueConstraint<bool>) -> Self {
298         Self::Bool(constraint)
299     }
300 }
301 
302 // Unsigned integer range constraint:
303 
304 impl From<ResolvedValueRangeConstraint<u64>> for ResolvedMediaTrackConstraint {
305     fn from(constraint: ResolvedValueRangeConstraint<u64>) -> Self {
306         Self::IntegerRange(constraint)
307     }
308 }
309 
310 // Floating-point range constraint:
311 
312 impl From<ResolvedValueRangeConstraint<f64>> for ResolvedMediaTrackConstraint {
313     fn from(constraint: ResolvedValueRangeConstraint<f64>) -> Self {
314         Self::FloatRange(constraint)
315     }
316 }
317 
318 // String sequence constraint:
319 
320 impl From<ResolvedValueSequenceConstraint<String>> for ResolvedMediaTrackConstraint {
321     fn from(constraint: ResolvedValueSequenceConstraint<String>) -> Self {
322         Self::StringSequence(constraint)
323     }
324 }
325 
326 // String constraint:
327 
328 impl From<ResolvedValueConstraint<String>> for ResolvedMediaTrackConstraint {
329     fn from(constraint: ResolvedValueConstraint<String>) -> Self {
330         Self::String(constraint)
331     }
332 }
333 
334 impl ResolvedMediaTrackConstraint {
335     pub fn exact_from(setting: MediaTrackSetting) -> Self {
336         MediaTrackConstraint::from(setting)
337             .into_resolved(MediaTrackConstraintResolutionStrategy::BareToExact)
338     }
339 
340     pub fn ideal_from(setting: MediaTrackSetting) -> Self {
341         MediaTrackConstraint::from(setting)
342             .into_resolved(MediaTrackConstraintResolutionStrategy::BareToIdeal)
343     }
344 
345     pub fn is_required(&self) -> bool {
346         match self {
347             Self::Empty(_constraint) => false,
348             Self::IntegerRange(constraint) => constraint.is_required(),
349             Self::FloatRange(constraint) => constraint.is_required(),
350             Self::Bool(constraint) => constraint.is_required(),
351             Self::StringSequence(constraint) => constraint.is_required(),
352             Self::String(constraint) => constraint.is_required(),
353         }
354     }
355 
356     pub fn is_empty(&self) -> bool {
357         match self {
358             Self::Empty(_constraint) => true,
359             Self::IntegerRange(constraint) => constraint.is_empty(),
360             Self::FloatRange(constraint) => constraint.is_empty(),
361             Self::Bool(constraint) => constraint.is_empty(),
362             Self::StringSequence(constraint) => constraint.is_empty(),
363             Self::String(constraint) => constraint.is_empty(),
364         }
365     }
366 
367     pub fn to_required_only(&self) -> Self {
368         self.clone().into_required_only()
369     }
370 
371     pub fn into_required_only(self) -> Self {
372         match self {
373             Self::Empty(constraint) => Self::Empty(constraint),
374             Self::IntegerRange(constraint) => Self::IntegerRange(constraint.into_required_only()),
375             Self::FloatRange(constraint) => Self::FloatRange(constraint.into_required_only()),
376             Self::Bool(constraint) => Self::Bool(constraint.into_required_only()),
377             Self::StringSequence(constraint) => {
378                 Self::StringSequence(constraint.into_required_only())
379             }
380             Self::String(constraint) => Self::String(constraint.into_required_only()),
381         }
382     }
383 
384     pub fn to_sanitized(&self) -> Option<SanitizedMediaTrackConstraint> {
385         self.clone().into_sanitized()
386     }
387 
388     pub fn into_sanitized(self) -> Option<SanitizedMediaTrackConstraint> {
389         if self.is_empty() {
390             return None;
391         }
392 
393         Some(SanitizedMediaTrackConstraint(self))
394     }
395 }
396 
397 /// A single non-empty [constraint][media_track_constraints] value for a [`MediaStreamTrack`][media_stream_track] object.
398 ///
399 /// # Invariant
400 ///
401 /// The wrapped `ResolvedMediaTrackConstraint` MUST not be empty.
402 ///
403 /// To enforce this invariant the only way to create an instance of this type
404 /// is by calling `constraint.to_sanitized()`/`constraint.into_sanitized()` on
405 /// an instance of `ResolvedMediaTrackConstraint`, which returns `None` if `self` is empty.
406 ///
407 /// Further more `self.0` MUST NOT be exposed mutably,
408 /// as otherwise it could become empty via mutation.
409 #[derive(Debug, Clone, PartialEq)]
410 pub struct SanitizedMediaTrackConstraint(ResolvedMediaTrackConstraint);
411 
412 impl Deref for SanitizedMediaTrackConstraint {
413     type Target = ResolvedMediaTrackConstraint;
414 
415     fn deref(&self) -> &Self::Target {
416         &self.0
417     }
418 }
419 
420 impl SanitizedMediaTrackConstraint {
421     pub fn into_inner(self) -> ResolvedMediaTrackConstraint {
422         self.0
423     }
424 }
425 
426 #[cfg(test)]
427 mod tests {
428     use super::*;
429 
430     use MediaTrackConstraintResolutionStrategy::*;
431 
432     type Subject = MediaTrackConstraint;
433 
434     #[test]
435     fn default() {
436         let subject = Subject::default();
437 
438         let actual = subject.is_empty();
439         let expected = true;
440 
441         assert_eq!(actual, expected);
442     }
443 
444     mod from {
445 
446         use super::*;
447 
448         #[test]
449         fn setting() {
450             use crate::MediaTrackSetting;
451 
452             assert!(matches!(
453                 Subject::from(MediaTrackSetting::Bool(true)),
454                 Subject::Bool(ValueConstraint::Bare(_))
455             ));
456             assert!(matches!(
457                 Subject::from(MediaTrackSetting::Integer(42)),
458                 Subject::IntegerRange(ValueRangeConstraint::Bare(_))
459             ));
460             assert!(matches!(
461                 Subject::from(MediaTrackSetting::Float(4.2)),
462                 Subject::FloatRange(ValueRangeConstraint::Bare(_))
463             ));
464             assert!(matches!(
465                 Subject::from(MediaTrackSetting::String("string".to_owned())),
466                 Subject::String(ValueConstraint::Bare(_))
467             ));
468         }
469 
470         #[test]
471         fn bool() {
472             let subjects = [
473                 Subject::from(false),
474                 Subject::from(ValueConstraint::<bool>::default()),
475                 Subject::from(ResolvedValueConstraint::<bool>::default()),
476             ];
477 
478             for subject in subjects {
479                 // TODO: replace with `assert_matches!(…)`, once stabilized:
480                 // Tracking issue: https://github.com/rust-lang/rust/issues/82775
481                 assert!(matches!(subject, Subject::Bool(_)));
482             }
483         }
484 
485         #[test]
486         fn integer_range() {
487             let subjects = [
488                 Subject::from(42_u64),
489                 Subject::from(ValueRangeConstraint::<u64>::default()),
490                 Subject::from(ResolvedValueRangeConstraint::<u64>::default()),
491             ];
492 
493             for subject in subjects {
494                 // TODO: replace with `assert_matches!(…)`, once stabilized:
495                 // Tracking issue: https://github.com/rust-lang/rust/issues/82775
496                 assert!(matches!(subject, Subject::IntegerRange(_)));
497             }
498         }
499 
500         #[test]
501         fn float_range() {
502             let subjects = [
503                 Subject::from(42.0_f64),
504                 Subject::from(ValueRangeConstraint::<f64>::default()),
505                 Subject::from(ResolvedValueRangeConstraint::<f64>::default()),
506             ];
507 
508             for subject in subjects {
509                 // TODO: replace with `assert_matches!(…)`, once stabilized:
510                 // Tracking issue: https://github.com/rust-lang/rust/issues/82775
511                 assert!(matches!(subject, Subject::FloatRange(_)));
512             }
513         }
514 
515         #[test]
516         fn string() {
517             let subjects = [
518                 Subject::from(""),
519                 Subject::from(String::new()),
520                 Subject::from(ValueConstraint::<String>::default()),
521                 Subject::from(ResolvedValueConstraint::<String>::default()),
522             ];
523 
524             for subject in subjects {
525                 // TODO: replace with `assert_matches!(…)`, once stabilized:
526                 // Tracking issue: https://github.com/rust-lang/rust/issues/82775
527                 assert!(matches!(subject, Subject::String(_)));
528             }
529         }
530 
531         #[test]
532         fn string_sequence() {
533             let subjects = [
534                 Subject::from(vec![""]),
535                 Subject::from(vec![String::new()]),
536                 Subject::from(ValueSequenceConstraint::<String>::default()),
537                 Subject::from(ResolvedValueSequenceConstraint::<String>::default()),
538             ];
539 
540             for subject in subjects {
541                 // TODO: replace with `assert_matches!(…)`, once stabilized:
542                 // Tracking issue: https://github.com/rust-lang/rust/issues/82775
543                 assert!(matches!(subject, Subject::StringSequence(_)));
544             }
545         }
546     }
547 
548     #[test]
549     fn is_empty() {
550         let empty_subject = Subject::Empty(EmptyConstraint {});
551 
552         assert!(empty_subject.is_empty());
553 
554         let non_empty_subjects = [
555             Subject::Bool(ValueConstraint::Bare(true)),
556             Subject::FloatRange(ValueRangeConstraint::Bare(42.0)),
557             Subject::IntegerRange(ValueRangeConstraint::Bare(42)),
558             Subject::String(ValueConstraint::Bare("string".to_owned())),
559             Subject::StringSequence(ValueSequenceConstraint::Bare(vec!["string".to_owned()])),
560         ];
561 
562         for non_empty_subject in non_empty_subjects {
563             assert!(!non_empty_subject.is_empty());
564         }
565     }
566 
567     #[test]
568     fn to_resolved() {
569         let subjects = [
570             (
571                 Subject::Empty(EmptyConstraint {}),
572                 ResolvedMediaTrackConstraint::Empty(EmptyConstraint {}),
573             ),
574             (
575                 Subject::Bool(ValueConstraint::Bare(true)),
576                 ResolvedMediaTrackConstraint::Bool(ResolvedValueConstraint::default().exact(true)),
577             ),
578             (
579                 Subject::FloatRange(ValueRangeConstraint::Bare(42.0)),
580                 ResolvedMediaTrackConstraint::FloatRange(
581                     ResolvedValueRangeConstraint::default().exact(42.0),
582                 ),
583             ),
584             (
585                 Subject::IntegerRange(ValueRangeConstraint::Bare(42)),
586                 ResolvedMediaTrackConstraint::IntegerRange(
587                     ResolvedValueRangeConstraint::default().exact(42),
588                 ),
589             ),
590             (
591                 Subject::String(ValueConstraint::Bare("string".to_owned())),
592                 ResolvedMediaTrackConstraint::String(
593                     ResolvedValueConstraint::default().exact("string".to_owned()),
594                 ),
595             ),
596             (
597                 Subject::StringSequence(ValueSequenceConstraint::Bare(vec!["string".to_owned()])),
598                 ResolvedMediaTrackConstraint::StringSequence(
599                     ResolvedValueSequenceConstraint::default().exact(vec!["string".to_owned()]),
600                 ),
601             ),
602         ];
603 
604         for (subject, expected) in subjects {
605             let actual = subject.to_resolved(BareToExact);
606 
607             assert_eq!(actual, expected);
608         }
609     }
610 
611     mod resolved {
612         use super::*;
613 
614         type Subject = ResolvedMediaTrackConstraint;
615 
616         #[test]
617         fn to_string() {
618             let scenarios = [
619                 (Subject::Empty(EmptyConstraint {}), "<empty>"),
620                 (
621                     Subject::Bool(ResolvedValueConstraint::default().exact(true)),
622                     "(x == true)",
623                 ),
624                 (
625                     Subject::FloatRange(ResolvedValueRangeConstraint::default().exact(42.0)),
626                     "(x == 42.0)",
627                 ),
628                 (
629                     Subject::IntegerRange(ResolvedValueRangeConstraint::default().exact(42)),
630                     "(x == 42)",
631                 ),
632                 (
633                     Subject::String(ResolvedValueConstraint::default().exact("string".to_owned())),
634                     "(x == \"string\")",
635                 ),
636                 (
637                     Subject::StringSequence(
638                         ResolvedValueSequenceConstraint::default().exact(vec!["string".to_owned()]),
639                     ),
640                     "(x == [\"string\"])",
641                 ),
642             ];
643 
644             for (subject, expected) in scenarios {
645                 let actual = subject.to_string();
646 
647                 assert_eq!(actual, expected);
648             }
649         }
650     }
651 }
652 
653 #[cfg(feature = "serde")]
654 #[cfg(test)]
655 mod serde_tests {
656     use crate::macros::test_serde_symmetry;
657 
658     use super::*;
659 
660     type Subject = MediaTrackConstraint;
661 
662     #[test]
663     fn empty() {
664         let subject = Subject::Empty(EmptyConstraint {});
665         let json = serde_json::json!({});
666 
667         test_serde_symmetry!(subject: subject, json: json);
668     }
669 
670     #[test]
671     fn bool_bare() {
672         let subject = Subject::Bool(true.into());
673         let json = serde_json::json!(true);
674 
675         test_serde_symmetry!(subject: subject, json: json);
676     }
677 
678     #[test]
679     fn bool_constraint() {
680         let subject = Subject::Bool(ResolvedValueConstraint::default().exact(true).into());
681         let json = serde_json::json!({ "exact": true });
682 
683         test_serde_symmetry!(subject: subject, json: json);
684     }
685 
686     #[test]
687     fn integer_range_bare() {
688         let subject = Subject::IntegerRange(42.into());
689         let json = serde_json::json!(42);
690 
691         test_serde_symmetry!(subject: subject, json: json);
692     }
693 
694     #[test]
695     fn integer_range_constraint() {
696         let subject =
697             Subject::IntegerRange(ResolvedValueRangeConstraint::default().exact(42).into());
698         let json = serde_json::json!({ "exact": 42 });
699 
700         test_serde_symmetry!(subject: subject, json: json);
701     }
702 
703     #[test]
704     fn float_range_bare() {
705         let subject = Subject::FloatRange(4.2.into());
706         let json = serde_json::json!(4.2);
707 
708         test_serde_symmetry!(subject: subject, json: json);
709     }
710 
711     #[test]
712     fn float_range_constraint() {
713         let subject =
714             Subject::FloatRange(ResolvedValueRangeConstraint::default().exact(42.0).into());
715         let json = serde_json::json!({ "exact": 42.0 });
716 
717         test_serde_symmetry!(subject: subject, json: json);
718     }
719 
720     #[test]
721     fn string_sequence_bare() {
722         let subject = Subject::StringSequence(vec!["foo".to_owned(), "bar".to_owned()].into());
723         let json = serde_json::json!(["foo", "bar"]);
724 
725         test_serde_symmetry!(subject: subject, json: json);
726     }
727 
728     #[test]
729     fn string_sequence_constraint() {
730         let subject = Subject::StringSequence(
731             ResolvedValueSequenceConstraint::default()
732                 .exact(vec!["foo".to_owned(), "bar".to_owned()])
733                 .into(),
734         );
735         let json = serde_json::json!({ "exact": ["foo", "bar"] });
736 
737         test_serde_symmetry!(subject: subject, json: json);
738     }
739 
740     #[test]
741     fn string_bare() {
742         let subject = Subject::String("foo".to_owned().into());
743         let json = serde_json::json!("foo");
744 
745         test_serde_symmetry!(subject: subject, json: json);
746     }
747 
748     #[test]
749     fn string_constraint() {
750         let subject = Subject::String(
751             ResolvedValueConstraint::default()
752                 .exact("foo".to_owned())
753                 .into(),
754         );
755         let json = serde_json::json!({ "exact": "foo" });
756 
757         test_serde_symmetry!(subject: subject, json: json);
758     }
759 }
760