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