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