1 use std::ops::Deref; 2 3 #[cfg(feature = "serde")] 4 use serde::{Deserialize, Serialize}; 5 6 use crate::MediaTrackSetting; 7 8 pub use self::{ 9 value::{ResolvedValueConstraint, ValueConstraint}, 10 value_range::{ResolvedValueRangeConstraint, ValueRangeConstraint}, 11 value_sequence::{ResolvedValueSequenceConstraint, ValueSequenceConstraint}, 12 }; 13 14 mod value; 15 mod value_range; 16 mod value_sequence; 17 18 /// An empty [constraint][media_track_constraints] value for a [`MediaStreamTrack`][media_stream_track] object. 19 /// 20 /// # W3C Spec Compliance 21 /// 22 /// There exists no corresponding type in the W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec. 23 /// 24 /// The purpose of this type is to reduce parsing ambiguity, since all constraint variant types 25 /// support serializing from an empty map, but an empty map isn't typed, really, 26 /// so parsing to a specifically typed constraint would be wrong, type-wise. 27 /// 28 /// [media_stream_track]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamtrack 29 /// [media_track_constraints]: https://www.w3.org/TR/mediacapture-streams/#dom-mediatrackconstraints 30 /// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams 31 #[derive(Debug, Clone, Eq, PartialEq)] 32 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 33 #[cfg_attr(feature = "serde", serde(deny_unknown_fields))] 34 pub struct EmptyConstraint {} 35 36 /// The strategy of a track [constraint][constraint]. 37 /// 38 /// [constraint]: https://www.w3.org/TR/mediacapture-streams/#dfn-constraint 39 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] 40 pub enum MediaTrackConstraintResolutionStrategy { 41 /// Resolve bare values to `ideal` constraints. 42 BareToIdeal, 43 /// Resolve bare values to `exact` constraints. 44 BareToExact, 45 } 46 47 /// A single [constraint][media_track_constraints] value for a [`MediaStreamTrack`][media_stream_track] object. 48 /// 49 /// # W3C Spec Compliance 50 /// 51 /// There exists no corresponding type in the W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec. 52 /// 53 /// [media_stream_track]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamtrack 54 /// [media_track_constraints]: https://www.w3.org/TR/mediacapture-streams/#dom-mediatrackconstraints 55 /// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams 56 #[derive(Debug, Clone, PartialEq)] 57 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 58 #[cfg_attr(feature = "serde", serde(untagged))] 59 pub enum MediaTrackConstraint { 60 /// An empty constraint. 61 Empty(EmptyConstraint), 62 // `IntegerRange` must be ordered before `FloatRange(…)` in order for 63 // `serde` to decode the correct variant. 64 /// An integer-valued media track range constraint. 65 IntegerRange(ValueRangeConstraint<u64>), 66 /// An floating-point-valued media track range constraint. 67 FloatRange(ValueRangeConstraint<f64>), 68 // `Bool` must be ordered after `IntegerRange(…)`/`FloatRange(…)` in order for 69 // `serde` to decode the correct variant. 70 /// A single boolean-valued media track constraint. 71 Bool(ValueConstraint<bool>), 72 // `StringSequence` must be ordered before `String(…)` in order for 73 // `serde` to decode the correct variant. 74 /// A sequence of string-valued media track constraints. 75 StringSequence(ValueSequenceConstraint<String>), 76 /// A single string-valued media track constraint. 77 String(ValueConstraint<String>), 78 } 79 80 impl Default for MediaTrackConstraint { default() -> Self81 fn default() -> Self { 82 Self::Empty(EmptyConstraint {}) 83 } 84 } 85 86 // Bool constraint: 87 88 impl From<bool> for MediaTrackConstraint { from(bare: bool) -> Self89 fn from(bare: bool) -> Self { 90 Self::Bool(bare.into()) 91 } 92 } 93 94 impl From<ResolvedValueConstraint<bool>> for MediaTrackConstraint { from(constraint: ResolvedValueConstraint<bool>) -> Self95 fn from(constraint: ResolvedValueConstraint<bool>) -> Self { 96 Self::Bool(constraint.into()) 97 } 98 } 99 100 impl From<ValueConstraint<bool>> for MediaTrackConstraint { from(constraint: ValueConstraint<bool>) -> Self101 fn from(constraint: ValueConstraint<bool>) -> Self { 102 Self::Bool(constraint) 103 } 104 } 105 106 // Unsigned integer range constraint: 107 108 impl From<u64> for MediaTrackConstraint { from(bare: u64) -> Self109 fn from(bare: u64) -> Self { 110 Self::IntegerRange(bare.into()) 111 } 112 } 113 114 impl From<ResolvedValueRangeConstraint<u64>> for MediaTrackConstraint { from(constraint: ResolvedValueRangeConstraint<u64>) -> Self115 fn from(constraint: ResolvedValueRangeConstraint<u64>) -> Self { 116 Self::IntegerRange(constraint.into()) 117 } 118 } 119 120 impl From<ValueRangeConstraint<u64>> for MediaTrackConstraint { from(constraint: ValueRangeConstraint<u64>) -> Self121 fn from(constraint: ValueRangeConstraint<u64>) -> Self { 122 Self::IntegerRange(constraint) 123 } 124 } 125 126 // Floating-point range constraint: 127 128 impl From<f64> for MediaTrackConstraint { from(bare: f64) -> Self129 fn from(bare: f64) -> Self { 130 Self::FloatRange(bare.into()) 131 } 132 } 133 134 impl From<ResolvedValueRangeConstraint<f64>> for MediaTrackConstraint { from(constraint: ResolvedValueRangeConstraint<f64>) -> Self135 fn from(constraint: ResolvedValueRangeConstraint<f64>) -> Self { 136 Self::FloatRange(constraint.into()) 137 } 138 } 139 140 impl From<ValueRangeConstraint<f64>> for MediaTrackConstraint { from(constraint: ValueRangeConstraint<f64>) -> Self141 fn from(constraint: ValueRangeConstraint<f64>) -> Self { 142 Self::FloatRange(constraint) 143 } 144 } 145 146 // String sequence constraint: 147 148 impl From<Vec<String>> for MediaTrackConstraint { from(bare: Vec<String>) -> Self149 fn from(bare: Vec<String>) -> Self { 150 Self::StringSequence(bare.into()) 151 } 152 } 153 154 impl From<Vec<&str>> for MediaTrackConstraint { from(bare: Vec<&str>) -> Self155 fn from(bare: Vec<&str>) -> Self { 156 let bare: Vec<String> = bare.into_iter().map(|c| c.to_owned()).collect(); 157 Self::from(bare) 158 } 159 } 160 161 impl From<ResolvedValueSequenceConstraint<String>> for MediaTrackConstraint { from(constraint: ResolvedValueSequenceConstraint<String>) -> Self162 fn from(constraint: ResolvedValueSequenceConstraint<String>) -> Self { 163 Self::StringSequence(constraint.into()) 164 } 165 } 166 167 impl From<ValueSequenceConstraint<String>> for MediaTrackConstraint { from(constraint: ValueSequenceConstraint<String>) -> Self168 fn from(constraint: ValueSequenceConstraint<String>) -> Self { 169 Self::StringSequence(constraint) 170 } 171 } 172 173 // String constraint: 174 175 impl From<String> for MediaTrackConstraint { from(bare: String) -> Self176 fn from(bare: String) -> Self { 177 Self::String(bare.into()) 178 } 179 } 180 181 impl<'a> From<&'a str> for MediaTrackConstraint { from(bare: &'a str) -> Self182 fn from(bare: &'a str) -> Self { 183 let bare: String = bare.to_owned(); 184 Self::from(bare) 185 } 186 } 187 188 impl From<ResolvedValueConstraint<String>> for MediaTrackConstraint { from(constraint: ResolvedValueConstraint<String>) -> Self189 fn from(constraint: ResolvedValueConstraint<String>) -> Self { 190 Self::String(constraint.into()) 191 } 192 } 193 194 impl From<ValueConstraint<String>> for MediaTrackConstraint { from(constraint: ValueConstraint<String>) -> Self195 fn from(constraint: ValueConstraint<String>) -> Self { 196 Self::String(constraint) 197 } 198 } 199 200 // Conversion from settings: 201 202 impl From<MediaTrackSetting> for MediaTrackConstraint { from(settings: MediaTrackSetting) -> Self203 fn from(settings: MediaTrackSetting) -> Self { 204 match settings { 205 MediaTrackSetting::Bool(value) => Self::Bool(value.into()), 206 MediaTrackSetting::Integer(value) => { 207 Self::IntegerRange((value.clamp(0, i64::MAX) as u64).into()) 208 } 209 MediaTrackSetting::Float(value) => Self::FloatRange(value.into()), 210 MediaTrackSetting::String(value) => Self::String(value.into()), 211 } 212 } 213 } 214 215 impl MediaTrackConstraint { 216 /// Returns `true` if `self` is empty, otherwise `false`. is_empty(&self) -> bool217 pub fn is_empty(&self) -> bool { 218 match self { 219 Self::Empty(_) => true, 220 Self::IntegerRange(constraint) => constraint.is_empty(), 221 Self::FloatRange(constraint) => constraint.is_empty(), 222 Self::Bool(constraint) => constraint.is_empty(), 223 Self::StringSequence(constraint) => constraint.is_empty(), 224 Self::String(constraint) => constraint.is_empty(), 225 } 226 } 227 228 /// Returns a resolved representation of the constraint 229 /// with bare values resolved to fully-qualified constraints. to_resolved( &self, strategy: MediaTrackConstraintResolutionStrategy, ) -> ResolvedMediaTrackConstraint230 pub fn to_resolved( 231 &self, 232 strategy: MediaTrackConstraintResolutionStrategy, 233 ) -> ResolvedMediaTrackConstraint { 234 self.clone().into_resolved(strategy) 235 } 236 237 /// Consumes the constraint, returning a resolved representation of the 238 /// constraint with bare values resolved to fully-qualified constraints. into_resolved( self, strategy: MediaTrackConstraintResolutionStrategy, ) -> ResolvedMediaTrackConstraint239 pub fn into_resolved( 240 self, 241 strategy: MediaTrackConstraintResolutionStrategy, 242 ) -> ResolvedMediaTrackConstraint { 243 match self { 244 Self::Empty(constraint) => ResolvedMediaTrackConstraint::Empty(constraint), 245 Self::IntegerRange(constraint) => { 246 ResolvedMediaTrackConstraint::IntegerRange(constraint.into_resolved(strategy)) 247 } 248 Self::FloatRange(constraint) => { 249 ResolvedMediaTrackConstraint::FloatRange(constraint.into_resolved(strategy)) 250 } 251 Self::Bool(constraint) => { 252 ResolvedMediaTrackConstraint::Bool(constraint.into_resolved(strategy)) 253 } 254 Self::StringSequence(constraint) => { 255 ResolvedMediaTrackConstraint::StringSequence(constraint.into_resolved(strategy)) 256 } 257 Self::String(constraint) => { 258 ResolvedMediaTrackConstraint::String(constraint.into_resolved(strategy)) 259 } 260 } 261 } 262 } 263 264 /// A single [constraint][media_track_constraints] value for a [`MediaStreamTrack`][media_stream_track] object 265 /// with its potential bare value either resolved to an `exact` or `ideal` constraint. 266 /// 267 /// # W3C Spec Compliance 268 /// 269 /// There exists no corresponding type in the W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec. 270 /// 271 /// [media_stream_track]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamtrack 272 /// [media_track_constraints]: https://www.w3.org/TR/mediacapture-streams/#dom-mediatrackconstraints 273 /// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams 274 #[derive(Debug, Clone, PartialEq)] 275 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 276 #[cfg_attr(feature = "serde", serde(untagged))] 277 pub enum ResolvedMediaTrackConstraint { 278 /// An empty constraint. 279 Empty(EmptyConstraint), 280 /// An integer-valued media track range constraint. 281 IntegerRange(ResolvedValueRangeConstraint<u64>), 282 /// An floating-point-valued media track range constraint. 283 FloatRange(ResolvedValueRangeConstraint<f64>), 284 /// A single boolean-valued media track constraint. 285 Bool(ResolvedValueConstraint<bool>), 286 /// A sequence of string-valued media track constraints. 287 StringSequence(ResolvedValueSequenceConstraint<String>), 288 /// A single string-valued media track constraint. 289 String(ResolvedValueConstraint<String>), 290 } 291 292 impl Default for ResolvedMediaTrackConstraint { default() -> Self293 fn default() -> Self { 294 Self::Empty(EmptyConstraint {}) 295 } 296 } 297 298 impl std::fmt::Display for ResolvedMediaTrackConstraint { fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result299 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 300 match self { 301 Self::Empty(_constraint) => "<empty>".fmt(f), 302 Self::IntegerRange(constraint) => constraint.fmt(f), 303 Self::FloatRange(constraint) => constraint.fmt(f), 304 Self::Bool(constraint) => constraint.fmt(f), 305 Self::StringSequence(constraint) => constraint.fmt(f), 306 Self::String(constraint) => constraint.fmt(f), 307 } 308 } 309 } 310 311 // Bool constraint: 312 313 impl From<ResolvedValueConstraint<bool>> for ResolvedMediaTrackConstraint { from(constraint: ResolvedValueConstraint<bool>) -> Self314 fn from(constraint: ResolvedValueConstraint<bool>) -> Self { 315 Self::Bool(constraint) 316 } 317 } 318 319 // Unsigned integer range constraint: 320 321 impl From<ResolvedValueRangeConstraint<u64>> for ResolvedMediaTrackConstraint { from(constraint: ResolvedValueRangeConstraint<u64>) -> Self322 fn from(constraint: ResolvedValueRangeConstraint<u64>) -> Self { 323 Self::IntegerRange(constraint) 324 } 325 } 326 327 // Floating-point range constraint: 328 329 impl From<ResolvedValueRangeConstraint<f64>> for ResolvedMediaTrackConstraint { from(constraint: ResolvedValueRangeConstraint<f64>) -> Self330 fn from(constraint: ResolvedValueRangeConstraint<f64>) -> Self { 331 Self::FloatRange(constraint) 332 } 333 } 334 335 // String sequence constraint: 336 337 impl From<ResolvedValueSequenceConstraint<String>> for ResolvedMediaTrackConstraint { from(constraint: ResolvedValueSequenceConstraint<String>) -> Self338 fn from(constraint: ResolvedValueSequenceConstraint<String>) -> Self { 339 Self::StringSequence(constraint) 340 } 341 } 342 343 // String constraint: 344 345 impl From<ResolvedValueConstraint<String>> for ResolvedMediaTrackConstraint { from(constraint: ResolvedValueConstraint<String>) -> Self346 fn from(constraint: ResolvedValueConstraint<String>) -> Self { 347 Self::String(constraint) 348 } 349 } 350 351 impl ResolvedMediaTrackConstraint { 352 /// Creates a resolved media track constraint by resolving 353 /// bare values to exact constraints: `{ exact: bare }`. exact_from(setting: MediaTrackSetting) -> Self354 pub fn exact_from(setting: MediaTrackSetting) -> Self { 355 MediaTrackConstraint::from(setting) 356 .into_resolved(MediaTrackConstraintResolutionStrategy::BareToExact) 357 } 358 359 /// Creates a resolved media track constraint by resolving 360 /// bare values to ideal constraints: `{ ideal: bare }`. ideal_from(setting: MediaTrackSetting) -> Self361 pub fn ideal_from(setting: MediaTrackSetting) -> Self { 362 MediaTrackConstraint::from(setting) 363 .into_resolved(MediaTrackConstraintResolutionStrategy::BareToIdeal) 364 } 365 366 /// Returns `true` if `self` is required, otherwise `false`. is_required(&self) -> bool367 pub fn is_required(&self) -> bool { 368 match self { 369 Self::Empty(_constraint) => false, 370 Self::IntegerRange(constraint) => constraint.is_required(), 371 Self::FloatRange(constraint) => constraint.is_required(), 372 Self::Bool(constraint) => constraint.is_required(), 373 Self::StringSequence(constraint) => constraint.is_required(), 374 Self::String(constraint) => constraint.is_required(), 375 } 376 } 377 378 /// Returns `true` if `self` is empty, otherwise `false`. is_empty(&self) -> bool379 pub fn is_empty(&self) -> bool { 380 match self { 381 Self::Empty(_constraint) => true, 382 Self::IntegerRange(constraint) => constraint.is_empty(), 383 Self::FloatRange(constraint) => constraint.is_empty(), 384 Self::Bool(constraint) => constraint.is_empty(), 385 Self::StringSequence(constraint) => constraint.is_empty(), 386 Self::String(constraint) => constraint.is_empty(), 387 } 388 } 389 390 /// Returns a corresponding constraint containing only required values. to_required_only(&self) -> Self391 pub fn to_required_only(&self) -> Self { 392 self.clone().into_required_only() 393 } 394 395 /// Consumes `self, returning a corresponding constraint 396 /// containing only required values. into_required_only(self) -> Self397 pub fn into_required_only(self) -> Self { 398 match self { 399 Self::Empty(constraint) => Self::Empty(constraint), 400 Self::IntegerRange(constraint) => Self::IntegerRange(constraint.into_required_only()), 401 Self::FloatRange(constraint) => Self::FloatRange(constraint.into_required_only()), 402 Self::Bool(constraint) => Self::Bool(constraint.into_required_only()), 403 Self::StringSequence(constraint) => { 404 Self::StringSequence(constraint.into_required_only()) 405 } 406 Self::String(constraint) => Self::String(constraint.into_required_only()), 407 } 408 } 409 410 /// Returns a corresponding sanitized constraint 411 /// if `self` is non-empty, otherwise `None`. to_sanitized(&self) -> Option<SanitizedMediaTrackConstraint>412 pub fn to_sanitized(&self) -> Option<SanitizedMediaTrackConstraint> { 413 self.clone().into_sanitized() 414 } 415 416 /// Consumes `self`, returning a corresponding sanitized constraint 417 /// if `self` is non-empty, otherwise `None`. into_sanitized(self) -> Option<SanitizedMediaTrackConstraint>418 pub fn into_sanitized(self) -> Option<SanitizedMediaTrackConstraint> { 419 if self.is_empty() { 420 return None; 421 } 422 423 Some(SanitizedMediaTrackConstraint(self)) 424 } 425 } 426 427 /// A single non-empty [constraint][media_track_constraints] value for a [`MediaStreamTrack`][media_stream_track] object. 428 /// 429 /// # Invariant 430 /// 431 /// The wrapped `ResolvedMediaTrackConstraint` MUST not be empty. 432 /// 433 /// To enforce this invariant the only way to create an instance of this type 434 /// is by calling `constraint.to_sanitized()`/`constraint.into_sanitized()` on 435 /// an instance of `ResolvedMediaTrackConstraint`, which returns `None` if `self` is empty. 436 /// 437 /// Further more `self.0` MUST NOT be exposed mutably, 438 /// as otherwise it could become empty via mutation. 439 /// 440 /// [media_stream_track]: https://www.w3.org/TR/mediacapture-streams/#dom-mediastreamtrack 441 /// [media_track_constraints]: https://www.w3.org/TR/mediacapture-streams/#dom-mediatrackconstraints 442 #[derive(Debug, Clone, PartialEq)] 443 pub struct SanitizedMediaTrackConstraint(ResolvedMediaTrackConstraint); 444 445 impl Deref for SanitizedMediaTrackConstraint { 446 type Target = ResolvedMediaTrackConstraint; 447 deref(&self) -> &Self::Target448 fn deref(&self) -> &Self::Target { 449 &self.0 450 } 451 } 452 453 impl SanitizedMediaTrackConstraint { 454 /// Consumes `self` returning its inner resolved constraint. into_inner(self) -> ResolvedMediaTrackConstraint455 pub fn into_inner(self) -> ResolvedMediaTrackConstraint { 456 self.0 457 } 458 } 459 460 #[cfg(test)] 461 mod tests { 462 use super::*; 463 464 use MediaTrackConstraintResolutionStrategy::*; 465 466 type Subject = MediaTrackConstraint; 467 468 #[test] default()469 fn default() { 470 let subject = Subject::default(); 471 472 let actual = subject.is_empty(); 473 let expected = true; 474 475 assert_eq!(actual, expected); 476 } 477 478 mod from { 479 480 use super::*; 481 482 #[test] setting()483 fn setting() { 484 use crate::MediaTrackSetting; 485 486 assert!(matches!( 487 Subject::from(MediaTrackSetting::Bool(true)), 488 Subject::Bool(ValueConstraint::Bare(_)) 489 )); 490 assert!(matches!( 491 Subject::from(MediaTrackSetting::Integer(42)), 492 Subject::IntegerRange(ValueRangeConstraint::Bare(_)) 493 )); 494 assert!(matches!( 495 Subject::from(MediaTrackSetting::Float(4.2)), 496 Subject::FloatRange(ValueRangeConstraint::Bare(_)) 497 )); 498 assert!(matches!( 499 Subject::from(MediaTrackSetting::String("string".to_owned())), 500 Subject::String(ValueConstraint::Bare(_)) 501 )); 502 } 503 504 #[test] bool()505 fn bool() { 506 let subjects = [ 507 Subject::from(false), 508 Subject::from(ValueConstraint::<bool>::default()), 509 Subject::from(ResolvedValueConstraint::<bool>::default()), 510 ]; 511 512 for subject in subjects { 513 // TODO: replace with `assert_matches!(…)`, once stabilized: 514 // Tracking issue: https://github.com/rust-lang/rust/issues/82775 515 assert!(matches!(subject, Subject::Bool(_))); 516 } 517 } 518 519 #[test] integer_range()520 fn integer_range() { 521 let subjects = [ 522 Subject::from(42_u64), 523 Subject::from(ValueRangeConstraint::<u64>::default()), 524 Subject::from(ResolvedValueRangeConstraint::<u64>::default()), 525 ]; 526 527 for subject in subjects { 528 // TODO: replace with `assert_matches!(…)`, once stabilized: 529 // Tracking issue: https://github.com/rust-lang/rust/issues/82775 530 assert!(matches!(subject, Subject::IntegerRange(_))); 531 } 532 } 533 534 #[test] float_range()535 fn float_range() { 536 let subjects = [ 537 Subject::from(42.0_f64), 538 Subject::from(ValueRangeConstraint::<f64>::default()), 539 Subject::from(ResolvedValueRangeConstraint::<f64>::default()), 540 ]; 541 542 for subject in subjects { 543 // TODO: replace with `assert_matches!(…)`, once stabilized: 544 // Tracking issue: https://github.com/rust-lang/rust/issues/82775 545 assert!(matches!(subject, Subject::FloatRange(_))); 546 } 547 } 548 549 #[test] string()550 fn string() { 551 let subjects = [ 552 Subject::from(""), 553 Subject::from(String::new()), 554 Subject::from(ValueConstraint::<String>::default()), 555 Subject::from(ResolvedValueConstraint::<String>::default()), 556 ]; 557 558 for subject in subjects { 559 // TODO: replace with `assert_matches!(…)`, once stabilized: 560 // Tracking issue: https://github.com/rust-lang/rust/issues/82775 561 assert!(matches!(subject, Subject::String(_))); 562 } 563 } 564 565 #[test] string_sequence()566 fn string_sequence() { 567 let subjects = [ 568 Subject::from(vec![""]), 569 Subject::from(vec![String::new()]), 570 Subject::from(ValueSequenceConstraint::<String>::default()), 571 Subject::from(ResolvedValueSequenceConstraint::<String>::default()), 572 ]; 573 574 for subject in subjects { 575 // TODO: replace with `assert_matches!(…)`, once stabilized: 576 // Tracking issue: https://github.com/rust-lang/rust/issues/82775 577 assert!(matches!(subject, Subject::StringSequence(_))); 578 } 579 } 580 } 581 582 #[test] is_empty()583 fn is_empty() { 584 let empty_subject = Subject::Empty(EmptyConstraint {}); 585 586 assert!(empty_subject.is_empty()); 587 588 let non_empty_subjects = [ 589 Subject::Bool(ValueConstraint::Bare(true)), 590 Subject::FloatRange(ValueRangeConstraint::Bare(42.0)), 591 Subject::IntegerRange(ValueRangeConstraint::Bare(42)), 592 Subject::String(ValueConstraint::Bare("string".to_owned())), 593 Subject::StringSequence(ValueSequenceConstraint::Bare(vec!["string".to_owned()])), 594 ]; 595 596 for non_empty_subject in non_empty_subjects { 597 assert!(!non_empty_subject.is_empty()); 598 } 599 } 600 601 #[test] to_resolved()602 fn to_resolved() { 603 let subjects = [ 604 ( 605 Subject::Empty(EmptyConstraint {}), 606 ResolvedMediaTrackConstraint::Empty(EmptyConstraint {}), 607 ), 608 ( 609 Subject::Bool(ValueConstraint::Bare(true)), 610 ResolvedMediaTrackConstraint::Bool(ResolvedValueConstraint::default().exact(true)), 611 ), 612 ( 613 Subject::FloatRange(ValueRangeConstraint::Bare(42.0)), 614 ResolvedMediaTrackConstraint::FloatRange( 615 ResolvedValueRangeConstraint::default().exact(42.0), 616 ), 617 ), 618 ( 619 Subject::IntegerRange(ValueRangeConstraint::Bare(42)), 620 ResolvedMediaTrackConstraint::IntegerRange( 621 ResolvedValueRangeConstraint::default().exact(42), 622 ), 623 ), 624 ( 625 Subject::String(ValueConstraint::Bare("string".to_owned())), 626 ResolvedMediaTrackConstraint::String( 627 ResolvedValueConstraint::default().exact("string".to_owned()), 628 ), 629 ), 630 ( 631 Subject::StringSequence(ValueSequenceConstraint::Bare(vec!["string".to_owned()])), 632 ResolvedMediaTrackConstraint::StringSequence( 633 ResolvedValueSequenceConstraint::default().exact(vec!["string".to_owned()]), 634 ), 635 ), 636 ]; 637 638 for (subject, expected) in subjects { 639 let actual = subject.to_resolved(BareToExact); 640 641 assert_eq!(actual, expected); 642 } 643 } 644 645 mod resolved { 646 use super::*; 647 648 type Subject = ResolvedMediaTrackConstraint; 649 650 #[test] to_string()651 fn to_string() { 652 let scenarios = [ 653 (Subject::Empty(EmptyConstraint {}), "<empty>"), 654 ( 655 Subject::Bool(ResolvedValueConstraint::default().exact(true)), 656 "(x == true)", 657 ), 658 ( 659 Subject::FloatRange(ResolvedValueRangeConstraint::default().exact(42.0)), 660 "(x == 42.0)", 661 ), 662 ( 663 Subject::IntegerRange(ResolvedValueRangeConstraint::default().exact(42)), 664 "(x == 42)", 665 ), 666 ( 667 Subject::String(ResolvedValueConstraint::default().exact("string".to_owned())), 668 "(x == \"string\")", 669 ), 670 ( 671 Subject::StringSequence( 672 ResolvedValueSequenceConstraint::default().exact(vec!["string".to_owned()]), 673 ), 674 "(x == [\"string\"])", 675 ), 676 ]; 677 678 for (subject, expected) in scenarios { 679 let actual = subject.to_string(); 680 681 assert_eq!(actual, expected); 682 } 683 } 684 } 685 } 686 687 #[cfg(feature = "serde")] 688 #[cfg(test)] 689 mod serde_tests { 690 use crate::macros::test_serde_symmetry; 691 692 use super::*; 693 694 type Subject = MediaTrackConstraint; 695 696 #[test] empty()697 fn empty() { 698 let subject = Subject::Empty(EmptyConstraint {}); 699 let json = serde_json::json!({}); 700 701 test_serde_symmetry!(subject: subject, json: json); 702 } 703 704 #[test] bool_bare()705 fn bool_bare() { 706 let subject = Subject::Bool(true.into()); 707 let json = serde_json::json!(true); 708 709 test_serde_symmetry!(subject: subject, json: json); 710 } 711 712 #[test] bool_constraint()713 fn bool_constraint() { 714 let subject = Subject::Bool(ResolvedValueConstraint::default().exact(true).into()); 715 let json = serde_json::json!({ "exact": true }); 716 717 test_serde_symmetry!(subject: subject, json: json); 718 } 719 720 #[test] integer_range_bare()721 fn integer_range_bare() { 722 let subject = Subject::IntegerRange(42.into()); 723 let json = serde_json::json!(42); 724 725 test_serde_symmetry!(subject: subject, json: json); 726 } 727 728 #[test] integer_range_constraint()729 fn integer_range_constraint() { 730 let subject = 731 Subject::IntegerRange(ResolvedValueRangeConstraint::default().exact(42).into()); 732 let json = serde_json::json!({ "exact": 42 }); 733 734 test_serde_symmetry!(subject: subject, json: json); 735 } 736 737 #[test] float_range_bare()738 fn float_range_bare() { 739 let subject = Subject::FloatRange(4.2.into()); 740 let json = serde_json::json!(4.2); 741 742 test_serde_symmetry!(subject: subject, json: json); 743 } 744 745 #[test] float_range_constraint()746 fn float_range_constraint() { 747 let subject = 748 Subject::FloatRange(ResolvedValueRangeConstraint::default().exact(42.0).into()); 749 let json = serde_json::json!({ "exact": 42.0 }); 750 751 test_serde_symmetry!(subject: subject, json: json); 752 } 753 754 #[test] string_sequence_bare()755 fn string_sequence_bare() { 756 let subject = Subject::StringSequence(vec!["foo".to_owned(), "bar".to_owned()].into()); 757 let json = serde_json::json!(["foo", "bar"]); 758 759 test_serde_symmetry!(subject: subject, json: json); 760 } 761 762 #[test] string_sequence_constraint()763 fn string_sequence_constraint() { 764 let subject = Subject::StringSequence( 765 ResolvedValueSequenceConstraint::default() 766 .exact(vec!["foo".to_owned(), "bar".to_owned()]) 767 .into(), 768 ); 769 let json = serde_json::json!({ "exact": ["foo", "bar"] }); 770 771 test_serde_symmetry!(subject: subject, json: json); 772 } 773 774 #[test] string_bare()775 fn string_bare() { 776 let subject = Subject::String("foo".to_owned().into()); 777 let json = serde_json::json!("foo"); 778 779 test_serde_symmetry!(subject: subject, json: json); 780 } 781 782 #[test] string_constraint()783 fn string_constraint() { 784 let subject = Subject::String( 785 ResolvedValueConstraint::default() 786 .exact("foo".to_owned()) 787 .into(), 788 ); 789 let json = serde_json::json!({ "exact": "foo" }); 790 791 test_serde_symmetry!(subject: subject, json: json); 792 } 793 } 794