webrtc_constraints/constraint/
value_range.rs1#[cfg(feature = "serde")]
2use serde::{Deserialize, Serialize};
3
4use crate::MediaTrackConstraintResolutionStrategy;
5
6#[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 Bare(T),
29 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 pub fn to_resolved(
58 &self,
59 strategy: MediaTrackConstraintResolutionStrategy,
60 ) -> ResolvedValueRangeConstraint<T> {
61 self.clone().into_resolved(strategy)
62 }
63
64 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 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#[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 #[cfg_attr(
109 feature = "serde",
110 serde(skip_serializing_if = "core::option::Option::is_none")
111 )]
112 pub min: Option<T>,
113 #[cfg_attr(
117 feature = "serde",
118 serde(skip_serializing_if = "core::option::Option::is_none")
119 )]
120 pub max: Option<T>,
121 #[cfg_attr(
125 feature = "serde",
126 serde(skip_serializing_if = "core::option::Option::is_none")
127 )]
128 pub exact: Option<T>,
129 #[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 #[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 #[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 #[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 #[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 pub fn is_required(&self) -> bool {
187 self.min.is_some() || self.max.is_some() || self.exact.is_some()
188 }
189
190 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 pub fn to_required_only(&self) -> Self
198 where
199 T: Clone,
200 {
201 self.clone().into_required_only()
202 }
203
204 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 let min = if min_is_some { Some(1) } else { None };
292 for max_is_some in [false, true] {
293 let max = if max_is_some { Some(2) } else { None };
296 for exact_is_some in [false, true] {
297 let exact = if exact_is_some { Some(3) } else { None };
300 for ideal_is_some in [false, true] {
301 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 let min = if min_is_some { Some(1) } else { None };
338 for max_is_some in [false, true] {
339 let max = if max_is_some { Some(2) } else { None };
342 for exact_is_some in [false, true] {
343 let exact = if exact_is_some { Some(3) } else { None };
346 for ideal_is_some in [false, true] {
347 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}