1cc088ae5SVincent Esche //! Errors, as defined in the ["Media Capture and Streams"][mediacapture_streams] spec. 2cc088ae5SVincent Esche //! 3cc088ae5SVincent Esche //! [mediacapture_streams]: https://www.w3.org/TR/mediacapture-streams/ 4cc088ae5SVincent Esche 56209c8acSVincent Esche use std::collections::HashMap; 66209c8acSVincent Esche 791a9d9aeSVincent Esche use crate::{ 891a9d9aeSVincent Esche algorithms::{ConstraintFailureInfo, SettingFitnessDistanceErrorKind}, 991a9d9aeSVincent Esche MediaTrackProperty, 1091a9d9aeSVincent Esche }; 116209c8acSVincent Esche 1286143b07SVincent Esche /// An error indicating one or more over-constrained settings. 1391a9d9aeSVincent Esche #[derive(Clone, Eq, PartialEq, Debug)] 14cc088ae5SVincent Esche pub struct OverconstrainedError { 15cc088ae5SVincent Esche /// The offending constraint's name. 1691a9d9aeSVincent Esche pub constraint: MediaTrackProperty, 1786143b07SVincent Esche /// An error message, or `None` if exposure-mode was `Protected`. 18cc088ae5SVincent Esche pub message: Option<String>, 19cc088ae5SVincent Esche } 20cc088ae5SVincent Esche 2191a9d9aeSVincent Esche impl Default for OverconstrainedError { default() -> Self2291a9d9aeSVincent Esche fn default() -> Self { 2391a9d9aeSVincent Esche Self { 2491a9d9aeSVincent Esche constraint: MediaTrackProperty::from(""), 2591a9d9aeSVincent Esche message: Default::default(), 2691a9d9aeSVincent Esche } 2791a9d9aeSVincent Esche } 2891a9d9aeSVincent Esche } 2991a9d9aeSVincent Esche 30cc088ae5SVincent Esche impl std::fmt::Display for OverconstrainedError { fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result31cc088ae5SVincent Esche fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 32cc088ae5SVincent Esche write!(f, "Overconstrained property {:?}", self.constraint)?; 33cc088ae5SVincent Esche if let Some(message) = self.message.as_ref() { 34*5d8fe953SJoão Oliveira write!(f, ": {message}")?; 35cc088ae5SVincent Esche } 36cc088ae5SVincent Esche Ok(()) 37cc088ae5SVincent Esche } 38cc088ae5SVincent Esche } 39cc088ae5SVincent Esche 40cc088ae5SVincent Esche impl std::error::Error for OverconstrainedError {} 416209c8acSVincent Esche 426209c8acSVincent Esche impl OverconstrainedError { exposing_device_information( failed_constraints: HashMap<MediaTrackProperty, ConstraintFailureInfo>, ) -> Self436209c8acSVincent Esche pub(super) fn exposing_device_information( 4491a9d9aeSVincent Esche failed_constraints: HashMap<MediaTrackProperty, ConstraintFailureInfo>, 456209c8acSVincent Esche ) -> Self { 466209c8acSVincent Esche let failed_constraint = failed_constraints 476209c8acSVincent Esche .into_iter() 486209c8acSVincent Esche .max_by_key(|(_, failure_info)| failure_info.failures); 496209c8acSVincent Esche 506209c8acSVincent Esche let (constraint, failure_info) = 516209c8acSVincent Esche failed_constraint.expect("Empty candidates implies non-empty failed constraints"); 526209c8acSVincent Esche 536209c8acSVincent Esche struct Violation { 546209c8acSVincent Esche constraint: String, 556209c8acSVincent Esche settings: Vec<String>, 566209c8acSVincent Esche } 576209c8acSVincent Esche let mut violators_by_kind: HashMap<SettingFitnessDistanceErrorKind, Violation> = 586209c8acSVincent Esche HashMap::default(); 596209c8acSVincent Esche 606209c8acSVincent Esche for error in failure_info.errors { 616209c8acSVincent Esche let violation = violators_by_kind.entry(error.kind).or_insert(Violation { 626209c8acSVincent Esche constraint: error.constraint.clone(), 636209c8acSVincent Esche settings: vec![], 646209c8acSVincent Esche }); 656209c8acSVincent Esche assert_eq!(violation.constraint, error.constraint); 666209c8acSVincent Esche if let Some(setting) = error.setting { 676209c8acSVincent Esche violation.settings.push(setting.clone()); 686209c8acSVincent Esche } 696209c8acSVincent Esche } 706209c8acSVincent Esche 716209c8acSVincent Esche let formatted_reasons: Vec<_> = violators_by_kind 726209c8acSVincent Esche .into_iter() 736209c8acSVincent Esche .map(|(kind, violation)| { 746209c8acSVincent Esche let kind_str = match kind { 756209c8acSVincent Esche SettingFitnessDistanceErrorKind::Missing => "missing", 766209c8acSVincent Esche SettingFitnessDistanceErrorKind::Mismatch => "a mismatch", 776209c8acSVincent Esche SettingFitnessDistanceErrorKind::TooSmall => "too small", 786209c8acSVincent Esche SettingFitnessDistanceErrorKind::TooLarge => "too large", 796209c8acSVincent Esche }; 806209c8acSVincent Esche 816209c8acSVincent Esche let mut settings = violation.settings; 826209c8acSVincent Esche 836209c8acSVincent Esche if settings.is_empty() { 846209c8acSVincent Esche return format!("{} (does not satisfy {})", kind_str, violation.constraint); 856209c8acSVincent Esche } 866209c8acSVincent Esche 876209c8acSVincent Esche settings.sort(); 886209c8acSVincent Esche 896209c8acSVincent Esche format!( 906209c8acSVincent Esche "{} ([{}] do not satisfy {})", 916209c8acSVincent Esche kind_str, 926209c8acSVincent Esche settings.join(", "), 936209c8acSVincent Esche violation.constraint 946209c8acSVincent Esche ) 956209c8acSVincent Esche }) 966209c8acSVincent Esche .collect(); 976209c8acSVincent Esche 986209c8acSVincent Esche let formatted_reason = match &formatted_reasons[..] { 996209c8acSVincent Esche [] => unreachable!(), 1006209c8acSVincent Esche [reason] => reason.clone(), 1016209c8acSVincent Esche [reasons @ .., reason] => { 1026209c8acSVincent Esche let reasons = reasons.join(", "); 103*5d8fe953SJoão Oliveira format!("either {reasons}, or {reason}") 1046209c8acSVincent Esche } 1056209c8acSVincent Esche }; 106*5d8fe953SJoão Oliveira let message = Some(format!("Setting was {formatted_reason}.")); 1076209c8acSVincent Esche 1086209c8acSVincent Esche Self { 1096209c8acSVincent Esche constraint, 1106209c8acSVincent Esche message, 1116209c8acSVincent Esche } 1126209c8acSVincent Esche } 1136209c8acSVincent Esche } 114