xref: /webrtc/constraints/src/constraint.rs (revision 38f8002a)
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     /// 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 
80 impl Default for MediaTrackConstraint {
default() -> Self81     fn default() -> Self {
82         Self::Empty(EmptyConstraint {})
83     }
84 }
85 
86 // Bool constraint:
87 
88 impl From<bool> for MediaTrackConstraint {
from(bare: bool) -> Self89     fn from(bare: bool) -> Self {
90         Self::Bool(bare.into())
91     }
92 }
93 
94 impl From<ResolvedValueConstraint<bool>> for MediaTrackConstraint {
from(constraint: ResolvedValueConstraint<bool>) -> Self95     fn from(constraint: ResolvedValueConstraint<bool>) -> Self {
96         Self::Bool(constraint.into())
97     }
98 }
99 
100 impl From<ValueConstraint<bool>> for MediaTrackConstraint {
from(constraint: ValueConstraint<bool>) -> Self101     fn from(constraint: ValueConstraint<bool>) -> Self {
102         Self::Bool(constraint)
103     }
104 }
105 
106 // Unsigned integer range constraint:
107 
108 impl From<u64> for MediaTrackConstraint {
from(bare: u64) -> Self109     fn from(bare: u64) -> Self {
110         Self::IntegerRange(bare.into())
111     }
112 }
113 
114 impl From<ResolvedValueRangeConstraint<u64>> for MediaTrackConstraint {
from(constraint: ResolvedValueRangeConstraint<u64>) -> Self115     fn from(constraint: ResolvedValueRangeConstraint<u64>) -> Self {
116         Self::IntegerRange(constraint.into())
117     }
118 }
119 
120 impl From<ValueRangeConstraint<u64>> for MediaTrackConstraint {
from(constraint: ValueRangeConstraint<u64>) -> Self121     fn from(constraint: ValueRangeConstraint<u64>) -> Self {
122         Self::IntegerRange(constraint)
123     }
124 }
125 
126 // Floating-point range constraint:
127 
128 impl From<f64> for MediaTrackConstraint {
from(bare: f64) -> Self129     fn from(bare: f64) -> Self {
130         Self::FloatRange(bare.into())
131     }
132 }
133 
134 impl From<ResolvedValueRangeConstraint<f64>> for MediaTrackConstraint {
from(constraint: ResolvedValueRangeConstraint<f64>) -> Self135     fn from(constraint: ResolvedValueRangeConstraint<f64>) -> Self {
136         Self::FloatRange(constraint.into())
137     }
138 }
139 
140 impl From<ValueRangeConstraint<f64>> for MediaTrackConstraint {
from(constraint: ValueRangeConstraint<f64>) -> Self141     fn from(constraint: ValueRangeConstraint<f64>) -> Self {
142         Self::FloatRange(constraint)
143     }
144 }
145 
146 // String sequence constraint:
147 
148 impl From<Vec<String>> for MediaTrackConstraint {
from(bare: Vec<String>) -> Self149     fn from(bare: Vec<String>) -> Self {
150         Self::StringSequence(bare.into())
151     }
152 }
153 
154 impl From<Vec<&str>> for MediaTrackConstraint {
from(bare: Vec<&str>) -> Self155     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 
161 impl From<ResolvedValueSequenceConstraint<String>> for MediaTrackConstraint {
from(constraint: ResolvedValueSequenceConstraint<String>) -> Self162     fn from(constraint: ResolvedValueSequenceConstraint<String>) -> Self {
163         Self::StringSequence(constraint.into())
164     }
165 }
166 
167 impl From<ValueSequenceConstraint<String>> for MediaTrackConstraint {
from(constraint: ValueSequenceConstraint<String>) -> Self168     fn from(constraint: ValueSequenceConstraint<String>) -> Self {
169         Self::StringSequence(constraint)
170     }
171 }
172 
173 // String constraint:
174 
175 impl From<String> for MediaTrackConstraint {
from(bare: String) -> Self176     fn from(bare: String) -> Self {
177         Self::String(bare.into())
178     }
179 }
180 
181 impl<'a> From<&'a str> for MediaTrackConstraint {
from(bare: &'a str) -> Self182     fn from(bare: &'a str) -> Self {
183         let bare: String = bare.to_owned();
184         Self::from(bare)
185     }
186 }
187 
188 impl From<ResolvedValueConstraint<String>> for MediaTrackConstraint {
from(constraint: ResolvedValueConstraint<String>) -> Self189     fn from(constraint: ResolvedValueConstraint<String>) -> Self {
190         Self::String(constraint.into())
191     }
192 }
193 
194 impl From<ValueConstraint<String>> for MediaTrackConstraint {
from(constraint: ValueConstraint<String>) -> Self195     fn from(constraint: ValueConstraint<String>) -> Self {
196         Self::String(constraint)
197     }
198 }
199 
200 // Conversion from settings:
201 
202 impl From<MediaTrackSetting> for MediaTrackConstraint {
from(settings: MediaTrackSetting) -> Self203     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 
215 impl MediaTrackConstraint {
216     /// Returns `true` if `self` is empty, otherwise `false`.
is_empty(&self) -> bool217     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.
to_resolved( &self, strategy: MediaTrackConstraintResolutionStrategy, ) -> ResolvedMediaTrackConstraint230     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.
into_resolved( self, strategy: MediaTrackConstraintResolutionStrategy, ) -> ResolvedMediaTrackConstraint239     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))]
277 pub 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 
292 impl Default for ResolvedMediaTrackConstraint {
default() -> Self293     fn default() -> Self {
294         Self::Empty(EmptyConstraint {})
295     }
296 }
297 
298 impl std::fmt::Display for ResolvedMediaTrackConstraint {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result299     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 
313 impl From<ResolvedValueConstraint<bool>> for ResolvedMediaTrackConstraint {
from(constraint: ResolvedValueConstraint<bool>) -> Self314     fn from(constraint: ResolvedValueConstraint<bool>) -> Self {
315         Self::Bool(constraint)
316     }
317 }
318 
319 // Unsigned integer range constraint:
320 
321 impl From<ResolvedValueRangeConstraint<u64>> for ResolvedMediaTrackConstraint {
from(constraint: ResolvedValueRangeConstraint<u64>) -> Self322     fn from(constraint: ResolvedValueRangeConstraint<u64>) -> Self {
323         Self::IntegerRange(constraint)
324     }
325 }
326 
327 // Floating-point range constraint:
328 
329 impl From<ResolvedValueRangeConstraint<f64>> for ResolvedMediaTrackConstraint {
from(constraint: ResolvedValueRangeConstraint<f64>) -> Self330     fn from(constraint: ResolvedValueRangeConstraint<f64>) -> Self {
331         Self::FloatRange(constraint)
332     }
333 }
334 
335 // String sequence constraint:
336 
337 impl From<ResolvedValueSequenceConstraint<String>> for ResolvedMediaTrackConstraint {
from(constraint: ResolvedValueSequenceConstraint<String>) -> Self338     fn from(constraint: ResolvedValueSequenceConstraint<String>) -> Self {
339         Self::StringSequence(constraint)
340     }
341 }
342 
343 // String constraint:
344 
345 impl From<ResolvedValueConstraint<String>> for ResolvedMediaTrackConstraint {
from(constraint: ResolvedValueConstraint<String>) -> Self346     fn from(constraint: ResolvedValueConstraint<String>) -> Self {
347         Self::String(constraint)
348     }
349 }
350 
351 impl ResolvedMediaTrackConstraint {
352     /// Creates a resolved media track constraint by resolving
353     /// bare values to exact constraints: `{ exact: bare }`.
exact_from(setting: MediaTrackSetting) -> Self354     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 }`.
ideal_from(setting: MediaTrackSetting) -> Self361     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`.
is_required(&self) -> bool367     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`.
is_empty(&self) -> bool379     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.
to_required_only(&self) -> Self391     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.
into_required_only(self) -> Self397     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`.
to_sanitized(&self) -> Option<SanitizedMediaTrackConstraint>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`.
into_sanitized(self) -> Option<SanitizedMediaTrackConstraint>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)]
443 pub struct SanitizedMediaTrackConstraint(ResolvedMediaTrackConstraint);
444 
445 impl Deref for SanitizedMediaTrackConstraint {
446     type Target = ResolvedMediaTrackConstraint;
447 
deref(&self) -> &Self::Target448     fn deref(&self) -> &Self::Target {
449         &self.0
450     }
451 }
452 
453 impl SanitizedMediaTrackConstraint {
454     /// Consumes `self` returning its inner resolved constraint.
into_inner(self) -> ResolvedMediaTrackConstraint455     pub fn into_inner(self) -> ResolvedMediaTrackConstraint {
456         self.0
457     }
458 }
459 
460 #[cfg(test)]
461 mod tests {
462     use super::*;
463 
464     use MediaTrackConstraintResolutionStrategy::*;
465 
466     type Subject = MediaTrackConstraint;
467 
468     #[test]
default()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]
setting()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]
bool()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]
integer_range()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]
float_range()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]
string()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]
string_sequence()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]
is_empty()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]
to_resolved()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]
to_string()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)]
689 mod serde_tests {
690     use crate::macros::test_serde_symmetry;
691 
692     use super::*;
693 
694     type Subject = MediaTrackConstraint;
695 
696     #[test]
empty()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]
bool_bare()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]
bool_constraint()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]
integer_range_bare()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]
integer_range_constraint()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]
float_range_bare()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]
float_range_constraint()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]
string_sequence_bare()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]
string_sequence_constraint()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]
string_bare()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]
string_constraint()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 }
794