webrtc_constraints/constraint/
value_range.rs

1#[cfg(feature = "serde")]
2use serde::{Deserialize, Serialize};
3
4use crate::MediaTrackConstraintResolutionStrategy;
5
6/// A bare value or constraint specifying a range of accepted values.
7///
8/// # W3C Spec Compliance
9///
10/// There exists no direct corresponding type in the
11/// W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec,
12/// since the `ValueConstraint<T>` type aims to be a generalization over
13/// multiple types in the spec.
14///
15/// | Rust                               | W3C                                   |
16/// | ---------------------------------- | ------------------------------------- |
17/// | `ValueRangeConstraint<u64>` | [`ConstrainULong`][constrain_ulong]   |
18/// | `ValueRangeConstraint<f64>` | [`ConstrainDouble`][constrain_double] |
19///
20/// [constrain_double]: https://www.w3.org/TR/mediacapture-streams/#dom-constraindouble
21/// [constrain_ulong]: https://www.w3.org/TR/mediacapture-streams/#dom-constrainulong
22/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams/
23#[derive(Debug, Clone, Eq, PartialEq)]
24#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
25#[cfg_attr(feature = "serde", serde(untagged))]
26pub enum ValueRangeConstraint<T> {
27    /// A bare-valued media track constraint.
28    Bare(T),
29    /// A fully-qualified media track constraint.
30    Constraint(ResolvedValueRangeConstraint<T>),
31}
32
33impl<T> Default for ValueRangeConstraint<T> {
34    fn default() -> Self {
35        Self::Constraint(Default::default())
36    }
37}
38
39impl<T> From<T> for ValueRangeConstraint<T> {
40    fn from(bare: T) -> Self {
41        Self::Bare(bare)
42    }
43}
44
45impl<T> From<ResolvedValueRangeConstraint<T>> for ValueRangeConstraint<T> {
46    fn from(constraint: ResolvedValueRangeConstraint<T>) -> Self {
47        Self::Constraint(constraint)
48    }
49}
50
51impl<T> ValueRangeConstraint<T>
52where
53    T: Clone,
54{
55    /// Returns a resolved representation of the constraint
56    /// with bare values resolved to fully-qualified constraints.
57    pub fn to_resolved(
58        &self,
59        strategy: MediaTrackConstraintResolutionStrategy,
60    ) -> ResolvedValueRangeConstraint<T> {
61        self.clone().into_resolved(strategy)
62    }
63
64    /// Consumes the constraint, returning a resolved representation of the
65    /// constraint with bare values resolved to fully-qualified constraints.
66    pub fn into_resolved(
67        self,
68        strategy: MediaTrackConstraintResolutionStrategy,
69    ) -> ResolvedValueRangeConstraint<T> {
70        match self {
71            Self::Bare(bare) => match strategy {
72                MediaTrackConstraintResolutionStrategy::BareToIdeal => {
73                    ResolvedValueRangeConstraint::default().ideal(bare)
74                }
75                MediaTrackConstraintResolutionStrategy::BareToExact => {
76                    ResolvedValueRangeConstraint::default().exact(bare)
77                }
78            },
79            Self::Constraint(constraint) => constraint,
80        }
81    }
82}
83
84impl<T> ValueRangeConstraint<T> {
85    /// Returns `true` if `self` is empty, otherwise `false`.
86    pub fn is_empty(&self) -> bool {
87        match self {
88            Self::Bare(_) => false,
89            Self::Constraint(constraint) => constraint.is_empty(),
90        }
91    }
92}
93
94/// A constraint specifying a range of accepted values.
95///
96/// Corresponding W3C spec types as per ["Media Capture and Streams"][spec]:
97/// - `ConstrainDouble` => `ResolvedValueRangeConstraint<f64>`
98/// - `ConstrainULong` => `ResolvedValueRangeConstraint<u64>`
99///
100/// [spec]: https://www.w3.org/TR/mediacapture-streams
101#[derive(Debug, Clone, Eq, PartialEq)]
102#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
103#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
104pub struct ResolvedValueRangeConstraint<T> {
105    /// The minimum legal value of this property.
106    ///
107    /// This is a required value.
108    #[cfg_attr(
109        feature = "serde",
110        serde(skip_serializing_if = "core::option::Option::is_none")
111    )]
112    pub min: Option<T>,
113    /// The maximum legal value of this property.
114    ///
115    /// This is a required value.
116    #[cfg_attr(
117        feature = "serde",
118        serde(skip_serializing_if = "core::option::Option::is_none")
119    )]
120    pub max: Option<T>,
121    /// The exact required value for this property.
122    ///
123    /// This is a required value.
124    #[cfg_attr(
125        feature = "serde",
126        serde(skip_serializing_if = "core::option::Option::is_none")
127    )]
128    pub exact: Option<T>,
129    /// The ideal (target) value for this property.
130    ///
131    /// This is an optional value.
132    #[cfg_attr(
133        feature = "serde",
134        serde(skip_serializing_if = "core::option::Option::is_none")
135    )]
136    pub ideal: Option<T>,
137}
138
139impl<T> ResolvedValueRangeConstraint<T> {
140    /// Consumes `self`, returning a corresponding constraint
141    /// with the exact required value set to `exact`.
142    #[inline]
143    pub fn exact<U>(mut self, exact: U) -> Self
144    where
145        Option<T>: From<U>,
146    {
147        self.exact = exact.into();
148        self
149    }
150
151    /// Consumes `self`, returning a corresponding constraint
152    /// with the ideal required value set to `ideal`.
153    #[inline]
154    pub fn ideal<U>(mut self, ideal: U) -> Self
155    where
156        Option<T>: From<U>,
157    {
158        self.ideal = ideal.into();
159        self
160    }
161
162    /// Consumes `self`, returning a corresponding constraint
163    /// with the minimum required value set to `min`.
164    #[inline]
165    pub fn min<U>(mut self, min: U) -> Self
166    where
167        Option<T>: From<U>,
168    {
169        self.min = min.into();
170        self
171    }
172
173    /// Consumes `self`, returning a corresponding constraint
174    /// with the maximum required value set to `max`.
175    #[inline]
176    pub fn max<U>(mut self, max: U) -> Self
177    where
178        Option<T>: From<U>,
179    {
180        self.max = max.into();
181        self
182    }
183
184    /// Returns `true` if `value.is_some()` is `true` for any of its required values,
185    /// otherwise `false`.
186    pub fn is_required(&self) -> bool {
187        self.min.is_some() || self.max.is_some() || self.exact.is_some()
188    }
189
190    /// Returns `true` if `value.is_none()` is `true` for all of its values,
191    /// otherwise `false`.
192    pub fn is_empty(&self) -> bool {
193        self.min.is_none() && self.max.is_none() && self.exact.is_none() && self.ideal.is_none()
194    }
195
196    /// Returns a corresponding constraint containing only required values.
197    pub fn to_required_only(&self) -> Self
198    where
199        T: Clone,
200    {
201        self.clone().into_required_only()
202    }
203
204    /// Consumes `self, returning a corresponding constraint
205    /// containing only required values.
206    pub fn into_required_only(self) -> Self {
207        Self {
208            min: self.min,
209            max: self.max,
210            exact: self.exact,
211            ideal: None,
212        }
213    }
214}
215
216impl<T> Default for ResolvedValueRangeConstraint<T> {
217    #[inline]
218    fn default() -> Self {
219        Self {
220            min: None,
221            max: None,
222            exact: None,
223            ideal: None,
224        }
225    }
226}
227
228impl<T> std::fmt::Display for ResolvedValueRangeConstraint<T>
229where
230    T: std::fmt::Debug,
231{
232    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
233        let mut is_first = true;
234        f.write_str("(")?;
235        if let Some(exact) = &self.exact {
236            f.write_fmt(format_args!("x == {:?}", exact))?;
237            is_first = false;
238        } else if let (Some(min), Some(max)) = (&self.min, &self.max) {
239            f.write_fmt(format_args!("{:?} <= x <= {:?}", min, max))?;
240            is_first = false;
241        } else if let Some(min) = &self.min {
242            f.write_fmt(format_args!("{:?} <= x", min))?;
243            is_first = false;
244        } else if let Some(max) = &self.max {
245            f.write_fmt(format_args!("x <= {:?}", max))?;
246            is_first = false;
247        }
248        if let Some(ideal) = &self.ideal {
249            if !is_first {
250                f.write_str(" && ")?;
251            }
252            f.write_fmt(format_args!("x ~= {:?}", ideal))?;
253            is_first = false;
254        }
255        if is_first {
256            f.write_str("<empty>")?;
257        }
258        f.write_str(")")?;
259        Ok(())
260    }
261}
262
263#[cfg(test)]
264mod tests {
265    use super::*;
266
267    #[test]
268    fn to_string() {
269        let scenarios = [
270            (ResolvedValueRangeConstraint::default(), "(<empty>)"),
271            (ResolvedValueRangeConstraint::default().exact(1), "(x == 1)"),
272            (ResolvedValueRangeConstraint::default().ideal(2), "(x ~= 2)"),
273            (
274                ResolvedValueRangeConstraint::default().exact(1).ideal(2),
275                "(x == 1 && x ~= 2)",
276            ),
277        ];
278
279        for (constraint, expected) in scenarios {
280            let actual = constraint.to_string();
281
282            assert_eq!(actual, expected);
283        }
284    }
285
286    #[test]
287    fn is_required() {
288        for min_is_some in [false, true] {
289            // TODO: Replace `if { Some(_) } else { None }` with `.then_some(_)`
290            // once MSRV has passed 1.62.0:
291            let min = if min_is_some { Some(1) } else { None };
292            for max_is_some in [false, true] {
293                // TODO: Replace `if { Some(_) } else { None }` with `.then_some(_)`
294                // once MSRV has passed 1.62.0:
295                let max = if max_is_some { Some(2) } else { None };
296                for exact_is_some in [false, true] {
297                    // TODO: Replace `if { Some(_) } else { None }` with `.then_some(_)`
298                    // once MSRV has passed 1.62.0:
299                    let exact = if exact_is_some { Some(3) } else { None };
300                    for ideal_is_some in [false, true] {
301                        // TODO: Replace `if { Some(_) } else { None }` with `.then_some(_)`
302                        // once MSRV has passed 1.62.0:
303                        let ideal = if ideal_is_some { Some(4) } else { None };
304
305                        let constraint = ResolvedValueRangeConstraint::<u64> {
306                            min,
307                            max,
308                            exact,
309                            ideal,
310                        };
311
312                        let actual = constraint.is_required();
313                        let expected = min_is_some || max_is_some || exact_is_some;
314
315                        assert_eq!(actual, expected);
316                    }
317                }
318            }
319        }
320    }
321
322    mod is_empty {
323        use super::*;
324
325        #[test]
326        fn bare() {
327            let constraint = ValueRangeConstraint::Bare(42);
328
329            assert!(!constraint.is_empty());
330        }
331
332        #[test]
333        fn constraint() {
334            for min_is_some in [false, true] {
335                // TODO: Replace `if { Some(_) } else { None }` with `.then_some(_)`
336                // once MSRV has passed 1.62.0:
337                let min = if min_is_some { Some(1) } else { None };
338                for max_is_some in [false, true] {
339                    // TODO: Replace `if { Some(_) } else { None }` with `.then_some(_)`
340                    // once MSRV has passed 1.62.0:
341                    let max = if max_is_some { Some(2) } else { None };
342                    for exact_is_some in [false, true] {
343                        // TODO: Replace `if { Some(_) } else { None }` with `.then_some(_)`
344                        // once MSRV has passed 1.62.0:
345                        let exact = if exact_is_some { Some(3) } else { None };
346                        for ideal_is_some in [false, true] {
347                            // TODO: Replace `if { Some(_) } else { None }` with `.then_some(_)`
348                            // once MSRV has passed 1.62.0:
349                            let ideal = if ideal_is_some { Some(4) } else { None };
350
351                            let constraint = ResolvedValueRangeConstraint::<u64> {
352                                min,
353                                max,
354                                exact,
355                                ideal,
356                            };
357
358                            let actual = constraint.is_empty();
359                            let expected =
360                                !(min_is_some || max_is_some || exact_is_some || ideal_is_some);
361
362                            assert_eq!(actual, expected);
363                        }
364                    }
365                }
366            }
367        }
368    }
369}
370
371#[test]
372fn resolve_to_advanced() {
373    let constraints = [
374        ValueRangeConstraint::Bare(42),
375        ValueRangeConstraint::Constraint(ResolvedValueRangeConstraint::default().exact(42)),
376    ];
377    let strategy = MediaTrackConstraintResolutionStrategy::BareToExact;
378
379    for constraint in constraints {
380        let actuals = [
381            constraint.to_resolved(strategy),
382            constraint.into_resolved(strategy),
383        ];
384
385        let expected = ResolvedValueRangeConstraint::default().exact(42);
386
387        for actual in actuals {
388            assert_eq!(actual, expected);
389        }
390    }
391}
392
393#[test]
394fn resolve_to_basic() {
395    let constraints = [
396        ValueRangeConstraint::Bare(42),
397        ValueRangeConstraint::Constraint(ResolvedValueRangeConstraint::default().ideal(42)),
398    ];
399    let strategy = MediaTrackConstraintResolutionStrategy::BareToIdeal;
400
401    for constraint in constraints {
402        let actuals = [
403            constraint.to_resolved(strategy),
404            constraint.into_resolved(strategy),
405        ];
406
407        let expected = ResolvedValueRangeConstraint::default().ideal(42);
408
409        for actual in actuals {
410            assert_eq!(actual, expected);
411        }
412    }
413}
414
415#[cfg(feature = "serde")]
416#[cfg(test)]
417mod serde_tests {
418    use crate::macros::test_serde_symmetry;
419
420    use super::*;
421
422    macro_rules! test_serde {
423        ($t:ty => {
424            value: $value:expr
425        }) => {
426            type Subject = ValueRangeConstraint<$t>;
427
428            #[test]
429            fn default() {
430                let subject = Subject::default();
431                let json = serde_json::json!({});
432
433                test_serde_symmetry!(subject: subject, json: json);
434            }
435
436            #[test]
437            fn bare() {
438                let subject = Subject::Bare($value.to_owned());
439                let json = serde_json::json!($value);
440
441                test_serde_symmetry!(subject: subject, json: json);
442            }
443
444            #[test]
445            fn min_constraint() {
446                let subject = Subject::Constraint(ResolvedValueRangeConstraint::default().min($value.to_owned()));
447                let json = serde_json::json!({
448                    "min": $value,
449                });
450
451                test_serde_symmetry!(subject: subject, json: json);
452            }
453
454            #[test]
455            fn max_constraint() {
456                let subject = Subject::Constraint(ResolvedValueRangeConstraint::default().max($value.to_owned()));
457                let json = serde_json::json!({
458                    "max": $value,
459                });
460
461                test_serde_symmetry!(subject: subject, json: json);
462            }
463
464            #[test]
465            fn exact_constraint() {
466                let subject = Subject::Constraint(ResolvedValueRangeConstraint::default().exact($value.to_owned()));
467                let json = serde_json::json!({
468                    "exact": $value,
469                });
470
471                test_serde_symmetry!(subject: subject, json: json);
472            }
473
474            #[test]
475            fn ideal_constraint() {
476                let subject = Subject::Constraint(ResolvedValueRangeConstraint::default().ideal($value.to_owned()));
477                let json = serde_json::json!({
478                    "ideal": $value,
479                });
480
481                test_serde_symmetry!(subject: subject, json: json);
482            }
483
484            #[test]
485            fn full_constraint() {
486                let subject = Subject::Constraint(ResolvedValueRangeConstraint::default().min($value.to_owned()).max($value.to_owned()).exact($value.to_owned()).ideal($value.to_owned()));
487                let json = serde_json::json!({
488                    "min": $value,
489                    "max": $value,
490                    "exact": $value,
491                    "ideal": $value,
492                });
493
494                test_serde_symmetry!(subject: subject, json: json);
495            }
496        };
497    }
498
499    mod f64 {
500        use super::*;
501
502        test_serde!(f64 => {
503            value: 42.0
504        });
505    }
506
507    mod u64 {
508        use super::*;
509
510        test_serde!(u64 => {
511            value: 42
512        });
513    }
514}