#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LimitSource {
Sink,
Source,
Cable,
}
impl core::fmt::Display for LimitSource {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
LimitSource::Sink => f.write_str("sink"),
LimitSource::Source => f.write_str("source"),
LimitSource::Cable => f.write_str("cable"),
}
}
}
#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone)]
pub struct TaggedViolation<V = Violation> {
#[cfg_attr(
feature = "serde",
serde(deserialize_with = "serde_de_ignore_rule_name")
)]
pub rule: &'static str,
pub violation: V,
}
impl<V: core::fmt::Display> core::fmt::Display for TaggedViolation<V> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "[{}] {}", self.rule, self.violation)
}
}
#[cfg(feature = "serde")]
fn serde_de_ignore_rule_name<'de, D: serde::Deserializer<'de>>(
d: D,
) -> Result<&'static str, D::Error> {
use serde::Deserialize as _;
serde::de::IgnoredAny::deserialize(d)?;
Ok("")
}
#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, thiserror::Error)]
pub enum Warning {
#[error("DSC required; lossy compression active")]
DscActive,
#[error("cable bandwidth marginal for selected mode")]
CableBandwidthMarginal,
}
#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, thiserror::Error)]
pub enum Violation {
#[error("pixel clock {required_mhz} MHz exceeds {limit_source} limit of {limit_mhz} MHz")]
PixelClockExceeded {
required_mhz: u32,
limit_mhz: u32,
limit_source: LimitSource,
},
#[error("TMDS clock {required_mhz} MHz exceeds {limit_source} limit of {limit_mhz} MHz")]
TmdsClockExceeded {
required_mhz: u32,
limit_mhz: u32,
limit_source: LimitSource,
},
#[error("FRL rate {requested:?} exceeds {limit_source} limit of {limit:?}")]
FrlRateExceeded {
requested: display_types::cea861::HdmiForumFrl,
limit: display_types::cea861::HdmiForumFrl,
limit_source: LimitSource,
},
#[error("color encoding not supported by sink")]
ColorEncodingUnsupported,
#[error("mode only supports YCbCr 4:2:0; other encodings are not valid for this mode")]
EncodingRestrictedToYCbCr420,
#[error("bit depth not supported by sink")]
BitDepthUnsupported,
#[error("DSC required but not supported")]
DscUnsupported,
#[error("refresh rate {rate_hz} Hz outside sink range [{min_hz}, {max_hz}] Hz")]
RefreshRateOutOfRange {
rate_hz: u16,
min_hz: u16,
max_hz: u16,
},
}
#[cfg(test)]
mod tests {
use super::*;
extern crate alloc;
use alloc::format;
#[test]
fn limit_source_display() {
assert_eq!(format!("{}", LimitSource::Sink), "sink");
assert_eq!(format!("{}", LimitSource::Source), "source");
assert_eq!(format!("{}", LimitSource::Cable), "cable");
}
#[test]
fn tagged_violation_display_includes_rule_and_message() {
let tv = TaggedViolation {
rule: "my_rule",
violation: Violation::ColorEncodingUnsupported,
};
assert_eq!(
format!("{tv}"),
"[my_rule] color encoding not supported by sink"
);
}
#[cfg(feature = "serde")]
#[test]
fn tagged_violation_rule_deserializes_as_empty_string() {
let json = r#"{"rule":"original","violation":"ColorEncodingUnsupported"}"#;
let de: TaggedViolation<Violation> = serde_json::from_str(json).unwrap();
assert_eq!(de.rule, "");
assert!(matches!(de.violation, Violation::ColorEncodingUnsupported));
}
}