webrtc_constraints/constraint/
value.rs

1#[cfg(feature = "serde")]
2use serde::{Deserialize, Serialize};
3
4use crate::MediaTrackConstraintResolutionStrategy;
5
6/// A bare value or constraint specifying a single accepted value.
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/// | `ValueConstraint<bool>` | [`ConstrainBoolean`][constrain_boolean] |
18///
19/// [constrain_boolean]: https://www.w3.org/TR/mediacapture-streams/#dom-constrainboolean
20/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams/
21#[derive(Debug, Clone, Eq, PartialEq)]
22#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
23#[cfg_attr(feature = "serde", serde(untagged))]
24pub enum ValueConstraint<T> {
25    /// A bare-valued media track constraint.
26    Bare(T),
27    /// A fully-qualified media track constraint.
28    Constraint(ResolvedValueConstraint<T>),
29}
30
31impl<T> Default for ValueConstraint<T> {
32    fn default() -> Self {
33        Self::Constraint(Default::default())
34    }
35}
36
37impl<T> From<T> for ValueConstraint<T> {
38    fn from(bare: T) -> Self {
39        Self::Bare(bare)
40    }
41}
42
43impl<T> From<ResolvedValueConstraint<T>> for ValueConstraint<T> {
44    fn from(constraint: ResolvedValueConstraint<T>) -> Self {
45        Self::Constraint(constraint)
46    }
47}
48
49impl<T> ValueConstraint<T>
50where
51    T: Clone,
52{
53    /// Returns a resolved representation of the constraint
54    /// with bare values resolved to fully-qualified constraints.
55    pub fn to_resolved(
56        &self,
57        strategy: MediaTrackConstraintResolutionStrategy,
58    ) -> ResolvedValueConstraint<T> {
59        self.clone().into_resolved(strategy)
60    }
61
62    /// Consumes the constraint, returning a resolved representation of the
63    /// constraint with bare values resolved to fully-qualified constraints.
64    pub fn into_resolved(
65        self,
66        strategy: MediaTrackConstraintResolutionStrategy,
67    ) -> ResolvedValueConstraint<T> {
68        match self {
69            Self::Bare(bare) => match strategy {
70                MediaTrackConstraintResolutionStrategy::BareToIdeal => {
71                    ResolvedValueConstraint::default().ideal(bare)
72                }
73                MediaTrackConstraintResolutionStrategy::BareToExact => {
74                    ResolvedValueConstraint::default().exact(bare)
75                }
76            },
77            Self::Constraint(constraint) => constraint,
78        }
79    }
80}
81
82impl<T> ValueConstraint<T> {
83    /// Returns `true` if `self` is empty, otherwise `false`.
84    pub fn is_empty(&self) -> bool {
85        match self {
86            Self::Bare(_) => false,
87            Self::Constraint(constraint) => constraint.is_empty(),
88        }
89    }
90}
91
92/// A constraint specifying a single accepted value.
93///
94/// # W3C Spec Compliance
95///
96/// There exists no direct corresponding type in the
97/// W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec,
98/// since the `ValueConstraint<T>` type aims to be a
99/// generalization over multiple types in the W3C spec:
100///
101/// | Rust                           | W3C                                     |
102/// | ------------------------------ | --------------------------------------- |
103/// | `ResolvedValueConstraint<bool>` | [`ConstrainBooleanParameters`][constrain_boolean_parameters] |
104///
105/// [constrain_boolean_parameters]: https://www.w3.org/TR/mediacapture-streams/#dom-constrainbooleanparameters
106/// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams/
107#[derive(Debug, Clone, Eq, PartialEq)]
108#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
109#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
110pub struct ResolvedValueConstraint<T> {
111    /// The exact required value for this property.
112    ///
113    /// This is a required value.
114    #[cfg_attr(
115        feature = "serde",
116        serde(skip_serializing_if = "core::option::Option::is_none")
117    )]
118    pub exact: Option<T>,
119    /// The ideal (target) value for this property.
120    ///
121    /// This is an optional value.
122    #[cfg_attr(
123        feature = "serde",
124        serde(skip_serializing_if = "core::option::Option::is_none")
125    )]
126    pub ideal: Option<T>,
127}
128
129impl<T> ResolvedValueConstraint<T> {
130    /// Consumes `self`, returning a corresponding constraint
131    /// with the exact required value set to `exact`.
132    #[inline]
133    pub fn exact<U>(mut self, exact: U) -> Self
134    where
135        Option<T>: From<U>,
136    {
137        self.exact = exact.into();
138        self
139    }
140
141    /// Consumes `self`, returning a corresponding constraint
142    /// with the ideal required value set to `ideal`.
143    #[inline]
144    pub fn ideal<U>(mut self, ideal: U) -> Self
145    where
146        Option<T>: From<U>,
147    {
148        self.ideal = ideal.into();
149        self
150    }
151
152    /// Returns `true` if `value.is_some()` is `true` for any of its required values,
153    /// otherwise `false`.
154    pub fn is_required(&self) -> bool {
155        self.exact.is_some()
156    }
157
158    /// Returns `true` if `value.is_none()` is `true` for all of its values,
159    /// otherwise `false`.
160    pub fn is_empty(&self) -> bool {
161        self.exact.is_none() && self.ideal.is_none()
162    }
163
164    /// Returns a corresponding constraint containing only required values.
165    pub fn to_required_only(&self) -> Self
166    where
167        T: Clone,
168    {
169        self.clone().into_required_only()
170    }
171
172    /// Consumes `self, returning a corresponding constraint
173    /// containing only required values.
174    pub fn into_required_only(self) -> Self {
175        Self {
176            exact: self.exact,
177            ideal: None,
178        }
179    }
180}
181
182impl<T> Default for ResolvedValueConstraint<T> {
183    #[inline]
184    fn default() -> Self {
185        Self {
186            exact: None,
187            ideal: None,
188        }
189    }
190}
191
192impl<T> std::fmt::Display for ResolvedValueConstraint<T>
193where
194    T: std::fmt::Debug,
195{
196    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197        let mut is_first = true;
198        f.write_str("(")?;
199        if let Some(ref exact) = &self.exact {
200            f.write_fmt(format_args!("x == {:?}", exact))?;
201            is_first = false;
202        }
203        if let Some(ref ideal) = &self.ideal {
204            if !is_first {
205                f.write_str(" && ")?;
206            }
207            f.write_fmt(format_args!("x ~= {:?}", ideal))?;
208            is_first = false;
209        }
210        if is_first {
211            f.write_str("<empty>")?;
212        }
213        f.write_str(")")?;
214        Ok(())
215    }
216}
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221
222    #[test]
223    fn to_string() {
224        let scenarios = [
225            (ResolvedValueConstraint::default(), "(<empty>)"),
226            (
227                ResolvedValueConstraint::default().exact(true),
228                "(x == true)",
229            ),
230            (
231                ResolvedValueConstraint::default().ideal(true),
232                "(x ~= true)",
233            ),
234            (
235                ResolvedValueConstraint::default().exact(true).ideal(true),
236                "(x == true && x ~= true)",
237            ),
238        ];
239
240        for (constraint, expected) in scenarios {
241            let actual = constraint.to_string();
242
243            assert_eq!(actual, expected);
244        }
245    }
246
247    #[test]
248    fn is_required() {
249        let scenarios = [
250            (ResolvedValueConstraint::default(), false),
251            (ResolvedValueConstraint::default().exact(true), true),
252            (ResolvedValueConstraint::default().ideal(true), false),
253            (
254                ResolvedValueConstraint::default().exact(true).ideal(true),
255                true,
256            ),
257        ];
258
259        for (constraint, expected) in scenarios {
260            let actual = constraint.is_required();
261
262            assert_eq!(actual, expected);
263        }
264    }
265
266    mod is_empty {
267        use super::*;
268
269        #[test]
270        fn bare() {
271            let constraint = ValueConstraint::Bare(true);
272
273            assert!(!constraint.is_empty());
274        }
275
276        #[test]
277        fn constraint() {
278            let scenarios = [
279                (ResolvedValueConstraint::default(), true),
280                (ResolvedValueConstraint::default().exact(true), false),
281                (ResolvedValueConstraint::default().ideal(true), false),
282                (
283                    ResolvedValueConstraint::default().exact(true).ideal(true),
284                    false,
285                ),
286            ];
287
288            for (constraint, expected) in scenarios {
289                let constraint = ValueConstraint::<bool>::Constraint(constraint);
290
291                let actual = constraint.is_empty();
292
293                assert_eq!(actual, expected);
294            }
295        }
296    }
297
298    #[test]
299    fn resolve_to_advanced() {
300        let constraints = [
301            ValueConstraint::Bare(true),
302            ValueConstraint::Constraint(ResolvedValueConstraint::default().exact(true)),
303        ];
304        let strategy = MediaTrackConstraintResolutionStrategy::BareToExact;
305
306        for constraint in constraints {
307            let actuals = [
308                constraint.to_resolved(strategy),
309                constraint.into_resolved(strategy),
310            ];
311
312            let expected = ResolvedValueConstraint::default().exact(true);
313
314            for actual in actuals {
315                assert_eq!(actual, expected);
316            }
317        }
318    }
319
320    #[test]
321    fn resolve_to_basic() {
322        let constraints = [
323            ValueConstraint::Bare(true),
324            ValueConstraint::Constraint(ResolvedValueConstraint::default().ideal(true)),
325        ];
326        let strategy = MediaTrackConstraintResolutionStrategy::BareToIdeal;
327
328        for constraint in constraints {
329            let actuals = [
330                constraint.to_resolved(strategy),
331                constraint.into_resolved(strategy),
332            ];
333
334            let expected = ResolvedValueConstraint::default().ideal(true);
335
336            for actual in actuals {
337                assert_eq!(actual, expected);
338            }
339        }
340    }
341}
342
343#[cfg(feature = "serde")]
344#[cfg(test)]
345mod serde_tests {
346    use crate::macros::test_serde_symmetry;
347
348    use super::*;
349
350    macro_rules! test_serde {
351        ($t:ty => {
352            value: $value:expr
353        }) => {
354            type Subject = ValueConstraint<$t>;
355
356            #[test]
357            fn default() {
358                let subject = Subject::default();
359                let json = serde_json::json!({});
360
361                test_serde_symmetry!(subject: subject, json: json);
362            }
363
364            #[test]
365            fn bare() {
366                let subject = Subject::Bare($value.to_owned());
367                let json = serde_json::json!($value);
368
369                test_serde_symmetry!(subject: subject, json: json);
370            }
371
372            #[test]
373            fn exact_constraint() {
374                let subject = Subject::Constraint(ResolvedValueConstraint::default().exact($value.to_owned()));
375                let json = serde_json::json!({
376                    "exact": $value,
377                });
378
379                test_serde_symmetry!(subject: subject, json: json);
380            }
381
382            #[test]
383            fn ideal_constraint() {
384                let subject = Subject::Constraint(ResolvedValueConstraint::default().ideal($value.to_owned()));
385                let json = serde_json::json!({
386                    "ideal": $value,
387                });
388
389                test_serde_symmetry!(subject: subject, json: json);
390            }
391
392            #[test]
393            fn full_constraint() {
394                let subject = Subject::Constraint(ResolvedValueConstraint::default().exact($value.to_owned()).ideal($value.to_owned()));
395                let json = serde_json::json!({
396                    "exact": $value,
397                    "ideal": $value,
398                });
399
400                test_serde_symmetry!(subject: subject, json: json);
401            }
402        };
403    }
404
405    mod bool {
406        use super::*;
407
408        test_serde!(bool => {
409            value: true
410        });
411    }
412
413    mod string {
414        use super::*;
415
416        test_serde!(String => {
417            value: "VALUE"
418        });
419    }
420}