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> {}