uri_resources/
lib.rs

1//! # URI Routes Resources.
2//! A sidecar library for detailing the specifics of how a URI should
3//! be constructed.
4//! Allows for a rudimentary check of path arguments, when/if they are
5//! required to build the resulting URI.
6use std::{borrow::BorrowMut, fmt::{Debug, Display}};
7
8use anyhow::Result;
9
10#[derive(Clone, Copy, Debug)]
11pub enum ArgRequiredBy {
12    Child,
13    Me,
14    NoOne,
15    Parent,
16}
17
18impl ArgRequiredBy {
19    pub fn is_child(self) -> bool {
20        matches!(self, Self::Child)
21    }
22
23    pub fn is_me(self) -> bool {
24        matches!(self, Self::Me)
25    }
26
27    pub fn is_noone(self) -> bool {
28        matches!(self, Self::NoOne)
29    }
30
31    pub fn is_parent(self) -> bool {
32        matches!(self, Self::Parent)
33    }
34}
35
36#[derive(thiserror::Error, Clone, Debug)]
37pub enum ArgError {
38    #[error("{0} requires an argument")]
39    Missing(String),
40    #[error("{0} invalid with reason(s): {1:?}")]
41    NotValid(String, Vec<String>)
42}
43
44#[derive(thiserror::Error, Clone, Debug)]
45pub enum ResourceError {
46    #[error("existing {1} node of {0} already set")]
47    AlreadySet(String, String),
48}
49
50/// Represents a single part of of a URI path.
51/// Where arguments are optional, there are
52/// interfaces which allow this object to check
53/// if an argument is required by either this
54/// component, or entities that are related to it.
55#[derive(Debug)]
56pub struct ApiResource<'a, T: Display> {
57    name:            &'a str,
58    arg:             Option<T>,
59    arg_required_by: ArgRequiredBy,
60    arg_validators:  Vec<fn(&T) -> Result<()>>,
61    child:           Option<Box<Self>>,
62    parent:          Option<Box<Self>>,
63    weight:          f32,
64}
65
66/// Barebones basic implementation of an
67/// `ApiResource`.
68/// ```rust
69/// use uri_resources::ApiResource;
70/// let resource: ApiResource<'_, String> = ApiResource::new("resource");
71/// ```
72impl<'a, T: Display> ApiResource<'a, T> {
73    /// Create a new instance of `ApiResource`.
74    pub fn new<'b: 'a>(name: &'b str) -> Self {
75        Self{
76            name,
77            arg: None,
78            arg_required_by: ArgRequiredBy::NoOne,
79            arg_validators: vec![],
80            child: None,
81            parent: None,
82            weight: 0.0
83        }
84    }
85}
86
87impl<T: Clone + Display> Clone for ApiResource<'_, T> {
88    fn clone(&self) -> Self {
89        Self{
90            name: self.name,
91            arg:  self.arg.clone(),
92            arg_required_by: self.arg_required_by,
93            arg_validators: self.arg_validators.clone(),
94            child: self.child.clone(),
95            parent: self.parent.clone(),
96            weight: self.weight
97        }
98    }
99}
100
101/// Composes an object into a path component,
102/// conditionally failing if the implemented
103/// instance does not meet the requirements set
104/// by it's declaration.
105///
106/// Ensure resources can be digested as path
107/// components.
108/// ```rust
109/// use uri_resources::{ApiResource, PathComponent};
110/// let path = ApiResource::<String>::new("resource").as_path_component();
111/// assert!(!path.is_err())
112/// ```
113pub trait PathComponent {
114    /// Composes this as a path component.
115    ///
116    /// Ensure resources can be digested and return
117    /// the expected value.
118    /// ```rust
119    /// use uri_resources::{ApiResource, PathComponent};
120    /// let path = ApiResource::<String>::new("resource").as_path_component();
121    /// assert_eq!(path.unwrap(), String::from("resource/"))
122    /// ```
123    fn as_path_component(&self) -> Result<String>;
124    /// Compose the entire heirarchy of components
125    /// into one string.
126    ///
127    /// Ensure the composition of a multi node
128    /// collection can be composed into a single
129    /// String value without error.
130    /// ```rust
131    /// use uri_resources::{ApiResource, LinkedResource, PathComponent};
132    /// let mut child0 = ApiResource::<String>::new("child_resource0");
133    /// let mut child1 = ApiResource::<String>::new("child_resource1");
134    ///
135    /// child0 = *child0.with_child(&mut child1).expect("resource node");
136    /// let parent = ApiResource::<String>::new("parent_resource")
137    ///     .with_child(&mut child0);
138    ///
139    /// let path = parent.expect("parent node").compose();
140    /// assert!(!path.is_err())
141    /// ```
142    ///
143    /// Ensure the composition of a multi node
144    /// collection can be composed into a single
145    /// String value without error.
146    /// ```rust
147    /// use uri_resources::{ApiResource, LinkedResource, PathComponent};
148    /// let mut child0 = ApiResource::<String>::new("child_resource0");
149    /// let mut child1 = ApiResource::<String>::new("child_resource1");
150    ///
151    /// child0 = *child0.with_child(&mut child1).expect("resource node");
152    /// let parent = ApiResource::<String>::new("parent_resource")
153    ///     .with_child(&mut child0);
154    ///
155    /// let path = parent.expect("parent node").compose();
156    /// assert_eq!(path.expect("composed path"), "parent_resource/child_resource0/child_resource1/")
157    /// ```
158    fn compose(&self) -> Result<String>;
159}
160
161impl<'a, T: Debug + Display + Clone> PathComponent for ApiResource<'a, T> {
162    fn as_path_component(&self) -> Result<String> {
163        let to_argnotfound = |n: &Self| {
164            Err(ArgError::Missing(n.name().to_owned()).into())
165        };
166
167        let compose_this = || {
168            let errors: Vec<_> = self.arg_validators
169                .iter()
170                .map(|f| { (f)(self.arg.as_ref().unwrap()) })
171                .filter(|r| r.is_err())
172                .map(|r| r.unwrap_err().to_string())
173                .collect();
174
175            if !errors.is_empty()  {
176                Err(ArgError::NotValid(self.name(), errors).into())
177            } else {
178                let ret = format!(
179                    "{}/{}",
180                    self.name(),
181                    self.arg.clone().map_or("".into(), |a| a.to_string()));
182                Ok(ret)
183            }
184        };
185
186        if self.arg.is_some() || self.required_by().is_noone() {
187            compose_this()
188        } else if self.required_by().is_parent() && self.parent.is_some() {
189            to_argnotfound(self.parent().unwrap())
190        } else if self.required_by().is_child() && self.child.is_some() {
191            to_argnotfound(self.child().unwrap())
192        } else {
193            compose_this()
194        }
195    }
196
197    fn compose(&self) -> Result<String> {
198        let mut curr = Some(self);
199        let mut components = vec![];
200
201        while curr.is_some() {
202            components.push(match curr.unwrap().as_path_component() {
203                Ok(path) => {
204                    curr = curr.unwrap().child();
205                    path
206                },
207                e => return e
208            });
209        }
210        Ok(components.join("/").replace("//", "/"))
211    }
212}
213
214pub trait ArgedResource<T> {
215    /// Argument set on this resource.
216    fn argument(&self) -> Option<&T>;
217    /// Determines if, and by whom, an argument
218    /// set on this is required.
219    fn required_by(&self) -> ArgRequiredBy;
220    /// Sets an argument on this resource
221    /// component.
222    fn with_arg(&mut self, arg: T) -> &mut Self;
223    /// Sets if, and by whom, this component's
224    /// argument is required.
225    fn with_arg_required(&mut self, required: ArgRequiredBy) -> &mut Self;
226}
227
228impl<'a, T: Clone + Display> ArgedResource<T> for ApiResource<'a, T> {
229    fn argument(&self) -> Option<&T> {
230        self.arg.as_ref()
231    }
232
233    fn required_by(&self) -> ArgRequiredBy {
234        self.arg_required_by
235    }
236
237    fn with_arg(&mut self, arg: T) -> &mut Self {
238        self.arg = Some(arg);
239        self
240    }
241
242    fn with_arg_required(&mut self, required: ArgRequiredBy) -> &mut Self {
243        self.arg_required_by = required;
244        self
245    }
246}
247
248/// The core functionality that is to be expected
249/// of some resource object. These methods assist
250/// in the work done by other traits in this
251/// library. Specifically by managing the the
252/// resource and it's relatives.
253pub trait CoreResource<T> {
254    /// The name of the resource component. Is
255    /// used as the path component on digestion.
256    fn name(&self) -> String;
257}
258
259impl<'a, T: Clone + Display> CoreResource<T> for ApiResource<'a, T> {
260    fn name(&self) -> String {
261        self.name.to_owned()
262    }
263}
264
265/// Allows resources to set their child and parent
266/// nodes.
267pub trait LinkedResource<'a, T: Display> {
268    /// The child `Resource` node.
269    fn child(&self) -> Option<&Self>;
270    /// The parent `Resource` node.
271    fn parent(&self) -> Option<&Self>;
272    /// If this is a child of another resource.
273    ///
274    /// Initialy created object should produce a
275    /// non-child node.
276    /// ```rust
277    /// use uri_resources::{ApiResource, LinkedResource};
278    /// let resource = ApiResource::<String>::new("resource");
279    /// assert_eq!(resource.is_child(), false)
280    /// ```
281    ///
282    /// Try to create an instance of two nodes
283    /// where one is related to the other as the
284    /// parent.
285    /// ```rust
286    /// use uri_resources::{ApiResource, LinkedResource};
287    /// let mut child = ApiResource::<String>::new("child_resource");
288    /// let parent = ApiResource::<String>::new("parent_resource")
289    ///     .with_child(&mut child);
290    /// assert_eq!(child.is_child(), true)
291    /// ```
292    fn is_child(&self) -> bool;
293    /// If this is the first resource of the path.
294    ///
295    /// Initialy created object should produce a
296    /// root node.
297    /// ```rust
298    /// use uri_resources::{ApiResource, LinkedResource};
299    /// let resource = ApiResource::<String>::new("resource");
300    /// assert_eq!(resource.is_root(), true)
301    /// ```
302    ///
303    /// Subsequent objects should not be a root
304    /// node.
305    /// ```rust
306    /// use uri_resources::{ApiResource, LinkedResource};
307    /// let mut child = ApiResource::<String>::new("child_resource");
308    /// let parent = ApiResource::<String>::new("parent_resource")
309    ///     .with_child(&mut child);
310    /// assert_ne!(child.is_root(), true)
311    /// ```
312    fn is_root(&self) -> bool;
313    /// If this is the last resource of the path.
314    ///
315    /// Root node can be a tail node if it is the
316    /// only resource node.
317    /// ```rust
318    /// use uri_resources::{ApiResource, LinkedResource};
319    /// let resource = ApiResource::<String>::new("resource");
320    /// assert!(resource.is_tail())
321    /// ```
322    ///
323    /// If there are otherwise child nodes, a root
324    /// node cannot be the 'tail'.
325    /// ```
326    /// use uri_resources::{ApiResource, LinkedResource};
327    /// let mut child0 = ApiResource::<String>::new("child_resource0");
328    /// let mut child1 = ApiResource::<String>::new("child_resource1");
329    ///
330    /// child0 = *child0.with_child(&mut child1).expect("resource node");
331    /// let parent = ApiResource::<String>::new("parent_resource")
332    ///     .with_child(&mut child0);
333    /// assert!(!parent.expect("parent node").is_tail())
334    /// ```
335    ///
336    /// The middle child cannot be the tail.
337    /// ```rust
338    /// use uri_resources::{ApiResource, LinkedResource};
339    /// let mut child0 = ApiResource::<String>::new("child_resource0");
340    /// let mut child1 = ApiResource::<String>::new("child_resource1");
341    ///
342    /// child0 = *child0.with_child(&mut child1).expect("resource node");
343    /// let parent = ApiResource::<String>::new("parent_resource")
344    ///     .with_child(&mut child0);
345    /// assert!(child0.is_child() && !child0.is_tail());
346    /// ```
347    ///
348    /// The last child should be the tail.
349    /// ```rust
350    /// use uri_resources::{ApiResource, LinkedResource};
351    /// let mut child0 = ApiResource::<String>::new("child_resource0");
352    /// let mut child1 = ApiResource::<String>::new("child_resource1");
353    ///
354    /// child0 = *child0.with_child(&mut child1).expect("resource node");
355    /// let parent = ApiResource::<String>::new("parent_resource")
356    ///     .with_child(&mut child0);
357    /// assert!(child1.is_child() && child1.is_tail())
358    /// ```
359    fn is_tail(&self) -> bool;
360    /// Adds a child node to this resource. Fails
361    /// if the child is already set.
362    fn with_child(&mut self, child: &mut ApiResource<'a, T>) -> Result<Box<Self>>;
363    /// Adds the parent node to this resource.
364    /// Fails if the parent is already set.
365    fn with_parent(&mut self, parent: &mut ApiResource<'a, T>) -> Result<Box<Self>>;
366}
367
368impl<'a, T: Debug + Display + Clone> LinkedResource<'a, T> for ApiResource<'a, T> {
369    fn child(&self) -> Option<&Self> {
370        self.child.as_deref()
371    }
372
373    fn parent(&self) -> Option<&Self> {
374        self.parent.as_deref()
375    }
376
377    fn is_child(&self) -> bool {
378        self.parent.is_some()
379    }
380
381    fn is_root(&self) -> bool {
382        self.parent.is_none()
383    }
384
385    fn is_tail(&self) -> bool {
386        self.child.is_none()
387    }
388
389    fn with_child(&mut self, child: &mut ApiResource<'a, T>) -> Result<Box<Self>> {
390        match self.child {
391            None => {
392                let mut new = self.clone();
393                match child.with_parent(new.borrow_mut()) {
394                    Ok(chld) => {
395                        new.child = Some(Box::new(chld.as_ref().clone()));
396                        Ok(Box::new(new))
397                    },
398                    Err(e) => Err(e)
399                }
400            },
401            Some(_) => Err(ResourceError::AlreadySet(self.name(), "child".into()).into())
402        }
403    }
404
405    fn with_parent(&mut self, parent: &mut ApiResource<'a, T>) -> Result<Box<Self>> {
406        match self.parent {
407            None => {
408                self.parent = Box::new(parent.clone()).into();
409                Ok(Box::new(self.clone()))
410            },
411            Some(_) => Err(ResourceError::AlreadySet(self.name(), "parent".into()).into())
412        }
413    }
414}
415
416/// Resource can be 'weighted'. This allows use
417/// in `uri_routes`, after digestion to sort
418/// paths in the final required. order.
419pub trait WeightedResource {
420    /// The sorting weight value of this.
421    fn weight(&self) -> f32;
422    /// Determines the ordering weight to be used
423    /// by pre-digestion sorting.
424    fn with_weight(&mut self, weight: f32) -> &Self;
425}
426
427impl<T: Display> WeightedResource for ApiResource<'_, T> {
428    fn weight(&self) -> f32 {
429        self.weight
430    }
431
432    fn with_weight(&mut self, weight: f32) -> &Self {
433        self.weight = weight;
434        self
435    }
436}
437
438pub trait Resource<'a, T: Clone + Display>:
439    CoreResource<T> +
440    ArgedResource<T> +
441    LinkedResource<'a, T> +
442    WeightedResource {}
443
444impl<'a, T: Clone + Debug + Display> Resource<'a, T> for ApiResource<'a, T> {}