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