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> { 34 fn default() -> Self { 35 Self::Constraint(Default::default()) 36 } 37 } 38 39 impl<T> From<T> for ValueRangeConstraint<T> { 40 fn from(bare: T) -> Self { 41 Self::Bare(bare) 42 } 43 } 44 45 impl<T> From<ResolvedValueRangeConstraint<T>> for ValueRangeConstraint<T> { 46 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. 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 84 impl<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"))] 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] 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 216 impl<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 228 impl<T> std::fmt::Display for ResolvedValueRangeConstraint<T> 229 where 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)] 264 mod 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] 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] 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