use self::RenameRule::*;
use crate::{error::Error, request::Request, serde_request::from_request};
use async_trait::async_trait;
use cruet::Inflector;
use serde::Deserialize;
use std::str::FromStr;
#[async_trait]
pub trait Extractor<'de>: Deserialize<'de> {
fn metadata() -> &'de Metadata;
async fn extract(req: &'de mut Request) -> Result<Self, Error> {
from_request(req, Self::metadata()).await
}
async fn extract_with_arg(req: &'de mut Request, _arg: &str) -> Result<Self, Error> {
Self::extract(req).await
}
}
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
#[non_exhaustive]
pub enum SourceFrom {
Path,
Query,
Header,
#[cfg(feature = "cookie")]
Cookie,
Body,
}
impl FromStr for SourceFrom {
type Err = Error;
fn from_str(input: &str) -> Result<Self, Self::Err> {
match input {
"path" => Ok(Self::Path),
"query" => Ok(Self::Query),
"header" => Ok(Self::Header),
#[cfg(feature = "cookie")]
"cookie" => Ok(Self::Cookie),
"body" => Ok(Self::Body),
_ => Err(Error::Other(format!("invalid source from `{input}`"))),
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[non_exhaustive]
pub enum RenameRule {
LowerCase,
UpperCase,
PascalCase,
CamelCase,
SnakeCase,
ScreamingSnakeCase,
KebabCase,
ScreamingKebabCase,
}
impl FromStr for RenameRule {
type Err = Error;
fn from_str(input: &str) -> Result<Self, Self::Err> {
for (name, rule) in RENAME_RULES {
if input == *name {
return Ok(*rule);
}
}
Err(Error::Other(format!("invalid rename rule: {input}")))
}
}
static RENAME_RULES: &[(&str, RenameRule)] = &[
("lowercase", LowerCase),
("UPPERCASE", UpperCase),
("PascalCase", PascalCase),
("camelCase", CamelCase),
("snake_case", SnakeCase),
("SCREAMING_SNAKE_CASE", ScreamingSnakeCase),
("kebab-case", KebabCase),
("SCREAMING-KEBAB-CASE", ScreamingKebabCase),
];
impl RenameRule {
pub fn rename(&self, name: impl AsRef<str>) -> String {
let name = name.as_ref();
match *self {
PascalCase => name.to_pascal_case(),
LowerCase => name.to_lowercase(),
UpperCase => name.to_uppercase(),
CamelCase => name.to_camel_case(),
SnakeCase => name.to_snake_case(),
ScreamingSnakeCase => SnakeCase.rename(name).to_ascii_uppercase(),
KebabCase => SnakeCase.rename(name).replace('_', "-"),
ScreamingKebabCase => ScreamingSnakeCase.rename(name).replace('_', "-"),
}
}
}
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
#[non_exhaustive]
pub enum SourceParser {
MultiMap,
Json,
Smart,
}
impl FromStr for SourceParser {
type Err = Error;
fn from_str(input: &str) -> Result<Self, Self::Err> {
match input {
"multimap" => Ok(Self::MultiMap),
"json" => Ok(Self::Json),
"smart" => Ok(Self::Smart),
_ => Err(Error::Other("invalid source format".to_owned())),
}
}
}
#[derive(Default, Clone, Debug)]
#[non_exhaustive]
pub struct Metadata {
pub name: &'static str,
pub default_sources: Vec<Source>,
pub fields: Vec<Field>,
pub rename_all: Option<RenameRule>,
}
impl Metadata {
pub const fn new(name: &'static str) -> Self {
Self {
name,
default_sources: vec![],
fields: vec![],
rename_all: None,
}
}
pub fn default_sources(mut self, default_sources: Vec<Source>) -> Self {
self.default_sources = default_sources;
self
}
pub fn fields(mut self, fields: Vec<Field>) -> Self {
self.fields = fields;
self
}
pub fn add_default_source(mut self, source: Source) -> Self {
self.default_sources.push(source);
self
}
pub fn add_field(mut self, field: Field) -> Self {
self.fields.push(field);
self
}
pub fn rename_all(mut self, rename_all: impl Into<Option<RenameRule>>) -> Self {
self.rename_all = rename_all.into();
self
}
pub(crate) fn has_body_required(&self) -> bool {
if self
.default_sources
.iter()
.any(|s| s.from == SourceFrom::Body)
{
return true;
}
self.fields.iter().any(|f| f.has_body_required())
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct Field {
pub name: &'static str,
pub flatten: bool,
pub sources: Vec<Source>,
pub aliases: Vec<&'static str>,
pub rename: Option<&'static str>,
pub metadata: Option<&'static Metadata>,
}
impl Field {
pub fn new(name: &'static str) -> Self {
Self::with_sources(name, vec![])
}
pub fn with_sources(name: &'static str, sources: Vec<Source>) -> Self {
Self {
name,
flatten: false,
sources,
aliases: vec![],
rename: None,
metadata: None,
}
}
pub fn set_flatten(mut self, flatten: bool) -> Self {
self.flatten = flatten;
self
}
pub fn metadata(mut self, metadata: &'static Metadata) -> Self {
self.metadata = Some(metadata);
self
}
pub fn add_source(mut self, source: Source) -> Self {
self.sources.push(source);
self
}
pub fn set_aliases(mut self, aliases: Vec<&'static str>) -> Self {
self.aliases = aliases;
self
}
pub fn add_alias(mut self, alias: &'static str) -> Self {
self.aliases.push(alias);
self
}
pub fn rename(mut self, rename: &'static str) -> Self {
self.rename = Some(rename);
self
}
pub(crate) fn has_body_required(&self) -> bool {
self.sources.iter().any(|s| s.from == SourceFrom::Body)
}
}
#[derive(Copy, Clone, Debug)]
#[non_exhaustive]
pub struct Source {
pub from: SourceFrom,
pub parser: SourceParser,
}
impl Source {
pub fn new(from: SourceFrom, format: SourceParser) -> Self {
Self {
from,
parser: format,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_source_from() {
for (key, value) in [
("path", SourceFrom::Path),
("query", SourceFrom::Query),
("header", SourceFrom::Header),
#[cfg(feature = "cookie")]
("cookie", SourceFrom::Cookie),
("body", SourceFrom::Body),
] {
assert_eq!(key.parse::<SourceFrom>().unwrap(), value);
}
assert!("abcd".parse::<SourceFrom>().is_err());
}
#[test]
fn test_parse_source_format() {
for (key, value) in [
("multimap", SourceParser::MultiMap),
("json", SourceParser::Json),
] {
assert_eq!(key.parse::<SourceParser>().unwrap(), value);
}
assert!("abcd".parse::<SourceParser>().is_err());
}
#[test]
fn test_parse_rename_rule() {
for (key, value) in RENAME_RULES {
assert_eq!(key.parse::<RenameRule>().unwrap(), *value);
}
assert!("abcd".parse::<RenameRule>().is_err());
}
#[test]
fn test_rename_rule() {
assert_eq!(PascalCase.rename("rename_rule"), "RenameRule");
assert_eq!(LowerCase.rename("RenameRule"), "renamerule");
assert_eq!(UpperCase.rename("rename_rule"), "RENAME_RULE");
assert_eq!(CamelCase.rename("RenameRule"), "renameRule");
assert_eq!(SnakeCase.rename("RenameRule"), "rename_rule");
assert_eq!(ScreamingSnakeCase.rename("rename_rule"), "RENAME_RULE");
assert_eq!(KebabCase.rename("rename_rule"), "rename-rule");
assert_eq!(ScreamingKebabCase.rename("rename_rule"), "RENAME-RULE");
}
}