use std::{borrow::BorrowMut, fmt::{Debug, Display}};
use anyhow::Result;
#[derive(Clone, Copy, Debug)]
pub enum ArgRequiredBy {
Child,
Me,
NoOne,
Parent,
}
impl ArgRequiredBy {
pub fn is_child(self) -> bool {
matches!(self, Self::Child)
}
pub fn is_me(self) -> bool {
matches!(self, Self::Me)
}
pub fn is_noone(self) -> bool {
matches!(self, Self::NoOne)
}
pub fn is_parent(self) -> bool {
matches!(self, Self::Parent)
}
}
#[derive(thiserror::Error, Clone, Debug)]
pub enum ArgError {
#[error("{0} requires an argument")]
Missing(String),
#[error("{0} invalid with reason(s): {1:?}")]
NotValid(String, Vec<String>)
}
#[derive(thiserror::Error, Clone, Debug)]
pub enum ResourceError {
#[error("existing {1} node of {0} already set")]
AlreadySet(String, String),
}
#[derive(Debug)]
pub struct ApiResource<'a, T: Display> {
name: &'a str,
arg: Option<T>,
arg_required_by: ArgRequiredBy,
arg_validators: Vec<fn(&T) -> Result<()>>,
child: Option<Box<Self>>,
parent: Option<Box<Self>>,
weight: f32,
}
impl<'a, T: Display> ApiResource<'a, T> {
pub fn new<'b: 'a>(name: &'b str) -> Self {
Self{
name,
arg: None,
arg_required_by: ArgRequiredBy::NoOne,
arg_validators: vec![],
child: None,
parent: None,
weight: 0.0
}
}
}
impl<T: Clone + Display> Clone for ApiResource<'_, T> {
fn clone(&self) -> Self {
Self{
name: self.name,
arg: self.arg.clone(),
arg_required_by: self.arg_required_by,
arg_validators: self.arg_validators.clone(),
child: self.child.clone(),
parent: self.parent.clone(),
weight: self.weight
}
}
}
pub trait PathComponent {
fn as_path_component(&self) -> Result<String>;
fn compose(&self) -> Result<String>;
}
impl<'a, T: Debug + Display + Clone> PathComponent for ApiResource<'a, T> {
fn as_path_component(&self) -> Result<String> {
let to_argnotfound = |n: &Self| {
Err(ArgError::Missing(n.name().to_owned()).into())
};
let compose_this = || {
let errors: Vec<_> = self.arg_validators
.iter()
.map(|f| { (f)(self.arg.as_ref().unwrap()) })
.filter(|r| r.is_err())
.map(|r| r.unwrap_err().to_string())
.collect();
if !errors.is_empty() {
Err(ArgError::NotValid(self.name(), errors).into())
} else {
let ret = format!(
"{}/{}",
self.name(),
self.arg.clone().map_or("".into(), |a| a.to_string()));
Ok(ret)
}
};
if self.arg.is_some() || self.required_by().is_noone() {
compose_this()
} else if self.required_by().is_parent() && self.parent.is_some() {
to_argnotfound(self.parent().unwrap())
} else if self.required_by().is_child() && self.child.is_some() {
to_argnotfound(self.child().unwrap())
} else {
compose_this()
}
}
fn compose(&self) -> Result<String> {
let mut curr = Some(self);
let mut components = vec![];
while curr.is_some() {
components.push(match curr.unwrap().as_path_component() {
Ok(path) => {
curr = curr.unwrap().child();
path
},
e => return e
});
}
Ok(components.join("/").replace("//", "/"))
}
}
pub trait ArgedResource<T> {
fn argument(&self) -> Option<&T>;
fn required_by(&self) -> ArgRequiredBy;
fn with_arg(&mut self, arg: T) -> &mut Self;
fn with_arg_required(&mut self, required: ArgRequiredBy) -> &mut Self;
}
impl<'a, T: Clone + Display> ArgedResource<T> for ApiResource<'a, T> {
fn argument(&self) -> Option<&T> {
self.arg.as_ref()
}
fn required_by(&self) -> ArgRequiredBy {
self.arg_required_by
}
fn with_arg(&mut self, arg: T) -> &mut Self {
self.arg = Some(arg);
self
}
fn with_arg_required(&mut self, required: ArgRequiredBy) -> &mut Self {
self.arg_required_by = required;
self
}
}
pub trait CoreResource<T> {
fn name(&self) -> String;
}
impl<'a, T: Clone + Display> CoreResource<T> for ApiResource<'a, T> {
fn name(&self) -> String {
self.name.to_owned()
}
}
pub trait LinkedResource<'a, T: Display> {
fn child(&self) -> Option<&Self>;
fn parent(&self) -> Option<&Self>;
fn is_child(&self) -> bool;
fn is_root(&self) -> bool;
fn is_tail(&self) -> bool;
fn with_child(&mut self, child: &mut ApiResource<'a, T>) -> Result<Box<Self>>;
fn with_parent(&mut self, parent: &mut ApiResource<'a, T>) -> Result<Box<Self>>;
}
impl<'a, T: Debug + Display + Clone> LinkedResource<'a, T> for ApiResource<'a, T> {
fn child(&self) -> Option<&Self> {
self.child.as_deref()
}
fn parent(&self) -> Option<&Self> {
self.parent.as_deref()
}
fn is_child(&self) -> bool {
self.parent.is_some()
}
fn is_root(&self) -> bool {
self.parent.is_none()
}
fn is_tail(&self) -> bool {
self.child.is_none()
}
fn with_child(&mut self, child: &mut ApiResource<'a, T>) -> Result<Box<Self>> {
match self.child {
None => {
let mut new = self.clone();
match child.with_parent(new.borrow_mut()) {
Ok(chld) => {
new.child = Some(Box::new(chld.as_ref().clone()));
Ok(Box::new(new))
},
Err(e) => Err(e)
}
},
Some(_) => Err(ResourceError::AlreadySet(self.name(), "child".into()).into())
}
}
fn with_parent(&mut self, parent: &mut ApiResource<'a, T>) -> Result<Box<Self>> {
match self.parent {
None => {
self.parent = Box::new(parent.clone()).into();
Ok(Box::new(self.clone()))
},
Some(_) => Err(ResourceError::AlreadySet(self.name(), "parent".into()).into())
}
}
}
pub trait WeightedResource {
fn weight(&self) -> f32;
fn with_weight(&mut self, weight: f32) -> &Self;
}
impl<T: Display> WeightedResource for ApiResource<'_, T> {
fn weight(&self) -> f32 {
self.weight
}
fn with_weight(&mut self, weight: f32) -> &Self {
self.weight = weight;
self
}
}
pub trait Resource<'a, T: Clone + Display>:
CoreResource<T> +
ArgedResource<T> +
LinkedResource<'a, T> +
WeightedResource {}
impl<'a, T: Clone + Debug + Display> Resource<'a, T> for ApiResource<'a, T> {}