use std::borrow::Cow;
use std::fmt;
use serde_derive::{Deserialize, Serialize};
use shrinkwraprs::Shrinkwrap;
use crate::errors::*;
use crate::model::io::IOType;
use crate::model::name::Name;
use crate::model::validation::Validate;
#[derive(Shrinkwrap, Hash, Debug, PartialEq, Ord, PartialOrd, Eq, Clone, Default, Serialize, Deserialize)]
#[shrinkwrap(mutable)]
pub struct Route(pub String);
pub enum RouteType {
FlowInput(Name, Route),
FlowOutput(Name),
SubProcess(Name, Route)
}
impl Route {
pub fn sub_route_of(&self, other: &Route) -> Option<Route> {
if self == other {
return Some(Route::from(""));
}
self.strip_prefix(&format!("{other}/")).map(Route::from)
}
pub fn insert<R: AsRef<str>>(&mut self, sub_route: R) -> &Self {
self.insert_str(0, sub_route.as_ref());
self
}
pub fn extend(&mut self, sub_route: &Route) -> &Self {
if !sub_route.is_empty() {
if !self.to_string().ends_with('/') && !sub_route.starts_with('/') {
self.push('/');
}
self.push_str(sub_route);
}
self
}
pub fn route_type(&self) -> Result<RouteType> {
let segments: Vec<&str> = self.split('/').collect();
match segments[0] {
"input" => Ok(RouteType::FlowInput(segments[1].into(),
segments[2..].join("/").into())),
"output" => Ok(RouteType::FlowOutput(segments[1].into())),
"" => bail!("Invalid route '{}' - 'input' or 'output' or a valid sub-process name \
must be specified in the route", self),
process_name => Ok(RouteType::SubProcess(process_name.into(),
segments[1..].join("/").into())),
}
}
pub fn pop(&self) -> (Cow<Route>, Option<Route>) {
let mut segments: Vec<&str> = self.split('/').collect();
let sub_route = segments.pop();
match sub_route {
None => (Cow::Borrowed(self), None),
Some("") => (Cow::Borrowed(self), None),
Some(sr) => (
Cow::Owned(Route::from(segments.join("/"))),
Some(Route::from(sr)),
),
}
}
pub fn without_trailing_array_index(&self) -> (Cow<Route>, usize, bool) {
let mut parts: Vec<&str> = self.split('/').collect();
if let Some(last_part) = parts.pop() {
if let Ok(index) = last_part.parse::<usize>() {
let route_without_number = parts.join("/");
return (Cow::Owned(Route::from(route_without_number)), index, true);
}
}
(Cow::Borrowed(self), 0, false)
}
pub fn is_array_selector(&self) -> bool {
if self.is_empty() {
return false;
}
let mut parts: Vec<&str> = self.split('/').collect();
if let Some(last_part) = parts.pop() {
return last_part.parse::<usize>().is_ok();
}
false
}
}
impl AsRef<str> for Route {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Validate for Route {
fn validate(&self) -> Result<()> {
self.route_type()?;
if self.parse::<usize>().is_ok() {
bail!("Route '{}' is invalid - cannot be an integer", self);
}
Ok(())
}
}
pub trait HasRoute {
fn route(&self) -> &Route;
fn route_mut(&mut self) -> &mut Route;
}
pub trait SetRoute {
fn set_routes_from_parent(&mut self, parent: &Route);
}
#[allow(clippy::upper_case_acronyms)]
pub trait SetIORoutes {
fn set_io_routes_from_parent(&mut self, parent: &Route, io_type: IOType);
}
impl fmt::Display for Route {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<&str> for Route {
fn from(string: &str) -> Self {
Route(string.to_string())
}
}
impl From<String> for Route {
fn from(string: String) -> Self {
Route(string)
}
}
impl From<&String> for Route {
fn from(string: &String) -> Self {
Route(string.to_string())
}
}
impl From<&Name> for Route {
fn from(name: &Name) -> Self {
Route(name.to_string())
}
}
#[cfg(test)]
mod test {
use crate::model::name::Name;
use crate::model::validation::Validate;
use super::Route;
#[test]
fn test_from_string() {
let route = Route::from("my-route".to_string());
assert_eq!(route, Route::from("my-route"));
}
#[test]
fn test_from_ref_string() {
let route = Route::from(&format!("{}{}", "my-route", "/subroute"));
assert_eq!(route, Route::from("my-route/subroute"));
}
#[test]
fn test_from_name() {
let name = Name::from("my-route-name");
assert_eq!(Route::from(&name), Route::from("my-route-name"));
}
#[test]
fn test_route_pop() {
let original = Route::from("/root/function/output/subroute");
let (level_up, sub) = original.pop();
assert_eq!(
level_up.into_owned(),
Route::from("/root/function/output")
);
assert_eq!(sub, Some(Route::from("subroute")));
}
#[test]
fn test_root_route_pop() {
let original = Route::from("/");
let (level_up, sub) = original.pop();
assert_eq!(level_up.into_owned(), Route::from("/"));
assert_eq!(sub, None);
}
#[test]
fn test_empty_route_pop() {
let original = Route::from("");
let (level_up, sub) = original.pop();
assert_eq!(level_up.into_owned(), Route::from(""));
assert_eq!(sub, None);
}
#[test]
fn no_path_no_change() {
let route = Route::from("");
let (new_route, _num, trailing_number) = route.without_trailing_array_index();
assert_eq!(new_route.into_owned(), Route::default());
assert!(!trailing_number);
}
#[test]
fn just_slash_no_change() {
let route = Route::from("/");
let (new_route, _num, trailing_number) = route.without_trailing_array_index();
assert_eq!(new_route.into_owned(), Route::from("/"));
assert!(!trailing_number);
}
#[test]
fn no_trailing_number_no_change() {
let route = Route::from("/output1");
let (new_route, _num, trailing_number) = route.without_trailing_array_index();
assert_eq!(new_route.into_owned(), route);
assert!(!trailing_number);
}
#[test]
fn detect_array_at_output_root() {
let route = Route::from("/0");
let (new_route, num, trailing_number) = route.without_trailing_array_index();
assert_eq!(new_route.into_owned(), Route::from(""));
assert_eq!(num, 0);
assert!(trailing_number);
}
#[test]
fn detect_array_at_output_subroute() {
let route = Route::from("/array_output/0");
let (new_route, num, trailing_number) = route.without_trailing_array_index();
assert_eq!(new_route.into_owned(), Route::from("/array_output"));
assert_eq!(num, 0);
assert!(trailing_number);
}
#[test]
fn valid_process_route() {
let route = Route::from("sub_process/i1");
assert!(route.validate().is_ok());
}
#[test]
fn valid_process_route_with_subroute() {
let route = Route::from("sub_process/i1/sub_route");
assert!(route.validate().is_ok());
}
#[test]
fn valid_input_route() {
let route = Route::from("input/i1");
assert!(route.validate().is_ok());
}
#[test]
fn valid_input_route_with_subroute() {
let route = Route::from("input/i1/sub_route");
assert!(route.validate().is_ok());
}
#[test]
fn valid_output_route() {
let route = Route::from("output/i1");
assert!(route.validate().is_ok());
}
#[test]
fn valid_output_route_with_subroute() {
let route = Route::from("output/i1/sub_route");
assert!(route.validate().is_ok());
}
#[test]
fn validate_invalid_empty_route() {
let route = Route::from("");
assert!(route.validate().is_err());
}
#[test]
fn validate_invalid_route() {
let route = Route::from("123");
assert!(route.validate().is_err());
}
#[test]
fn subroute_equal_route() {
let route = Route::from("/root/function");
assert!(route
.sub_route_of(&Route::from("/root/function"))
.is_some())
}
#[test]
fn subroute_distinct_route() {
let route = Route::from("/root/function");
assert!(route.sub_route_of(&Route::from("/root/foo")).is_none())
}
#[test]
fn subroute_extended_name_route() {
let route = Route::from("/root/function_foo");
assert!(route
.sub_route_of(&Route::from("/root/function"))
.is_none())
}
#[test]
fn is_a_subroute() {
let route = Route::from("/root/function/input");
assert!(route
.sub_route_of(&Route::from("/root/function"))
.is_some())
}
#[test]
fn is_a_sub_subroute() {
let route = Route::from("/root/function/input/element");
assert!(route
.sub_route_of(&Route::from("/root/function"))
.is_some())
}
#[test]
fn is_array_element_subroute() {
let route = Route::from("/root/function/1");
assert!(route
.sub_route_of(&Route::from("/root/function"))
.is_some())
}
#[test]
fn is_array_element_sub_subroute() {
let route = Route::from("/root/function/input/1");
assert!(route
.sub_route_of(&Route::from("/root/function"))
.is_some())
}
#[test]
fn extend_empty_route() {
let mut route = Route::default();
route.extend(&Route::from("sub"));
assert_eq!(route, Route::from("/sub"));
}
#[test]
fn extend_root_route() {
let mut route = Route::from("/");
route.extend(&Route::from("sub"));
assert_eq!(route, Route::from("/sub"));
}
#[test]
fn extend_route() {
let mut route = Route::from("/root/function");
route.extend(&Route::from("sub"));
assert_eq!(route, Route::from("/root/function/sub"));
}
#[test]
fn extend_route_with_nothing() {
let mut route = Route::from("/root/function");
route.extend(&Route::from(""));
assert_eq!(route, Route::from("/root/function"));
}
}