webrtc_constraints/
constraint.rs

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