use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result as FormatResult;
use bevy::prelude::Reflect;
use bevy::reflect::ReflectDeserialize;
use bevy::reflect::ReflectSerialize;
use serde::Deserialize;
use serde::Deserializer;
use serde::Serialize;
use serde::de::Error as DeserializeError;
use thiserror::Error;
use crate::DeviceKey;
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize, Reflect)]
#[reflect(Serialize, Deserialize)]
pub struct DeviceEndpoint {
pub device: DeviceKey,
pub id: EndpointId,
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize, Reflect)]
#[reflect(Serialize, Deserialize)]
pub enum EndpointId {
Whole,
Part(PartName),
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Reflect)]
#[reflect(opaque)]
#[reflect(Serialize, Deserialize)]
pub struct PartName(String);
impl PartName {
pub fn new(value: impl Into<String>) -> Result<Self, PartNameError> {
let value = value.into();
if value.is_empty() {
return Err(PartNameError::Empty);
}
if value.chars().any(char::is_control) {
return Err(PartNameError::ContainsControlCharacter);
}
Ok(Self(value))
}
#[must_use]
pub fn as_str(&self) -> &str { &self.0 }
}
impl Display for PartName {
fn fmt(&self, formatter: &mut Formatter<'_>) -> FormatResult { formatter.write_str(&self.0) }
}
impl<'de> Deserialize<'de> for PartName {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
Self::new(value).map_err(<D::Error as DeserializeError>::custom)
}
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
pub enum PartNameError {
#[error("endpoint part names must not be empty")]
Empty,
#[error("endpoint part names must not contain control characters")]
ContainsControlCharacter,
}
#[cfg(test)]
mod tests {
use std::error::Error;
use ron::Options;
use ron::extensions::Extensions;
use super::DeviceEndpoint;
use super::EndpointId;
use super::PartName;
use super::PartNameError;
use crate::DeviceIdSource;
use crate::DeviceKey;
use crate::DeviceKind;
use crate::ReportedId;
use crate::SchemeName;
#[test]
fn whole_display_endpoint_survives_serde_round_trip() -> Result<(), Box<dyn Error>> {
let device_endpoint = DeviceEndpoint {
device: display_key()?,
id: EndpointId::Whole,
};
let options = Options::default().with_default_extension(Extensions::UNWRAP_NEWTYPES);
let encoded = options.to_string(&device_endpoint)?;
let decoded: DeviceEndpoint = options.from_str(&encoded)?;
assert_eq!(decoded, device_endpoint);
Ok(())
}
#[test]
fn named_part_endpoint_survives_serde_round_trip() -> Result<(), Box<dyn Error>> {
let device_endpoint = DeviceEndpoint {
device: display_key()?,
id: EndpointId::Part(PartName::new("ch/7")?),
};
let options = Options::default().with_default_extension(Extensions::UNWRAP_NEWTYPES);
let encoded = options.to_string(&device_endpoint)?;
let decoded: DeviceEndpoint = options.from_str(&encoded)?;
assert_eq!(decoded, device_endpoint);
Ok(())
}
#[test]
fn empty_part_name_returns_empty_error() {
assert_eq!(PartName::new(""), Err(PartNameError::Empty));
}
#[test]
fn part_name_with_control_character_returns_error() {
assert_eq!(
PartName::new("input\nchannel"),
Err(PartNameError::ContainsControlCharacter)
);
}
#[test]
fn empty_part_name_fails_ron_deserialization() {
assert!(ron::from_str::<PartName>("\"\"").is_err());
}
#[test]
fn part_name_with_control_character_fails_ron_deserialization() {
assert!(ron::from_str::<PartName>(r#""input\nchannel""#).is_err());
}
fn display_key() -> Result<DeviceKey, Box<dyn Error>> {
Ok(DeviceKey {
kind: DeviceKind::Display,
id: DeviceIdSource::Reported {
scheme: SchemeName::new("edid-serial")?,
value: ReportedId::new("DELL-U2723QE-9J4K2H3")?,
},
})
}
}