use std::{fmt::Display, num::ParseIntError, ops::Deref, str::FromStr};
use snafu::Snafu;
#[derive(Debug, Snafu)]
pub enum ResourceParseError {
#[snafu(display("Invalid UUID: {source}"), context(false))]
Uuid {
#[snafu(source(from(uuid::Error, Box::new)))]
source: Box<uuid::Error>,
},
#[snafu(display("Invalid integer: {source}"), context(false))]
ParseInt {
#[snafu(source(from(ParseIntError, Box::new)))]
source: Box<ParseIntError>,
},
#[snafu(whatever)]
Other {
message: String,
#[snafu(source(from(Box<dyn std::error::Error + Send + Sync>, Some)))]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
}
pub trait Resource: Sized + Display + KustosFromStr {
const PREFIX: &'static str;
fn resource_id(&self) -> ResourceId {
debug_assert!(Self::PREFIX.starts_with('/') && Self::PREFIX.ends_with('/'));
ResourceId(format!("{}{}", Self::PREFIX, self))
}
}
pub trait KustosFromStr: Sized {
fn kustos_from_str(s: &str) -> Result<Self, ResourceParseError>;
}
impl<T: Resource + FromStr<Err = E>, E: Into<ResourceParseError>> KustosFromStr for T {
fn kustos_from_str(s: &str) -> Result<Self, ResourceParseError> {
Self::from_str(s).map_err(Into::into)
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct ResourceId(pub(crate) String);
impl ResourceId {
pub fn into_inner(self) -> String {
self.0
}
pub fn with_suffix<S>(&self, suffix: S) -> ResourceId
where
S: AsRef<str>,
{
let mut inner = self.0.clone();
inner.push_str(suffix.as_ref());
ResourceId(inner)
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl Display for ResourceId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl AsRef<str> for ResourceId {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Deref for ResourceId {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<&str> for ResourceId {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
impl From<String> for ResourceId {
fn from(s: String) -> Self {
Self(s)
}
}
#[derive(Debug)]
pub enum AccessibleResources<T: Resource> {
List(Vec<T>),
All,
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use super::*;
struct ResourceX(uuid::Uuid);
impl Display for ResourceX {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.as_hyphenated().fmt(f)
}
}
impl Resource for ResourceX {
const PREFIX: &'static str = "/resources/";
}
impl FromStr for ResourceX {
type Err = ResourceParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse().map(Self).map_err(Into::into)
}
}
#[test]
fn test_a() {
let x = ResourceId("/resources/00000000-0000-0000-0000-000000000000".to_string());
let target: ResourceX = x.strip_prefix(ResourceX::PREFIX).unwrap().parse().unwrap();
assert_eq!(target.0, uuid::Uuid::nil())
}
}