use super::word::{pluralize, singularize};
use crate::error::Error;
use inflector::Inflector;
use regex::Regex;
pub struct Endpoint {
original: String,
version: Option<String>,
method: String,
resources: Vec<String>,
identifiers: Vec<String>,
}
impl Endpoint {
pub fn new(method: String, original_path: String) -> Result<Endpoint, Error> {
lazy_static! {
static ref METHOD: Regex =
Regex::new("^(get|head|post|put|delete|connect|options|trace|patch)$").unwrap();
static ref VERSION: Regex = Regex::new("^v([0-9]+)$").unwrap();
}
let path = original_path
.trim_matches('/')
.trim_matches('_')
.to_string();
if !METHOD.is_match(&method) || path.is_empty() {
return Err(Error::EndpointValidation { method, path });
}
let mut parts = path.split('/').collect::<Vec<&str>>();
let mut version = None;
if VERSION.is_match(parts.first().unwrap()) {
version = Some(parts.first().unwrap().to_string());
parts = parts.drain(1..).collect(); }
let resources: Vec<String> = parts
.clone()
.into_iter()
.filter(|s| !s.starts_with('{'))
.map(|s| s.to_string())
.collect();
let identifiers: Vec<String> = parts
.into_iter()
.filter(|s| s.starts_with('{'))
.map(|s| s.to_string())
.collect();
Ok(Endpoint {
original: original_path,
version,
method,
resources,
identifiers,
})
}
pub fn get_operation_id(&self, resource_method_version: bool) -> String {
let mut parts: Vec<String> = vec![];
if let Some(v) = self.version.clone() {
parts.push(v);
}
parts.push(
match self.method.as_str() {
"get" => {
if !self.original.ends_with('}') {
"list"
} else {
"get"
}
}
"post" => "create",
"patch" => "update",
m => m,
}
.to_string(),
);
let mut resources: Vec<String> = vec![];
for (i, resource) in self.resources.iter().enumerate() {
let processed = resource.clone().to_camel_case();
resources.push(
{
if i < self.identifiers.len() {
singularize(processed)
} else {
match self.method.as_str() {
"post" => singularize(processed),
"get" => processed,
_ => pluralize(processed),
}
}
}
.to_string(),
);
}
if !resource_method_version {
parts.append(&mut resources)
} else {
parts.reverse();
resources.append(&mut parts);
parts = resources;
};
parts
.into_iter()
.collect::<Vec<String>>()
.join(" ")
.to_camel_case()
}
}
#[cfg(test)]
mod tests {
use super::*;
use test_case::test_case;
#[test_case( "get".to_string(), "users/{id}".to_string(), "getUser".to_string(); "endpoint name test 1" )]
#[test_case( "post".to_string(), "users/{id}/groups".to_string(), "createUserGroup".to_string(); "endpoint name test 2" )]
#[test_case( "get".to_string(), "users/{id}/groups".to_string(), "listUserGroups".to_string(); "endpoint name test 3" )]
#[test_case( "patch".to_string(), "users/{id}/groups".to_string(), "updateUserGroups".to_string(); "endpoint name test 4" )]
#[test_case( "patch".to_string(), "users/{id}/groups/{id}".to_string(), "updateUserGroup".to_string(); "endpoint name test 5" )]
#[test_case( "get".to_string(), "users/{id}/groups/{id}".to_string(), "getUserGroup".to_string(); "endpoint name test 6" )]
#[test_case( "get".to_string(), "users".to_string(), "listUsers".to_string(); "endpoint name test 7" )]
#[test_case( "get".to_string(), "users/{id}".to_string(), "getUser".to_string(); "endpoint name test 8" )]
#[test_case( "get".to_string(), "v2/users".to_string(), "v2ListUsers".to_string(); "endpoint name test 9" )]
#[test_case( "get".to_string(), "v2/users/{id}".to_string(), "v2GetUser".to_string(); "endpoint name test 10" )]
#[test_case( "get".to_string(), "v1/users/{id}/status".to_string(), "v1ListUserStatus".to_string(); "endpoint name test 11" )]
#[test_case( "post".to_string(), "users/{id}/groups".to_string(), "createUserGroup".to_string(); "endpoint name test 12" )]
#[test_case( "get".to_string(), "user-groups/{id}".to_string(), "getUserGroup".to_string(); "endpoint name test 13" )]
#[test_case( "get".to_string(), "v1/users/{id}/statuses".to_string(), "v1ListUserStatuses".to_string(); "endpoint name test 14" )]
fn test_operation_name(method: String, path: String, expected: String) {
assert_eq!(
Endpoint::new(method, path).unwrap().get_operation_id(false),
expected
);
}
#[test_case( "get".to_string(), "/api/xyz/v1/namespaces".to_string(), "listApiXyzV1Namespaces".to_string(); "endpoint name non restful endpoint 1" )]
#[test_case( "get".to_string(), "/api/xyz/v1/namespaces/{namespaceId}".to_string(), "getApiXyzV1Namespaces".to_string(); "endpoint name non restful endpoint 2" )]
fn test_operation_name2(method: String, path: String, expected: String) {
assert_eq!(
Endpoint::new(method, path).unwrap().get_operation_id(false),
expected
);
}
#[test_case( "get".to_string(), "user-groups/{id}".to_string(), "userGroupGet".to_string(); "endpoint name reverse test 1" )]
#[test_case( "get".to_string(), "v1/users/{id}/statuses".to_string(), "userStatusesListV1".to_string(); "endpoint name reverse test 2" )]
fn test_operation_name_reverse(method: String, path: String, expected: String) {
assert_eq!(
Endpoint::new(method, path).unwrap().get_operation_id(true),
expected
);
}
}