1 #[cfg(feature = "serde")] 2 use serde::{Deserialize, Serialize}; 3 4 use crate::MediaTrackConstraintResolutionStrategy; 5 6 /// A bare value or constraint specifying a single accepted value. 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 /// | `ValueConstraint<bool>` | [`ConstrainBoolean`][constrain_boolean] | 18 /// 19 /// [constrain_boolean]: https://www.w3.org/TR/mediacapture-streams/#dom-constrainboolean 20 /// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams/ 21 #[derive(Debug, Clone, Eq, PartialEq)] 22 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 23 #[cfg_attr(feature = "serde", serde(untagged))] 24 pub enum ValueConstraint<T> { 25 /// A bare-valued media track constraint. 26 Bare(T), 27 /// A fully-qualified media track constraint. 28 Constraint(ResolvedValueConstraint<T>), 29 } 30 31 impl<T> Default for ValueConstraint<T> { default() -> Self32 fn default() -> Self { 33 Self::Constraint(Default::default()) 34 } 35 } 36 37 impl<T> From<T> for ValueConstraint<T> { from(bare: T) -> Self38 fn from(bare: T) -> Self { 39 Self::Bare(bare) 40 } 41 } 42 43 impl<T> From<ResolvedValueConstraint<T>> for ValueConstraint<T> { from(constraint: ResolvedValueConstraint<T>) -> Self44 fn from(constraint: ResolvedValueConstraint<T>) -> Self { 45 Self::Constraint(constraint) 46 } 47 } 48 49 impl<T> ValueConstraint<T> 50 where 51 T: Clone, 52 { 53 /// Returns a resolved representation of the constraint 54 /// with bare values resolved to fully-qualified constraints. to_resolved( &self, strategy: MediaTrackConstraintResolutionStrategy, ) -> ResolvedValueConstraint<T>55 pub fn to_resolved( 56 &self, 57 strategy: MediaTrackConstraintResolutionStrategy, 58 ) -> ResolvedValueConstraint<T> { 59 self.clone().into_resolved(strategy) 60 } 61 62 /// Consumes the constraint, returning a resolved representation of the 63 /// constraint with bare values resolved to fully-qualified constraints. into_resolved( self, strategy: MediaTrackConstraintResolutionStrategy, ) -> ResolvedValueConstraint<T>64 pub fn into_resolved( 65 self, 66 strategy: MediaTrackConstraintResolutionStrategy, 67 ) -> ResolvedValueConstraint<T> { 68 match self { 69 Self::Bare(bare) => match strategy { 70 MediaTrackConstraintResolutionStrategy::BareToIdeal => { 71 ResolvedValueConstraint::default().ideal(bare) 72 } 73 MediaTrackConstraintResolutionStrategy::BareToExact => { 74 ResolvedValueConstraint::default().exact(bare) 75 } 76 }, 77 Self::Constraint(constraint) => constraint, 78 } 79 } 80 } 81 82 impl<T> ValueConstraint<T> { 83 /// Returns `true` if `self` is empty, otherwise `false`. is_empty(&self) -> bool84 pub fn is_empty(&self) -> bool { 85 match self { 86 Self::Bare(_) => false, 87 Self::Constraint(constraint) => constraint.is_empty(), 88 } 89 } 90 } 91 92 /// A constraint specifying a single accepted value. 93 /// 94 /// # W3C Spec Compliance 95 /// 96 /// There exists no direct corresponding type in the 97 /// W3C ["Media Capture and Streams"][media_capture_and_streams_spec] spec, 98 /// since the `ValueConstraint<T>` type aims to be a 99 /// generalization over multiple types in the W3C spec: 100 /// 101 /// | Rust | W3C | 102 /// | ------------------------------ | --------------------------------------- | 103 /// | `ResolvedValueConstraint<bool>` | [`ConstrainBooleanParameters`][constrain_boolean_parameters] | 104 /// 105 /// [constrain_boolean_parameters]: https://www.w3.org/TR/mediacapture-streams/#dom-constrainbooleanparameters 106 /// [media_capture_and_streams_spec]: https://www.w3.org/TR/mediacapture-streams/ 107 #[derive(Debug, Clone, Eq, PartialEq)] 108 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 109 #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] 110 pub struct ResolvedValueConstraint<T> { 111 /// The exact required value for this property. 112 /// 113 /// This is a required value. 114 #[cfg_attr( 115 feature = "serde", 116 serde(skip_serializing_if = "core::option::Option::is_none") 117 )] 118 pub exact: Option<T>, 119 /// The ideal (target) value for this property. 120 /// 121 /// This is an optional value. 122 #[cfg_attr( 123 feature = "serde", 124 serde(skip_serializing_if = "core::option::Option::is_none") 125 )] 126 pub ideal: Option<T>, 127 } 128 129 impl<T> ResolvedValueConstraint<T> { 130 /// Consumes `self`, returning a corresponding constraint 131 /// with the exact required value set to `exact`. 132 #[inline] exact<U>(mut self, exact: U) -> Self where Option<T>: From<U>,133 pub fn exact<U>(mut self, exact: U) -> Self 134 where 135 Option<T>: From<U>, 136 { 137 self.exact = exact.into(); 138 self 139 } 140 141 /// Consumes `self`, returning a corresponding constraint 142 /// with the ideal required value set to `ideal`. 143 #[inline] ideal<U>(mut self, ideal: U) -> Self where Option<T>: From<U>,144 pub fn ideal<U>(mut self, ideal: U) -> Self 145 where 146 Option<T>: From<U>, 147 { 148 self.ideal = ideal.into(); 149 self 150 } 151 152 /// Returns `true` if `value.is_some()` is `true` for any of its required values, 153 /// otherwise `false`. is_required(&self) -> bool154 pub fn is_required(&self) -> bool { 155 self.exact.is_some() 156 } 157 158 /// Returns `true` if `value.is_none()` is `true` for all of its values, 159 /// otherwise `false`. is_empty(&self) -> bool160 pub fn is_empty(&self) -> bool { 161 self.exact.is_none() && self.ideal.is_none() 162 } 163 164 /// Returns a corresponding constraint containing only required values. to_required_only(&self) -> Self where T: Clone,165 pub fn to_required_only(&self) -> Self 166 where 167 T: Clone, 168 { 169 self.clone().into_required_only() 170 } 171 172 /// Consumes `self, returning a corresponding constraint 173 /// containing only required values. into_required_only(self) -> Self174 pub fn into_required_only(self) -> Self { 175 Self { 176 exact: self.exact, 177 ideal: None, 178 } 179 } 180 } 181 182 impl<T> Default for ResolvedValueConstraint<T> { 183 #[inline] default() -> Self184 fn default() -> Self { 185 Self { 186 exact: None, 187 ideal: None, 188 } 189 } 190 } 191 192 impl<T> std::fmt::Display for ResolvedValueConstraint<T> 193 where 194 T: std::fmt::Debug, 195 { fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result196 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 197 let mut is_first = true; 198 f.write_str("(")?; 199 if let Some(ref exact) = &self.exact { 200 f.write_fmt(format_args!("x == {exact:?}"))?; 201 is_first = false; 202 } 203 if let Some(ref ideal) = &self.ideal { 204 if !is_first { 205 f.write_str(" && ")?; 206 } 207 f.write_fmt(format_args!("x ~= {ideal:?}"))?; 208 is_first = false; 209 } 210 if is_first { 211 f.write_str("<empty>")?; 212 } 213 f.write_str(")")?; 214 Ok(()) 215 } 216 } 217 218 #[cfg(test)] 219 mod tests { 220 use super::*; 221 222 #[test] to_string()223 fn to_string() { 224 let scenarios = [ 225 (ResolvedValueConstraint::default(), "(<empty>)"), 226 ( 227 ResolvedValueConstraint::default().exact(true), 228 "(x == true)", 229 ), 230 ( 231 ResolvedValueConstraint::default().ideal(true), 232 "(x ~= true)", 233 ), 234 ( 235 ResolvedValueConstraint::default().exact(true).ideal(true), 236 "(x == true && x ~= true)", 237 ), 238 ]; 239 240 for (constraint, expected) in scenarios { 241 let actual = constraint.to_string(); 242 243 assert_eq!(actual, expected); 244 } 245 } 246 247 #[test] is_required()248 fn is_required() { 249 let scenarios = [ 250 (ResolvedValueConstraint::default(), false), 251 (ResolvedValueConstraint::default().exact(true), true), 252 (ResolvedValueConstraint::default().ideal(true), false), 253 ( 254 ResolvedValueConstraint::default().exact(true).ideal(true), 255 true, 256 ), 257 ]; 258 259 for (constraint, expected) in scenarios { 260 let actual = constraint.is_required(); 261 262 assert_eq!(actual, expected); 263 } 264 } 265 266 mod is_empty { 267 use super::*; 268 269 #[test] bare()270 fn bare() { 271 let constraint = ValueConstraint::Bare(true); 272 273 assert!(!constraint.is_empty()); 274 } 275 276 #[test] constraint()277 fn constraint() { 278 let scenarios = [ 279 (ResolvedValueConstraint::default(), true), 280 (ResolvedValueConstraint::default().exact(true), false), 281 (ResolvedValueConstraint::default().ideal(true), false), 282 ( 283 ResolvedValueConstraint::default().exact(true).ideal(true), 284 false, 285 ), 286 ]; 287 288 for (constraint, expected) in scenarios { 289 let constraint = ValueConstraint::<bool>::Constraint(constraint); 290 291 let actual = constraint.is_empty(); 292 293 assert_eq!(actual, expected); 294 } 295 } 296 } 297 298 #[test] resolve_to_advanced()299 fn resolve_to_advanced() { 300 let constraints = [ 301 ValueConstraint::Bare(true), 302 ValueConstraint::Constraint(ResolvedValueConstraint::default().exact(true)), 303 ]; 304 let strategy = MediaTrackConstraintResolutionStrategy::BareToExact; 305 306 for constraint in constraints { 307 let actuals = [ 308 constraint.to_resolved(strategy), 309 constraint.into_resolved(strategy), 310 ]; 311 312 let expected = ResolvedValueConstraint::default().exact(true); 313 314 for actual in actuals { 315 assert_eq!(actual, expected); 316 } 317 } 318 } 319 320 #[test] resolve_to_basic()321 fn resolve_to_basic() { 322 let constraints = [ 323 ValueConstraint::Bare(true), 324 ValueConstraint::Constraint(ResolvedValueConstraint::default().ideal(true)), 325 ]; 326 let strategy = MediaTrackConstraintResolutionStrategy::BareToIdeal; 327 328 for constraint in constraints { 329 let actuals = [ 330 constraint.to_resolved(strategy), 331 constraint.into_resolved(strategy), 332 ]; 333 334 let expected = ResolvedValueConstraint::default().ideal(true); 335 336 for actual in actuals { 337 assert_eq!(actual, expected); 338 } 339 } 340 } 341 } 342 343 #[cfg(feature = "serde")] 344 #[cfg(test)] 345 mod serde_tests { 346 use crate::macros::test_serde_symmetry; 347 348 use super::*; 349 350 macro_rules! test_serde { 351 ($t:ty => { 352 value: $value:expr 353 }) => { 354 type Subject = ValueConstraint<$t>; 355 356 #[test] 357 fn default() { 358 let subject = Subject::default(); 359 let json = serde_json::json!({}); 360 361 test_serde_symmetry!(subject: subject, json: json); 362 } 363 364 #[test] 365 fn bare() { 366 let subject = Subject::Bare($value.to_owned()); 367 let json = serde_json::json!($value); 368 369 test_serde_symmetry!(subject: subject, json: json); 370 } 371 372 #[test] 373 fn exact_constraint() { 374 let subject = Subject::Constraint(ResolvedValueConstraint::default().exact($value.to_owned())); 375 let json = serde_json::json!({ 376 "exact": $value, 377 }); 378 379 test_serde_symmetry!(subject: subject, json: json); 380 } 381 382 #[test] 383 fn ideal_constraint() { 384 let subject = Subject::Constraint(ResolvedValueConstraint::default().ideal($value.to_owned())); 385 let json = serde_json::json!({ 386 "ideal": $value, 387 }); 388 389 test_serde_symmetry!(subject: subject, json: json); 390 } 391 392 #[test] 393 fn full_constraint() { 394 let subject = Subject::Constraint(ResolvedValueConstraint::default().exact($value.to_owned()).ideal($value.to_owned())); 395 let json = serde_json::json!({ 396 "exact": $value, 397 "ideal": $value, 398 }); 399 400 test_serde_symmetry!(subject: subject, json: json); 401 } 402 }; 403 } 404 405 mod bool { 406 use super::*; 407 408 test_serde!(bool => { 409 value: true 410 }); 411 } 412 413 mod string { 414 use super::*; 415 416 test_serde!(String => { 417 value: "VALUE" 418 }); 419 } 420 } 421