1 #[cfg(feature = "serde")]
2 use serde::{Deserialize, Serialize};
3
4 use 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))]
26 pub 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
33 impl<T> Default for ValueRangeConstraint<T> {
default() -> Self34 fn default() -> Self {
35 Self::Constraint(Default::default())
36 }
37 }
38
39 impl<T> From<T> for ValueRangeConstraint<T> {
from(bare: T) -> Self40 fn from(bare: T) -> Self {
41 Self::Bare(bare)
42 }
43 }
44
45 impl<T> From<ResolvedValueRangeConstraint<T>> for ValueRangeConstraint<T> {
from(constraint: ResolvedValueRangeConstraint<T>) -> Self46 fn from(constraint: ResolvedValueRangeConstraint<T>) -> Self {
47 Self::Constraint(constraint)
48 }
49 }
50
51 impl<T> ValueRangeConstraint<T>
52 where
53 T: Clone,
54 {
55 /// Returns a resolved representation of the constraint
56 /// with bare values resolved to fully-qualified constraints.
to_resolved( &self, strategy: MediaTrackConstraintResolutionStrategy, ) -> ResolvedValueRangeConstraint<T>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.
into_resolved( self, strategy: MediaTrackConstraintResolutionStrategy, ) -> ResolvedValueRangeConstraint<T>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
84 impl<T> ValueRangeConstraint<T> {
85 /// Returns `true` if `self` is empty, otherwise `false`.
is_empty(&self) -> bool86 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"))]
104 pub 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
139 impl<T> ResolvedValueRangeConstraint<T> {
140 /// Consumes `self`, returning a corresponding constraint
141 /// with the exact required value set to `exact`.
142 #[inline]
exact<U>(mut self, exact: U) -> Self where Option<T>: From<U>,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]
ideal<U>(mut self, ideal: U) -> Self where Option<T>: From<U>,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]
min<U>(mut self, min: U) -> Self where Option<T>: From<U>,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]
max<U>(mut self, max: U) -> Self where Option<T>: From<U>,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`.
is_required(&self) -> bool186 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`.
is_empty(&self) -> bool192 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.
to_required_only(&self) -> Self where T: Clone,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.
into_required_only(self) -> Self206 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
216 impl<T> Default for ResolvedValueRangeConstraint<T> {
217 #[inline]
default() -> Self218 fn default() -> Self {
219 Self {
220 min: None,
221 max: None,
222 exact: None,
223 ideal: None,
224 }
225 }
226 }
227
228 impl<T> std::fmt::Display for ResolvedValueRangeConstraint<T>
229 where
230 T: std::fmt::Debug,
231 {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result232 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!("{min:?} <= x <= {max:?}"))?;
240 is_first = false;
241 } else if let Some(min) = &self.min {
242 f.write_fmt(format_args!("{min:?} <= x"))?;
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)]
264 mod tests {
265 use super::*;
266
267 #[test]
to_string()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]
is_required()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]
bare()326 fn bare() {
327 let constraint = ValueRangeConstraint::Bare(42);
328
329 assert!(!constraint.is_empty());
330 }
331
332 #[test]
constraint()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]
resolve_to_advanced()372 fn 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]
resolve_to_basic()394 fn 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)]
417 mod 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 }
515