use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Result as FormatResult;
use bevy::ecs::reflect::ReflectComponent;
use bevy::prelude::Component;
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::AttemptId;
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default, Component, Reflect)]
#[reflect(Component, PartialEq)]
pub enum RoleState {
#[default]
Waiting,
Ready,
Applying(AttemptId),
StoppedAfterRepeatedFailures,
Retired,
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, Component, Serialize, Reflect)]
#[reflect(opaque)]
#[reflect(Component, PartialEq, Serialize, Deserialize)]
pub struct RoleKey(String);
impl RoleKey {
pub fn new(value: impl Into<String>) -> Result<Self, RoleKeyError> {
let value = value.into();
if value.is_empty() {
return Err(RoleKeyError::Empty);
}
if value.chars().any(char::is_control) {
return Err(RoleKeyError::ContainsControlCharacter);
}
Ok(Self(value))
}
#[must_use]
pub fn as_str(&self) -> &str { &self.0 }
}
impl Display for RoleKey {
fn fmt(&self, formatter: &mut Formatter<'_>) -> FormatResult { formatter.write_str(&self.0) }
}
impl<'de> Deserialize<'de> for RoleKey {
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 RoleKeyError {
#[error("role keys must not be empty")]
Empty,
#[error("role keys must not contain control characters")]
ContainsControlCharacter,
}
#[cfg(test)]
mod tests {
use std::any::TypeId;
use bevy::app::App;
use bevy::ecs::reflect::AppTypeRegistry;
use bevy::ecs::reflect::ReflectComponent;
use super::RoleKey;
use super::RoleKeyError;
use super::RoleState;
#[test]
fn role_key_retains_valid_application_handle_text() {
assert_eq!(
RoleKey::new("primary-window").as_ref().map(RoleKey::as_str),
Ok("primary-window")
);
}
#[test]
fn empty_role_key_returns_empty_error() {
assert_eq!(RoleKey::new(""), Err(RoleKeyError::Empty));
}
#[test]
fn role_key_with_control_character_returns_error() {
assert_eq!(
RoleKey::new("primary\nwindow"),
Err(RoleKeyError::ContainsControlCharacter)
);
}
#[test]
fn role_state_registers_component_reflection_metadata() {
let app = App::new();
let type_registry = app.world().resource::<AppTypeRegistry>().read();
let type_id = TypeId::of::<RoleState>();
assert!(type_registry.contains(type_id));
assert!(
type_registry
.get_type_data::<ReflectComponent>(type_id)
.is_some()
);
drop(type_registry);
}
}