Use enum for route resolution

This commit is contained in:
Edward Shen 2020-09-27 17:02:43 -04:00
parent abbd1d9fea
commit 7fdf451470
Signed by: edward
GPG key ID: 19182661E818369F
2 changed files with 53 additions and 18 deletions

View file

@ -38,6 +38,8 @@ pub struct Route {
pub path: String, pub path: String,
pub hidden: bool, pub hidden: bool,
pub description: Option<String>, pub description: Option<String>,
pub min_args: Option<usize>,
pub max_args: Option<usize>,
} }
impl FromStr for Route { impl FromStr for Route {
@ -48,6 +50,8 @@ impl FromStr for Route {
path: s.to_string(), path: s.to_string(),
hidden: false, hidden: false,
description: None, description: None,
min_args: None,
max_args: None,
}) })
} }
} }
@ -68,6 +72,8 @@ impl<'de> Deserialize<'de> for Route {
Path, Path,
Hidden, Hidden,
Description, Description,
MinArgs,
MaxArgs,
} }
struct RouteVisitor; struct RouteVisitor;
@ -83,7 +89,7 @@ impl<'de> Deserialize<'de> for Route {
where where
E: serde::de::Error, E: serde::de::Error,
{ {
// This is infalliable // This is infallable
Ok(Self::Value::from_str(path).unwrap()) Ok(Self::Value::from_str(path).unwrap())
} }
@ -94,6 +100,8 @@ impl<'de> Deserialize<'de> for Route {
let mut path = None; let mut path = None;
let mut hidden = None; let mut hidden = None;
let mut description = None; let mut description = None;
let mut min_args = None;
let mut max_args = None;
while let Some(key) = map.next_key()? { while let Some(key) = map.next_key()? {
match key { match key {
@ -115,6 +123,18 @@ impl<'de> Deserialize<'de> for Route {
} }
description = Some(map.next_value()?); description = Some(map.next_value()?);
} }
Field::MinArgs => {
if min_args.is_some() {
return Err(de::Error::duplicate_field("min_args"));
}
min_args = Some(map.next_value()?);
}
Field::MaxArgs => {
if max_args.is_some() {
return Err(de::Error::duplicate_field("max_args"));
}
max_args = Some(map.next_value()?);
}
} }
} }
@ -124,6 +144,8 @@ impl<'de> Deserialize<'de> for Route {
path, path,
hidden: hidden.unwrap_or_default(), hidden: hidden.unwrap_or_default(),
description, description,
min_args,
max_args,
}) })
} }
} }
@ -337,7 +359,7 @@ mod route {
fn serialize() { fn serialize() {
assert_eq!( assert_eq!(
&to_string(&Route::from_str("hello world").unwrap()).unwrap(), &to_string(&Route::from_str("hello world").unwrap()).unwrap(),
"---\nroute_type: External\npath: hello world\nhidden: false\ndescription: ~" "---\nroute_type: External\npath: hello world\nhidden: false\ndescription: ~\nmin_args: ~\nmax_args: ~"
); );
} }
} }

View file

@ -90,7 +90,7 @@ pub async fn hop(
let data = data.read().unwrap(); let data = data.read().unwrap();
match resolve_hop(&query.to, &data.routes, &data.default_route) { match resolve_hop(&query.to, &data.routes, &data.default_route) {
(Some(path), args) => { RouteResolution::Resolved { route: path, args } => {
let resolved_template = match path { let resolved_template = match path {
ConfigRoute { ConfigRoute {
route_type: RouteType::Internal, route_type: RouteType::Internal,
@ -126,10 +126,16 @@ pub async fn hop(
} }
} }
} }
(None, _) => HttpResponse::NotFound().body("not found"), RouteResolution::Unresolved => HttpResponse::NotFound().body("not found"),
} }
} }
#[derive(Debug, PartialEq)]
enum RouteResolution<'a> {
Resolved { route: &'a Route, args: String },
Unresolved,
}
/// Attempts to resolve the provided string into its route and its arguments. /// Attempts to resolve the provided string into its route and its arguments.
/// If a default route was provided, then this will consider that route before /// If a default route was provided, then this will consider that route before
/// failing to resolve a route. /// failing to resolve a route.
@ -140,21 +146,20 @@ fn resolve_hop<'a>(
query: &str, query: &str,
routes: &'a HashMap<String, Route>, routes: &'a HashMap<String, Route>,
default_route: &Option<String>, default_route: &Option<String>,
) -> (Option<&'a Route>, String) { ) -> RouteResolution<'a> {
let mut split_args = query.split_ascii_whitespace().peekable(); let mut split_args = query.split_ascii_whitespace().peekable();
let command = match split_args.peek() { let command = match split_args.peek() {
Some(command) => command, Some(command) => command,
None => { None => {
debug!("Found empty query, returning no route."); debug!("Found empty query, returning no route.");
return (None, String::new()); return RouteResolution::Unresolved;
} }
}; };
match (routes.get(*command), default_route) { match (routes.get(*command), default_route) {
// Found a route // Found a route
(Some(resolved), _) => ( (Some(resolved), _) => {
Some(resolved), let args = match split_args.next() {
match split_args.next() {
// Discard the first result, we found the route using the first arg // Discard the first result, we found the route using the first arg
Some(_) => { Some(_) => {
let args = split_args.collect::<Vec<&str>>().join(" "); let args = split_args.collect::<Vec<&str>>().join(" ");
@ -165,21 +170,26 @@ fn resolve_hop<'a>(
debug!("Resolved {} with no args", resolved); debug!("Resolved {} with no args", resolved);
String::new() String::new()
} }
}, };
),
RouteResolution::Resolved {
route: resolved,
args,
}
}
// Unable to find route, but had a default route // Unable to find route, but had a default route
(None, Some(route)) => { (None, Some(route)) => {
let args = split_args.collect::<Vec<&str>>().join(" "); let args = split_args.collect::<Vec<&str>>().join(" ");
debug!("Using default route {} with args {}", route, args); debug!("Using default route {} with args {}", route, args);
match routes.get(route) { match routes.get(route) {
Some(v) => (Some(v), args), Some(route) => RouteResolution::Resolved { route, args },
None => (None, String::new()), None => RouteResolution::Unresolved,
} }
} }
// No default route and no match // No default route and no match
(None, None) => { (None, None) => {
debug!("Failed to resolve route!"); debug!("Failed to resolve route!");
(None, String::new()) RouteResolution::Unresolved
} }
} }
} }
@ -211,15 +221,18 @@ mod resolve_hop {
fn generate_route_result<'a>( fn generate_route_result<'a>(
keyword: &'a Route, keyword: &'a Route,
args: &str, args: &str,
) -> (Option<&'a Route>, String) { ) -> RouteResolution<'a> {
(Some(keyword), String::from(args)) RouteResolution::Resolved {
route: keyword,
args: String::from(args),
}
} }
#[test] #[test]
fn empty_routes_no_default_yields_failed_hop() { fn empty_routes_no_default_yields_failed_hop() {
assert_eq!( assert_eq!(
resolve_hop("hello world", &HashMap::new(), &None), resolve_hop("hello world", &HashMap::new(), &None),
(None, String::new()) RouteResolution::Unresolved
); );
} }
@ -231,7 +244,7 @@ mod resolve_hop {
&HashMap::new(), &HashMap::new(),
&Some(String::from("google")) &Some(String::from("google"))
), ),
(None, String::new()) RouteResolution::Unresolved
); );
} }