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#[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#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
40pub enum MediaTrackConstraintResolutionStrategy {
41 BareToIdeal,
43 BareToExact,
45}
46
47#[derive(Debug, Clone, PartialEq)]
57#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
58#[cfg_attr(feature = "serde", serde(untagged))]
59pub enum MediaTrackConstraint {
60 Empty(EmptyConstraint),
62 IntegerRange(ValueRangeConstraint<u64>),
66 FloatRange(ValueRangeConstraint<f64>),
68 Bool(ValueConstraint<bool>),
72 StringSequence(ValueSequenceConstraint<String>),
76 String(ValueConstraint<String>),
78}
79
80impl Default for MediaTrackConstraint {
81 fn default() -> Self {
82 Self::Empty(EmptyConstraint {})
83 }
84}
85
86impl 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
106impl 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
126impl 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
146impl 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
173impl 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
200impl 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 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 pub fn to_resolved(
231 &self,
232 strategy: MediaTrackConstraintResolutionStrategy,
233 ) -> ResolvedMediaTrackConstraint {
234 self.clone().into_resolved(strategy)
235 }
236
237 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#[derive(Debug, Clone, PartialEq)]
275#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
276#[cfg_attr(feature = "serde", serde(untagged))]
277pub enum ResolvedMediaTrackConstraint {
278 Empty(EmptyConstraint),
280 IntegerRange(ResolvedValueRangeConstraint<u64>),
282 FloatRange(ResolvedValueRangeConstraint<f64>),
284 Bool(ResolvedValueConstraint<bool>),
286 StringSequence(ResolvedValueSequenceConstraint<String>),
288 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
311impl From<ResolvedValueConstraint<bool>> for ResolvedMediaTrackConstraint {
314 fn from(constraint: ResolvedValueConstraint<bool>) -> Self {
315 Self::Bool(constraint)
316 }
317}
318
319impl From<ResolvedValueRangeConstraint<u64>> for ResolvedMediaTrackConstraint {
322 fn from(constraint: ResolvedValueRangeConstraint<u64>) -> Self {
323 Self::IntegerRange(constraint)
324 }
325}
326
327impl From<ResolvedValueRangeConstraint<f64>> for ResolvedMediaTrackConstraint {
330 fn from(constraint: ResolvedValueRangeConstraint<f64>) -> Self {
331 Self::FloatRange(constraint)
332 }
333}
334
335impl From<ResolvedValueSequenceConstraint<String>> for ResolvedMediaTrackConstraint {
338 fn from(constraint: ResolvedValueSequenceConstraint<String>) -> Self {
339 Self::StringSequence(constraint)
340 }
341}
342
343impl From<ResolvedValueConstraint<String>> for ResolvedMediaTrackConstraint {
346 fn from(constraint: ResolvedValueConstraint<String>) -> Self {
347 Self::String(constraint)
348 }
349}
350
351impl ResolvedMediaTrackConstraint {
352 pub fn exact_from(setting: MediaTrackSetting) -> Self {
355 MediaTrackConstraint::from(setting)
356 .into_resolved(MediaTrackConstraintResolutionStrategy::BareToExact)
357 }
358
359 pub fn ideal_from(setting: MediaTrackSetting) -> Self {
362 MediaTrackConstraint::from(setting)
363 .into_resolved(MediaTrackConstraintResolutionStrategy::BareToIdeal)
364 }
365
366 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 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 pub fn to_required_only(&self) -> Self {
392 self.clone().into_required_only()
393 }
394
395 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 pub fn to_sanitized(&self) -> Option<SanitizedMediaTrackConstraint> {
413 self.clone().into_sanitized()
414 }
415
416 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#[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 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 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 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 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 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 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}