From d55b0897e9dd93aa59c960fcb561ac403f18b4cc Mon Sep 17 00:00:00 2001 From: Dominic Date: Tue, 15 Sep 2020 15:10:41 +0200 Subject: [PATCH] update to gotham 0.5 and start using rustfmt --- Cargo.toml | 11 +- derive/src/from_body.rs | 82 ++--- derive/src/lib.rs | 64 ++-- derive/src/method.rs | 357 +++++++++---------- derive/src/openapi_type.rs | 160 ++++----- derive/src/request_body.rs | 49 ++- derive/src/resource.rs | 53 +-- derive/src/resource_error.rs | 243 +++++++------ derive/src/util.rs | 40 +-- example/src/main.rs | 120 +++---- rustfmt.toml | 19 + src/auth.rs | 260 ++++++-------- src/cors.rs | 102 +++--- src/lib.rs | 64 +--- src/matcher/access_control_request_method.rs | 64 ++-- src/matcher/mod.rs | 1 - src/openapi/builder.rs | 121 +++---- src/openapi/handler.rs | 47 +-- src/openapi/mod.rs | 3 +- src/openapi/operation.rs | 168 ++++----- src/openapi/router.rs | 167 +++++---- src/openapi/types.rs | 142 ++++---- src/resource.rs | 97 ++--- src/response.rs | 41 +-- src/result/auth_result.rs | 18 +- src/result/mod.rs | 118 +++--- src/result/no_content.rs | 77 ++-- src/result/raw.rs | 90 ++--- src/result/result.rs | 95 +++-- src/result/success.rs | 98 ++--- src/routing.rs | 345 +++++++++--------- src/types.rs | 69 ++-- tests/async_methods.rs | 97 ++--- tests/cors_handling.rs | 193 +++++++--- tests/custom_request_body.rs | 28 +- tests/openapi_specification.rs | 59 ++- tests/openapi_supports_scope.rs | 24 +- tests/sync_methods.rs | 97 ++--- tests/trybuild_ui.rs | 10 +- 39 files changed, 1798 insertions(+), 2095 deletions(-) create mode 100644 rustfmt.toml diff --git a/Cargo.toml b/Cargo.toml index 1a28e4a..61682b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,12 +20,12 @@ gitlab = { repository = "msrd0/gotham-restful", branch = "master" } [dependencies] base64 = { version = "0.12.1", optional = true } chrono = { version = "0.4.11", features = ["serde"], optional = true } -cookie = { version = "0.13.3", optional = true } +cookie = { version = "0.14", optional = true } futures-core = "0.3.5" futures-util = "0.3.5" -gotham = { version = "0.5.0-rc.1", default-features = false } -gotham_derive = "0.5.0-rc.1" -gotham_middleware_diesel = { version = "0.1.2", optional = true } +gotham = { version = "0.5.0", default-features = false } +gotham_derive = "0.5.0" +gotham_middleware_diesel = { version = "0.2.0", optional = true } gotham_restful_derive = { version = "0.1.0-rc0" } indexmap = { version = "1.3.2", optional = true } itertools = "0.9.0" @@ -40,7 +40,7 @@ uuid = { version = "0.8.1", optional = true } [dev-dependencies] diesel = { version = "1.4.4", features = ["postgres"] } futures-executor = "0.3.5" -paste = "0.1.12" +paste = "1.0" thiserror = "1.0.18" trybuild = "1.0.27" @@ -56,5 +56,6 @@ openapi = ["gotham_restful_derive/openapi", "indexmap", "openapiv3"] all-features = true [patch.crates-io] +gotham = { path = "../gotham/gotham" } gotham_restful = { path = "." } gotham_restful_derive = { path = "./derive" } diff --git a/derive/src/from_body.rs b/derive/src/from_body.rs index f7c04ad..a4ec7b2 100644 --- a/derive/src/from_body.rs +++ b/derive/src/from_body.rs @@ -1,73 +1,65 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote}; use std::cmp::min; -use syn::{ - spanned::Spanned, - Data, - DeriveInput, - Error, - Field, - Fields, - Ident, - Result, - Type -}; +use syn::{spanned::Spanned, Data, DeriveInput, Error, Field, Fields, Ident, Result, Type}; -struct ParsedFields -{ - fields : Vec<(Ident, Type)>, - named : bool +struct ParsedFields { + fields: Vec<(Ident, Type)>, + named: bool } -impl ParsedFields -{ - fn from_named(fields : I) -> Self +impl ParsedFields { + fn from_named(fields: I) -> Self where - I : Iterator + I: Iterator { let fields = fields.map(|field| (field.ident.unwrap(), field.ty)).collect(); Self { fields, named: true } } - - fn from_unnamed(fields : I) -> Self + + fn from_unnamed(fields: I) -> Self where - I : Iterator + I: Iterator { - let fields = fields.enumerate().map(|(i, field)| (format_ident!("arg{}", i), field.ty)).collect(); + let fields = fields + .enumerate() + .map(|(i, field)| (format_ident!("arg{}", i), field.ty)) + .collect(); Self { fields, named: false } } - - fn from_unit() -> Self - { - Self { fields: Vec::new(), named: false } + + fn from_unit() -> Self { + Self { + fields: Vec::new(), + named: false + } } } -pub fn expand_from_body(input : DeriveInput) -> Result -{ +pub fn expand_from_body(input: DeriveInput) -> Result { let krate = super::krate(); let ident = input.ident; let generics = input.generics; - + let strukt = match input.data { Data::Enum(inum) => Err(inum.enum_token.span()), Data::Struct(strukt) => Ok(strukt), Data::Union(uni) => Err(uni.union_token.span()) - }.map_err(|span| Error::new(span, "#[derive(FromBody)] only works for structs"))?; - + } + .map_err(|span| Error::new(span, "#[derive(FromBody)] only works for structs"))?; + let fields = match strukt.fields { Fields::Named(named) => ParsedFields::from_named(named.named.into_iter()), Fields::Unnamed(unnamed) => ParsedFields::from_unnamed(unnamed.unnamed.into_iter()), Fields::Unit => ParsedFields::from_unit() }; - + let mut where_clause = quote!(); let mut block = quote!(); let mut body_ident = format_ident!("_body"); let mut type_ident = format_ident!("_type"); - - if let Some(body_field) = fields.fields.get(0) - { + + if let Some(body_field) = fields.fields.get(0) { body_ident = body_field.0.clone(); let body_ty = &body_field.1; where_clause = quote! { @@ -80,9 +72,8 @@ pub fn expand_from_body(input : DeriveInput) -> Result let #body_ident : #body_ty = #body_ident.into(); }; } - - if let Some(type_field) = fields.fields.get(1) - { + + if let Some(type_field) = fields.fields.get(1) { type_ident = type_field.0.clone(); let type_ty = &type_field.1; where_clause = quote! { @@ -94,9 +85,8 @@ pub fn expand_from_body(input : DeriveInput) -> Result let #type_ident : #type_ty = #type_ident.into(); }; } - - for field in &fields.fields[min(2, fields.fields.len())..] - { + + for field in &fields.fields[min(2, fields.fields.len())..] { let field_ident = &field.0; let field_ty = &field.1; where_clause = quote! { @@ -108,20 +98,20 @@ pub fn expand_from_body(input : DeriveInput) -> Result let #field_ident : #field_ty = Default::default(); }; } - - let field_names : Vec<&Ident> = fields.fields.iter().map(|field| &field.0).collect(); + + let field_names: Vec<&Ident> = fields.fields.iter().map(|field| &field.0).collect(); let ctor = if fields.named { quote!(Self { #(#field_names),* }) } else { quote!(Self ( #(#field_names),* )) }; - + Ok(quote! { impl #generics #krate::FromBody for #ident #generics where #where_clause { type Err = ::std::convert::Infallible; - + fn from_body(#body_ident : #krate::gotham::hyper::body::Bytes, #type_ident : #krate::Mime) -> Result { #block diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 7e0dc00..c3f313a 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -21,114 +21,96 @@ mod openapi_type; use openapi_type::expand_openapi_type; #[inline] -fn print_tokens(tokens : TokenStream2) -> TokenStream -{ +fn print_tokens(tokens: TokenStream2) -> TokenStream { //eprintln!("{}", tokens); tokens.into() } #[inline] -fn expand_derive(input : TokenStream, expand : F) -> TokenStream +fn expand_derive(input: TokenStream, expand: F) -> TokenStream where - F : FnOnce(DeriveInput) -> Result + F: FnOnce(DeriveInput) -> Result { - print_tokens(expand(parse_macro_input!(input)) - .unwrap_or_else(|err| err.to_compile_error())) + print_tokens(expand(parse_macro_input!(input)).unwrap_or_else(|err| err.to_compile_error())) } #[inline] -fn expand_macro(attrs : TokenStream, item : TokenStream, expand : F) -> TokenStream +fn expand_macro(attrs: TokenStream, item: TokenStream, expand: F) -> TokenStream where - F : FnOnce(A, I) -> Result, - A : ParseMacroInput, - I : ParseMacroInput + F: FnOnce(A, I) -> Result, + A: ParseMacroInput, + I: ParseMacroInput { - print_tokens(expand(parse_macro_input!(attrs), parse_macro_input!(item)) - .unwrap_or_else(|err| err.to_compile_error())) + print_tokens(expand(parse_macro_input!(attrs), parse_macro_input!(item)).unwrap_or_else(|err| err.to_compile_error())) } #[inline] -fn krate() -> TokenStream2 -{ +fn krate() -> TokenStream2 { quote!(::gotham_restful) } #[proc_macro_derive(FromBody)] -pub fn derive_from_body(input : TokenStream) -> TokenStream -{ +pub fn derive_from_body(input: TokenStream) -> TokenStream { expand_derive(input, expand_from_body) } #[cfg(feature = "openapi")] #[proc_macro_derive(OpenapiType, attributes(openapi))] -pub fn derive_openapi_type(input : TokenStream) -> TokenStream -{ +pub fn derive_openapi_type(input: TokenStream) -> TokenStream { expand_derive(input, expand_openapi_type) } #[proc_macro_derive(RequestBody, attributes(supported_types))] -pub fn derive_request_body(input : TokenStream) -> TokenStream -{ +pub fn derive_request_body(input: TokenStream) -> TokenStream { expand_derive(input, expand_request_body) } #[proc_macro_derive(Resource, attributes(resource))] -pub fn derive_resource(input : TokenStream) -> TokenStream -{ +pub fn derive_resource(input: TokenStream) -> TokenStream { expand_derive(input, expand_resource) } #[proc_macro_derive(ResourceError, attributes(display, from, status))] -pub fn derive_resource_error(input : TokenStream) -> TokenStream -{ +pub fn derive_resource_error(input: TokenStream) -> TokenStream { expand_derive(input, expand_resource_error) } - #[proc_macro_attribute] -pub fn read_all(attr : TokenStream, item : TokenStream) -> TokenStream -{ +pub fn read_all(attr: TokenStream, item: TokenStream) -> TokenStream { expand_macro(attr, item, |attr, item| expand_method(Method::ReadAll, attr, item)) } #[proc_macro_attribute] -pub fn read(attr : TokenStream, item : TokenStream) -> TokenStream -{ +pub fn read(attr: TokenStream, item: TokenStream) -> TokenStream { expand_macro(attr, item, |attr, item| expand_method(Method::Read, attr, item)) } #[proc_macro_attribute] -pub fn search(attr : TokenStream, item : TokenStream) -> TokenStream -{ +pub fn search(attr: TokenStream, item: TokenStream) -> TokenStream { expand_macro(attr, item, |attr, item| expand_method(Method::Search, attr, item)) } #[proc_macro_attribute] -pub fn create(attr : TokenStream, item : TokenStream) -> TokenStream -{ +pub fn create(attr: TokenStream, item: TokenStream) -> TokenStream { expand_macro(attr, item, |attr, item| expand_method(Method::Create, attr, item)) } #[proc_macro_attribute] -pub fn change_all(attr : TokenStream, item : TokenStream) -> TokenStream -{ +pub fn change_all(attr: TokenStream, item: TokenStream) -> TokenStream { expand_macro(attr, item, |attr, item| expand_method(Method::ChangeAll, attr, item)) } #[proc_macro_attribute] -pub fn change(attr : TokenStream, item : TokenStream) -> TokenStream -{ +pub fn change(attr: TokenStream, item: TokenStream) -> TokenStream { expand_macro(attr, item, |attr, item| expand_method(Method::Change, attr, item)) } #[proc_macro_attribute] -pub fn remove_all(attr : TokenStream, item : TokenStream) -> TokenStream -{ +pub fn remove_all(attr: TokenStream, item: TokenStream) -> TokenStream { expand_macro(attr, item, |attr, item| expand_method(Method::RemoveAll, attr, item)) } #[proc_macro_attribute] -pub fn remove(attr : TokenStream, item : TokenStream) -> TokenStream -{ +pub fn remove(attr: TokenStream, item: TokenStream) -> TokenStream { expand_macro(attr, item, |attr, item| expand_method(Method::Remove, attr, item)) } diff --git a/derive/src/method.rs b/derive/src/method.rs index be19e0b..dc14359 100644 --- a/derive/src/method.rs +++ b/derive/src/method.rs @@ -2,26 +2,13 @@ use crate::util::CollectToResult; use heck::{CamelCase, SnakeCase}; use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, quote}; -use syn::{ - spanned::Spanned, - Attribute, - AttributeArgs, - Error, - FnArg, - ItemFn, - Lit, - LitBool, - Meta, - NestedMeta, - PatType, - Result, - ReturnType, - Type -}; use std::str::FromStr; +use syn::{ + spanned::Spanned, Attribute, AttributeArgs, Error, FnArg, ItemFn, Lit, LitBool, Meta, NestedMeta, PatType, Result, + ReturnType, Type +}; -pub enum Method -{ +pub enum Method { ReadAll, Read, Search, @@ -32,12 +19,10 @@ pub enum Method Remove } -impl FromStr for Method -{ +impl FromStr for Method { type Err = Error; - - fn from_str(str : &str) -> Result - { + + fn from_str(str: &str) -> Result { match str { "ReadAll" | "read_all" => Ok(Self::ReadAll), "Read" | "read" => Ok(Self::Read), @@ -52,12 +37,10 @@ impl FromStr for Method } } -impl Method -{ - pub fn type_names(&self) -> Vec<&'static str> - { +impl Method { + pub fn type_names(&self) -> Vec<&'static str> { use Method::*; - + match self { ReadAll | RemoveAll => vec![], Read | Remove => vec!["ID"], @@ -66,11 +49,10 @@ impl Method Change => vec!["ID", "Body"] } } - - pub fn trait_ident(&self) -> Ident - { + + pub fn trait_ident(&self) -> Ident { use Method::*; - + let name = match self { ReadAll => "ReadAll", Read => "Read", @@ -83,11 +65,10 @@ impl Method }; format_ident!("Resource{}", name) } - - pub fn fn_ident(&self) -> Ident - { + + pub fn fn_ident(&self) -> Ident { use Method::*; - + let name = match self { ReadAll => "read_all", Read => "read", @@ -100,26 +81,26 @@ impl Method }; format_ident!("{}", name) } - - pub fn mod_ident(&self, resource : &str) -> Ident - { - format_ident!("_gotham_restful_resource_{}_method_{}", resource.to_snake_case(), self.fn_ident()) + + pub fn mod_ident(&self, resource: &str) -> Ident { + format_ident!( + "_gotham_restful_resource_{}_method_{}", + resource.to_snake_case(), + self.fn_ident() + ) } - - pub fn handler_struct_ident(&self, resource : &str) -> Ident - { + + pub fn handler_struct_ident(&self, resource: &str) -> Ident { format_ident!("{}{}Handler", resource.to_camel_case(), self.trait_ident()) } - - pub fn setup_ident(&self, resource : &str) -> Ident - { + + pub fn setup_ident(&self, resource: &str) -> Ident { format_ident!("{}_{}_setup_impl", resource.to_snake_case(), self.fn_ident()) } } #[allow(clippy::large_enum_variant)] -enum MethodArgumentType -{ +enum MethodArgumentType { StateRef, StateMutRef, MethodArg(Type), @@ -128,116 +109,111 @@ enum MethodArgumentType AuthStatusRef(Type) } -impl MethodArgumentType -{ - fn is_state_ref(&self) -> bool - { +impl MethodArgumentType { + fn is_state_ref(&self) -> bool { matches!(self, Self::StateRef | Self::StateMutRef) } - - fn is_method_arg(&self) -> bool - { + + fn is_method_arg(&self) -> bool { matches!(self, Self::MethodArg(_)) } - - fn is_database_conn(&self) -> bool - { + + fn is_database_conn(&self) -> bool { matches!(self, Self::DatabaseConnection(_)) } - - fn is_auth_status(&self) -> bool - { + + fn is_auth_status(&self) -> bool { matches!(self, Self::AuthStatus(_) | Self::AuthStatusRef(_)) } - - fn ty(&self) -> Option<&Type> - { + + fn ty(&self) -> Option<&Type> { match self { Self::MethodArg(ty) | Self::DatabaseConnection(ty) | Self::AuthStatus(ty) | Self::AuthStatusRef(ty) => Some(ty), _ => None } } - - fn quote_ty(&self) -> Option - { + + fn quote_ty(&self) -> Option { self.ty().map(|ty| quote!(#ty)) } } -struct MethodArgument -{ - ident : Ident, - ident_span : Span, - ty : MethodArgumentType +struct MethodArgument { + ident: Ident, + ident_span: Span, + ty: MethodArgumentType } -impl Spanned for MethodArgument -{ - fn span(&self) -> Span - { +impl Spanned for MethodArgument { + fn span(&self) -> Span { self.ident_span } } -fn interpret_arg_ty(attrs : &[Attribute], name : &str, ty : Type) -> Result -{ - let attr = attrs.iter() +fn interpret_arg_ty(attrs: &[Attribute], name: &str, ty: Type) -> Result { + let attr = attrs + .iter() .find(|arg| arg.path.segments.iter().any(|path| &path.ident.to_string() == "rest_arg")) .map(|arg| arg.tokens.to_string()); - + // TODO issue a warning for _state usage once diagnostics become stable - if attr.as_deref() == Some("state") || (attr.is_none() && (name == "state" || name == "_state")) - { + if attr.as_deref() == Some("state") || (attr.is_none() && (name == "state" || name == "_state")) { return match ty { - Type::Reference(ty) => Ok(if ty.mutability.is_none() { MethodArgumentType::StateRef } else { MethodArgumentType::StateMutRef }), - _ => Err(Error::new(ty.span(), "The state parameter has to be a (mutable) reference to gotham_restful::State")) + Type::Reference(ty) => Ok(if ty.mutability.is_none() { + MethodArgumentType::StateRef + } else { + MethodArgumentType::StateMutRef + }), + _ => Err(Error::new( + ty.span(), + "The state parameter has to be a (mutable) reference to gotham_restful::State" + )) }; } - - if cfg!(feature = "auth") && (attr.as_deref() == Some("auth") || (attr.is_none() && name == "auth")) - { + + if cfg!(feature = "auth") && (attr.as_deref() == Some("auth") || (attr.is_none() && name == "auth")) { return Ok(match ty { Type::Reference(ty) => MethodArgumentType::AuthStatusRef(*ty.elem), ty => MethodArgumentType::AuthStatus(ty) }); } - - if cfg!(feature = "database") && (attr.as_deref() == Some("connection") || attr.as_deref() == Some("conn") || (attr.is_none() && name == "conn")) + + if cfg!(feature = "database") + && (attr.as_deref() == Some("connection") || attr.as_deref() == Some("conn") || (attr.is_none() && name == "conn")) { return Ok(MethodArgumentType::DatabaseConnection(match ty { Type::Reference(ty) => *ty.elem, ty => ty })); } - + Ok(MethodArgumentType::MethodArg(ty)) } -fn interpret_arg(index : usize, arg : &PatType) -> Result -{ +fn interpret_arg(index: usize, arg: &PatType) -> Result { let pat = &arg.pat; let ident = format_ident!("arg{}", index); let orig_name = quote!(#pat); let ty = interpret_arg_ty(&arg.attrs, &orig_name.to_string(), *arg.ty.clone())?; - - Ok(MethodArgument { ident, ident_span: arg.pat.span(), ty }) + + Ok(MethodArgument { + ident, + ident_span: arg.pat.span(), + ty + }) } #[cfg(feature = "openapi")] -fn expand_operation_id(attrs : &[NestedMeta]) -> TokenStream -{ - let mut operation_id : Option<&Lit> = None; - for meta in attrs - { - if let NestedMeta::Meta(Meta::NameValue(kv)) = meta - { - if kv.path.segments.last().map(|p| p.ident.to_string()) == Some("operation_id".to_owned()) - { +fn expand_operation_id(attrs: &[NestedMeta]) -> TokenStream { + let mut operation_id: Option<&Lit> = None; + for meta in attrs { + if let NestedMeta::Meta(Meta::NameValue(kv)) = meta { + if kv.path.segments.last().map(|p| p.ident.to_string()) == Some("operation_id".to_owned()) { operation_id = Some(&kv.lit) } } } - + match operation_id { Some(operation_id) => quote! { fn operation_id() -> Option @@ -250,26 +226,24 @@ fn expand_operation_id(attrs : &[NestedMeta]) -> TokenStream } #[cfg(not(feature = "openapi"))] -fn expand_operation_id(_ : &[NestedMeta]) -> TokenStream -{ +fn expand_operation_id(_: &[NestedMeta]) -> TokenStream { quote!() } -fn expand_wants_auth(attrs : &[NestedMeta], default : bool) -> TokenStream -{ - let default_lit = Lit::Bool(LitBool { value: default, span: Span::call_site() }); +fn expand_wants_auth(attrs: &[NestedMeta], default: bool) -> TokenStream { + let default_lit = Lit::Bool(LitBool { + value: default, + span: Span::call_site() + }); let mut wants_auth = &default_lit; - for meta in attrs - { - if let NestedMeta::Meta(Meta::NameValue(kv)) = meta - { - if kv.path.segments.last().map(|p| p.ident.to_string()) == Some("wants_auth".to_owned()) - { + for meta in attrs { + if let NestedMeta::Meta(Meta::NameValue(kv)) = meta { + if kv.path.segments.last().map(|p| p.ident.to_string()) == Some("wants_auth".to_owned()) { wants_auth = &kv.lit } } } - + quote! { fn wants_auth() -> bool { @@ -279,73 +253,80 @@ fn expand_wants_auth(attrs : &[NestedMeta], default : bool) -> TokenStream } #[allow(clippy::comparison_chain)] -pub fn expand_method(method : Method, mut attrs : AttributeArgs, fun : ItemFn) -> Result -{ +pub fn expand_method(method: Method, mut attrs: AttributeArgs, fun: ItemFn) -> Result { let krate = super::krate(); - + // parse attributes - if attrs.len() < 1 - { - return Err(Error::new(Span::call_site(), "Missing Resource struct. Example: #[read_all(MyResource)]")); + if attrs.len() < 1 { + return Err(Error::new( + Span::call_site(), + "Missing Resource struct. Example: #[read_all(MyResource)]" + )); } let resource_path = match attrs.remove(0) { NestedMeta::Meta(Meta::Path(path)) => path, - p => return Err(Error::new(p.span(), "Expected name of the Resource struct this method belongs to")) + p => { + return Err(Error::new( + p.span(), + "Expected name of the Resource struct this method belongs to" + )) + }, }; - let resource_name = resource_path.segments.last().map(|s| s.ident.to_string()) - .ok_or_else(|| Error::new(resource_path.span(), "Resource name must not be empty"))?; - + let resource_name = resource_path + .segments + .last() + .map(|s| s.ident.to_string()) + .ok_or_else(|| Error::new(resource_path.span(), "Resource name must not be empty"))?; + let fun_ident = &fun.sig.ident; let fun_vis = &fun.vis; let fun_is_async = fun.sig.asyncness.is_some(); - - if let Some(unsafety) = fun.sig.unsafety - { + + if let Some(unsafety) = fun.sig.unsafety { return Err(Error::new(unsafety.span(), "Resource methods must not be unsafe")); } - + let trait_ident = method.trait_ident(); let method_ident = method.fn_ident(); let mod_ident = method.mod_ident(&resource_name); let handler_ident = method.handler_struct_ident(&resource_name); let setup_ident = method.setup_ident(&resource_name); - + let (ret, is_no_content) = match &fun.sig.output { ReturnType::Default => (quote!(#krate::NoContent), true), ReturnType::Type(_, ty) => (quote!(#ty), false) }; - + // some default idents we'll need let state_ident = format_ident!("state"); let repo_ident = format_ident!("repo"); let conn_ident = format_ident!("conn"); let auth_ident = format_ident!("auth"); let res_ident = format_ident!("res"); - + // extract arguments into pattern, ident and type - let args = fun.sig.inputs.iter() + let args = fun + .sig + .inputs + .iter() .enumerate() .map(|(i, arg)| match arg { FnArg::Typed(arg) => interpret_arg(i, arg), FnArg::Receiver(_) => Err(Error::new(arg.span(), "Didn't expect self parameter")) }) .collect_to_result()?; - + // extract the generic parameters to use let ty_names = method.type_names(); let ty_len = ty_names.len(); - let generics_args : Vec<&MethodArgument> = args.iter() - .filter(|arg| (*arg).ty.is_method_arg()) - .collect(); - if generics_args.len() > ty_len - { + let generics_args: Vec<&MethodArgument> = args.iter().filter(|arg| (*arg).ty.is_method_arg()).collect(); + if generics_args.len() > ty_len { return Err(Error::new(generics_args[ty_len].span(), "Too many arguments")); - } - else if generics_args.len() < ty_len - { + } else if generics_args.len() < ty_len { return Err(Error::new(fun_ident.span(), "Too few arguments")); } - let generics : Vec = generics_args.iter() + let generics: Vec = generics_args + .iter() .map(|arg| arg.ty.quote_ty().unwrap()) .zip(ty_names) .map(|(arg, name)| { @@ -353,47 +334,53 @@ pub fn expand_method(method : Method, mut attrs : AttributeArgs, fun : ItemFn) - quote!(type #ident = #arg;) }) .collect(); - + // extract the definition of our method - let mut args_def : Vec = args.iter() + let mut args_def: Vec = args + .iter() .filter(|arg| (*arg).ty.is_method_arg()) .map(|arg| { let ident = &arg.ident; let ty = arg.ty.quote_ty(); quote!(#ident : #ty) - }).collect(); + }) + .collect(); args_def.insert(0, quote!(mut #state_ident : #krate::State)); - + // extract the arguments to pass over to the supplied method - let args_pass : Vec = args.iter().map(|arg| match (&arg.ty, &arg.ident) { - (MethodArgumentType::StateRef, _) => quote!(&#state_ident), - (MethodArgumentType::StateMutRef, _) => quote!(&mut #state_ident), - (MethodArgumentType::MethodArg(_), ident) => quote!(#ident), - (MethodArgumentType::DatabaseConnection(_), _) => quote!(&#conn_ident), - (MethodArgumentType::AuthStatus(_), _) => quote!(#auth_ident), - (MethodArgumentType::AuthStatusRef(_), _) => quote!(&#auth_ident) - }).collect(); - + let args_pass: Vec = args + .iter() + .map(|arg| match (&arg.ty, &arg.ident) { + (MethodArgumentType::StateRef, _) => quote!(&#state_ident), + (MethodArgumentType::StateMutRef, _) => quote!(&mut #state_ident), + (MethodArgumentType::MethodArg(_), ident) => quote!(#ident), + (MethodArgumentType::DatabaseConnection(_), _) => quote!(&#conn_ident), + (MethodArgumentType::AuthStatus(_), _) => quote!(#auth_ident), + (MethodArgumentType::AuthStatusRef(_), _) => quote!(&#auth_ident) + }) + .collect(); + // prepare the method block let mut block = quote!(#fun_ident(#(#args_pass),*)); let mut state_block = quote!(); - if fun_is_async - { - if let Some(arg) = args.iter().find(|arg| (*arg).ty.is_state_ref()) - { - return Err(Error::new(arg.span(), "async fn must not take &State as an argument as State is not Sync, consider boxing")); + if fun_is_async { + if let Some(arg) = args.iter().find(|arg| (*arg).ty.is_state_ref()) { + return Err(Error::new( + arg.span(), + "async fn must not take &State as an argument as State is not Sync, consider boxing" + )); } block = quote!(#block.await); } - if is_no_content - { + if is_no_content { block = quote!(#block; Default::default()) } - if let Some(arg) = args.iter().find(|arg| (*arg).ty.is_database_conn()) - { - if fun_is_async - { - return Err(Error::new(arg.span(), "async fn is not supported when database support is required, consider boxing")); + if let Some(arg) = args.iter().find(|arg| (*arg).ty.is_database_conn()) { + if fun_is_async { + return Err(Error::new( + arg.span(), + "async fn is not supported when database support is required, consider boxing" + )); } let conn_ty = arg.ty.quote_ty(); state_block = quote! { @@ -411,70 +398,68 @@ pub fn expand_method(method : Method, mut attrs : AttributeArgs, fun : ItemFn) - } }; } - if let Some(arg) = args.iter().find(|arg| (*arg).ty.is_auth_status()) - { + if let Some(arg) = args.iter().find(|arg| (*arg).ty.is_auth_status()) { let auth_ty = arg.ty.quote_ty(); state_block = quote! { #state_block let #auth_ident : #auth_ty = <#auth_ty>::borrow_from(&#state_ident).clone(); }; } - + // prepare the where clause let mut where_clause = quote!(#resource_path : #krate::Resource,); - for arg in args.iter().filter(|arg| (*arg).ty.is_auth_status()) - { + for arg in args.iter().filter(|arg| (*arg).ty.is_auth_status()) { let auth_ty = arg.ty.quote_ty(); where_clause = quote!(#where_clause #auth_ty : Clone,); } - + // attribute generated code let operation_id = expand_operation_id(&attrs); let wants_auth = expand_wants_auth(&attrs, args.iter().any(|arg| (*arg).ty.is_auth_status())); - + // put everything together Ok(quote! { #fun - + #fun_vis mod #mod_ident { use super::*; - + struct #handler_ident; - + impl #krate::ResourceMethod for #handler_ident { type Res = #ret; - + #operation_id #wants_auth } - + impl #krate::#trait_ident for #handler_ident where #where_clause { #(#generics)* - + fn #method_ident(#(#args_def),*) -> std::pin::Pin + Send>> { #[allow(unused_imports)] use #krate::{export::FutureExt, FromState}; - + #state_block - + async move { let #res_ident = { #block }; (#state_ident, #res_ident) }.boxed() } } - + #[deny(dead_code)] pub fn #setup_ident(route : &mut D) { route.#method_ident::<#handler_ident>(); } - + } }) } diff --git a/derive/src/openapi_type.rs b/derive/src/openapi_type.rs index 57a7ceb..58965ba 100644 --- a/derive/src/openapi_type.rs +++ b/derive/src/openapi_type.rs @@ -1,99 +1,77 @@ -use crate::util::{CollectToResult, remove_parens}; +use crate::util::{remove_parens, CollectToResult}; use proc_macro2::{Ident, TokenStream}; use quote::quote; use syn::{ - parse_macro_input, - spanned::Spanned, - Attribute, - AttributeArgs, - Data, - DataEnum, - DataStruct, - DeriveInput, - Error, - Field, - Fields, - Generics, - GenericParam, - Lit, - LitStr, - Meta, - NestedMeta, - Result, - Variant + parse_macro_input, spanned::Spanned, Attribute, AttributeArgs, Data, DataEnum, DataStruct, DeriveInput, Error, Field, + Fields, GenericParam, Generics, Lit, LitStr, Meta, NestedMeta, Result, Variant }; -pub fn expand_openapi_type(input : DeriveInput) -> Result -{ +pub fn expand_openapi_type(input: DeriveInput) -> Result { match (input.ident, input.generics, input.attrs, input.data) { (ident, generics, attrs, Data::Enum(inum)) => expand_enum(ident, generics, attrs, inum), (ident, generics, attrs, Data::Struct(strukt)) => expand_struct(ident, generics, attrs, strukt), - (_, _, _, Data::Union(uni)) => Err(Error::new(uni.union_token.span(), "#[derive(OpenapiType)] only works for structs and enums")) + (_, _, _, Data::Union(uni)) => Err(Error::new( + uni.union_token.span(), + "#[derive(OpenapiType)] only works for structs and enums" + )) } } -fn expand_where(generics : &Generics) -> TokenStream -{ - if generics.params.is_empty() - { +fn expand_where(generics: &Generics) -> TokenStream { + if generics.params.is_empty() { return quote!(); } - + let krate = super::krate(); - let idents = generics.params.iter() + let idents = generics + .params + .iter() .map(|param| match param { GenericParam::Type(ty) => Some(ty.ident.clone()), _ => None }) .filter(|param| param.is_some()) .map(|param| param.unwrap()); - + quote! { where #(#idents : #krate::OpenapiType),* } } #[derive(Debug, Default)] -struct Attrs -{ - nullable : bool, - rename : Option +struct Attrs { + nullable: bool, + rename: Option } -fn to_string(lit : &Lit) -> Result -{ +fn to_string(lit: &Lit) -> Result { match lit { Lit::Str(str) => Ok(str.value()), _ => Err(Error::new(lit.span(), "Expected string literal")) } } -fn to_bool(lit : &Lit) -> Result -{ +fn to_bool(lit: &Lit) -> Result { match lit { Lit::Bool(bool) => Ok(bool.value), _ => Err(Error::new(lit.span(), "Expected bool")) } } -fn parse_attributes(input : &[Attribute]) -> Result -{ +fn parse_attributes(input: &[Attribute]) -> Result { let mut parsed = Attrs::default(); - for attr in input - { - if attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("openapi".to_owned()) - { + for attr in input { + if attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("openapi".to_owned()) { let tokens = remove_parens(attr.tokens.clone()); // TODO this is not public api but syn currently doesn't offer another convenient way to parse AttributeArgs let nested = parse_macro_input::parse::(tokens.into())?; - for meta in nested - { + for meta in nested { match &meta { NestedMeta::Meta(Meta::NameValue(kv)) => match kv.path.segments.last().map(|s| s.ident.to_string()) { Some(key) => match key.as_ref() { "nullable" => parsed.nullable = to_bool(&kv.lit)?, - "rename" => parsed.rename = Some(to_string(&kv.lit)?), - _ => return Err(Error::new(kv.path.span(), "Unknown key")), + "rename" => parsed.rename = Some(to_string(&kv.lit)?), + _ => return Err(Error::new(kv.path.span(), "Unknown key")) }, _ => return Err(Error::new(meta.span(), "Unexpected token")) }, @@ -105,42 +83,40 @@ fn parse_attributes(input : &[Attribute]) -> Result Ok(parsed) } -fn expand_variant(variant : &Variant) -> Result -{ - if variant.fields != Fields::Unit - { - return Err(Error::new(variant.span(), "#[derive(OpenapiType)] does not support enum variants with fields")); +fn expand_variant(variant: &Variant) -> Result { + if variant.fields != Fields::Unit { + return Err(Error::new( + variant.span(), + "#[derive(OpenapiType)] does not support enum variants with fields" + )); } - + let ident = &variant.ident; - + let attrs = parse_attributes(&variant.attrs)?; let name = match attrs.rename { Some(rename) => rename, None => ident.to_string() }; - + Ok(quote! { enumeration.push(#name.to_string()); }) } -fn expand_enum(ident : Ident, generics : Generics, attrs : Vec, input : DataEnum) -> Result -{ +fn expand_enum(ident: Ident, generics: Generics, attrs: Vec, input: DataEnum) -> Result { let krate = super::krate(); let where_clause = expand_where(&generics); - + let attrs = parse_attributes(&attrs)?; let nullable = attrs.nullable; let name = match attrs.rename { Some(rename) => rename, None => ident.to_string() }; - - let variants = input.variants.iter() - .map(expand_variant) - .collect_to_result()?; - + + let variants = input.variants.iter().map(expand_variant).collect_to_result()?; + Ok(quote! { impl #generics #krate::OpenapiType for #ident #generics #where_clause @@ -148,11 +124,11 @@ fn expand_enum(ident : Ident, generics : Generics, attrs : Vec, input fn schema() -> #krate::OpenapiSchema { use #krate::{export::openapi::*, OpenapiSchema}; - + let mut enumeration : Vec = Vec::new(); - + #(#variants)* - + let schema = SchemaKind::Type(Type::String(StringType { format: VariantOrUnknownOrEmpty::Empty, enumeration, @@ -170,25 +146,29 @@ fn expand_enum(ident : Ident, generics : Generics, attrs : Vec, input }) } -fn expand_field(field : &Field) -> Result -{ +fn expand_field(field: &Field) -> Result { let ident = match &field.ident { Some(ident) => ident, - None => return Err(Error::new(field.span(), "#[derive(OpenapiType)] does not support fields without an ident")) + None => { + return Err(Error::new( + field.span(), + "#[derive(OpenapiType)] does not support fields without an ident" + )) + }, }; let ident_str = LitStr::new(&ident.to_string(), ident.span()); let ty = &field.ty; - + let attrs = parse_attributes(&field.attrs)?; let nullable = attrs.nullable; let name = match attrs.rename { Some(rename) => rename, None => ident.to_string() }; - + Ok(quote! {{ let mut schema = <#ty>::schema(); - + if schema.nullable { schema.nullable = false; @@ -197,7 +177,7 @@ fn expand_field(field : &Field) -> Result { required.push(#ident_str.to_string()); } - + let keys : Vec = schema.dependencies.keys().map(|k| k.to_string()).collect(); for dep in keys { @@ -207,7 +187,7 @@ fn expand_field(field : &Field) -> Result dependencies.insert(dep, dep_schema); } } - + match schema.name.clone() { Some(schema_name) => { properties.insert( @@ -226,42 +206,42 @@ fn expand_field(field : &Field) -> Result }}) } -fn expand_struct(ident : Ident, generics : Generics, attrs : Vec, input : DataStruct) -> Result -{ +fn expand_struct(ident: Ident, generics: Generics, attrs: Vec, input: DataStruct) -> Result { let krate = super::krate(); let where_clause = expand_where(&generics); - + let attrs = parse_attributes(&attrs)?; let nullable = attrs.nullable; let name = match attrs.rename { Some(rename) => rename, None => ident.to_string() }; - - let fields : Vec = match input.fields { - Fields::Named(named_fields) => { - named_fields.named.iter() - .map(expand_field) - .collect_to_result()? + + let fields: Vec = match input.fields { + Fields::Named(named_fields) => named_fields.named.iter().map(expand_field).collect_to_result()?, + Fields::Unnamed(fields) => { + return Err(Error::new( + fields.span(), + "#[derive(OpenapiType)] does not support unnamed fields" + )) }, - Fields::Unnamed(fields) => return Err(Error::new(fields.span(), "#[derive(OpenapiType)] does not support unnamed fields")), Fields::Unit => Vec::new() }; - - Ok(quote!{ + + Ok(quote! { impl #generics #krate::OpenapiType for #ident #generics #where_clause { fn schema() -> #krate::OpenapiSchema { use #krate::{export::{openapi::*, IndexMap}, OpenapiSchema}; - + let mut properties : IndexMap>> = IndexMap::new(); let mut required : Vec = Vec::new(); let mut dependencies : IndexMap = IndexMap::new(); - + #(#fields)* - + let schema = SchemaKind::Type(Type::Object(ObjectType { properties, required, diff --git a/derive/src/request_body.rs b/derive/src/request_body.rs index 76c80aa..c469a79 100644 --- a/derive/src/request_body.rs +++ b/derive/src/request_body.rs @@ -6,43 +6,34 @@ use syn::{ parse::{Parse, ParseStream}, punctuated::Punctuated, spanned::Spanned, - DeriveInput, - Error, - Generics, - Path, - Result, - Token + DeriveInput, Error, Generics, Path, Result, Token }; struct MimeList(Punctuated); -impl Parse for MimeList -{ - fn parse(input: ParseStream) -> Result - { +impl Parse for MimeList { + fn parse(input: ParseStream) -> Result { let list = Punctuated::parse_separated_nonempty(&input)?; Ok(Self(list)) } } #[cfg(not(feature = "openapi"))] -fn impl_openapi_type(_ident : &Ident, _generics : &Generics) -> TokenStream -{ +fn impl_openapi_type(_ident: &Ident, _generics: &Generics) -> TokenStream { quote!() } #[cfg(feature = "openapi")] -fn impl_openapi_type(ident : &Ident, generics : &Generics) -> TokenStream -{ +fn impl_openapi_type(ident: &Ident, generics: &Generics) -> TokenStream { let krate = super::krate(); - + quote! { impl #generics #krate::OpenapiType for #ident #generics { fn schema() -> #krate::OpenapiSchema { use #krate::{export::openapi::*, OpenapiSchema}; - + OpenapiSchema::new(SchemaKind::Type(Type::String(StringType { format: VariantOrUnknownOrEmpty::Item(StringFormat::Binary), ..Default::default() @@ -52,32 +43,38 @@ fn impl_openapi_type(ident : &Ident, generics : &Generics) -> TokenStream } } -pub fn expand_request_body(input : DeriveInput) -> Result -{ +pub fn expand_request_body(input: DeriveInput) -> Result { let krate = super::krate(); let ident = input.ident; let generics = input.generics; - - let types = input.attrs.into_iter() - .filter(|attr| attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("supported_types".to_string())) + + let types = input + .attrs + .into_iter() + .filter(|attr| { + attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("supported_types".to_string()) + }) .flat_map(|attr| { let span = attr.span(); attr.parse_args::() .map(|list| Box::new(list.0.into_iter().map(Ok)) as Box>>) .unwrap_or_else(|mut err| { - err.combine(Error::new(span, "Hint: Types list should look like #[supported_types(TEXT_PLAIN, APPLICATION_JSON)]")); + err.combine(Error::new( + span, + "Hint: Types list should look like #[supported_types(TEXT_PLAIN, APPLICATION_JSON)]" + )); Box::new(iter::once(Err(err))) }) }) .collect_to_result()?; - + let types = match types { ref types if types.is_empty() => quote!(None), types => quote!(Some(vec![#(#types),*])) }; - + let impl_openapi_type = impl_openapi_type(&ident, &generics); - + Ok(quote! { impl #generics #krate::RequestBody for #ident #generics where #ident #generics : #krate::FromBody @@ -87,7 +84,7 @@ pub fn expand_request_body(input : DeriveInput) -> Result #types } } - + #impl_openapi_type }) } diff --git a/derive/src/resource.rs b/derive/src/resource.rs index 79482e6..a81e6d9 100644 --- a/derive/src/resource.rs +++ b/derive/src/resource.rs @@ -1,23 +1,18 @@ use crate::{method::Method, util::CollectToResult}; use proc_macro2::{Ident, TokenStream}; use quote::quote; +use std::{iter, str::FromStr}; use syn::{ parenthesized, parse::{Parse, ParseStream}, punctuated::Punctuated, - DeriveInput, - Error, - Result, - Token + DeriveInput, Error, Result, Token }; -use std::{iter, str::FromStr}; struct MethodList(Punctuated); -impl Parse for MethodList -{ - fn parse(input: ParseStream) -> Result - { +impl Parse for MethodList { + fn parse(input: ParseStream) -> Result { let content; let _paren = parenthesized!(content in input); let list = Punctuated::parse_separated_nonempty(&content)?; @@ -25,26 +20,32 @@ impl Parse for MethodList } } -pub fn expand_resource(input : DeriveInput) -> Result -{ +pub fn expand_resource(input: DeriveInput) -> Result { let krate = super::krate(); let ident = input.ident; let name = ident.to_string(); - - let methods = input.attrs.into_iter().filter(|attr| - attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("resource".to_string()) // TODO wtf - ).map(|attr| { - syn::parse2(attr.tokens).map(|m : MethodList| m.0.into_iter()) - }).flat_map(|list| match list { - Ok(iter) => Box::new(iter.map(|method| { - let method = Method::from_str(&method.to_string()).map_err(|err| Error::new(method.span(), err))?; - let mod_ident = method.mod_ident(&name); - let ident = method.setup_ident(&name); - Ok(quote!(#mod_ident::#ident(&mut route);)) - })) as Box>>, - Err(err) => Box::new(iter::once(Err(err))) - }).collect_to_result()?; - + + let methods = + input + .attrs + .into_iter() + .filter( + |attr| { + attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("resource".to_string()) + } // TODO wtf + ) + .map(|attr| syn::parse2(attr.tokens).map(|m: MethodList| m.0.into_iter())) + .flat_map(|list| match list { + Ok(iter) => Box::new(iter.map(|method| { + let method = Method::from_str(&method.to_string()).map_err(|err| Error::new(method.span(), err))?; + let mod_ident = method.mod_ident(&name); + let ident = method.setup_ident(&name); + Ok(quote!(#mod_ident::#ident(&mut route);)) + })) as Box>>, + Err(err) => Box::new(iter::once(Err(err))) + }) + .collect_to_result()?; + Ok(quote! { impl #krate::Resource for #ident { diff --git a/derive/src/resource_error.rs b/derive/src/resource_error.rs index 032151b..b3af040 100644 --- a/derive/src/resource_error.rs +++ b/derive/src/resource_error.rs @@ -1,68 +1,54 @@ -use crate::util::{CollectToResult, remove_parens}; +use crate::util::{remove_parens, CollectToResult}; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; use std::iter; use syn::{ - spanned::Spanned, - Attribute, - Data, - DeriveInput, - Error, - Fields, - GenericParam, - LitStr, - Path, - PathSegment, - Result, - Type, + spanned::Spanned, Attribute, Data, DeriveInput, Error, Fields, GenericParam, LitStr, Path, PathSegment, Result, Type, Variant }; - -struct ErrorVariantField -{ - attrs : Vec, - ident : Ident, - ty : Type +struct ErrorVariantField { + attrs: Vec, + ident: Ident, + ty: Type } -struct ErrorVariant -{ - ident : Ident, - status : Option, - is_named : bool, - fields : Vec, - from_ty : Option<(usize, Type)>, - display : Option +struct ErrorVariant { + ident: Ident, + status: Option, + is_named: bool, + fields: Vec, + from_ty: Option<(usize, Type)>, + display: Option } -fn process_variant(variant : Variant) -> Result -{ - let status = match variant.attrs.iter() - .find(|attr| attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("status".to_string())) - { - Some(attr) => Some(syn::parse2(remove_parens(attr.tokens.clone()))?), - None => None - }; - +fn process_variant(variant: Variant) -> Result { + let status = + match variant.attrs.iter().find(|attr| { + attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("status".to_string()) + }) { + Some(attr) => Some(syn::parse2(remove_parens(attr.tokens.clone()))?), + None => None + }; + let mut is_named = false; let mut fields = Vec::new(); match variant.fields { Fields::Named(named) => { is_named = true; - for field in named.named - { + for field in named.named { let span = field.span(); fields.push(ErrorVariantField { attrs: field.attrs, - ident: field.ident.ok_or_else(|| Error::new(span, "Missing ident for this enum variant field"))?, + ident: field + .ident + .ok_or_else(|| Error::new(span, "Missing ident for this enum variant field"))?, ty: field.ty }); } }, Fields::Unnamed(unnamed) => { - for (i, field) in unnamed.unnamed.into_iter().enumerate() - { + for (i, field) in unnamed.unnamed.into_iter().enumerate() { fields.push(ErrorVariantField { attrs: field.attrs, ident: format_ident!("arg{}", i), @@ -72,19 +58,25 @@ fn process_variant(variant : Variant) -> Result }, Fields::Unit => {} } - - let from_ty = fields.iter() + + let from_ty = fields + .iter() .enumerate() - .find(|(_, field)| field.attrs.iter().any(|attr| attr.path.segments.last().map(|segment| segment.ident.to_string()) == Some("from".to_string()))) + .find(|(_, field)| { + field + .attrs + .iter() + .any(|attr| attr.path.segments.last().map(|segment| segment.ident.to_string()) == Some("from".to_string())) + }) .map(|(i, field)| (i, field.ty.clone())); - - let display = match variant.attrs.iter() - .find(|attr| attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("display".to_string())) - { + + let display = match variant.attrs.iter().find(|attr| { + attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("display".to_string()) + }) { Some(attr) => Some(syn::parse2(remove_parens(attr.tokens.clone()))?), None => None }; - + Ok(ErrorVariant { ident: variant.ident, status, @@ -95,18 +87,15 @@ fn process_variant(variant : Variant) -> Result }) } -fn path_segment(name : &str) -> PathSegment -{ +fn path_segment(name: &str) -> PathSegment { PathSegment { ident: format_ident!("{}", name), arguments: Default::default() } } -impl ErrorVariant -{ - fn fields_pat(&self) -> TokenStream - { +impl ErrorVariant { + fn fields_pat(&self) -> TokenStream { let mut fields = self.fields.iter().map(|field| &field.ident).peekable(); if fields.peek().is_none() { quote!() @@ -116,74 +105,90 @@ impl ErrorVariant quote!( ( #( #fields ),* ) ) } } - - fn to_display_match_arm(&self, formatter_ident : &Ident, enum_ident : &Ident) -> Result - { + + fn to_display_match_arm(&self, formatter_ident: &Ident, enum_ident: &Ident) -> Result { let ident = &self.ident; - let display = self.display.as_ref().ok_or_else(|| Error::new(self.ident.span(), "Missing display string for this variant"))?; - + let display = self + .display + .as_ref() + .ok_or_else(|| Error::new(self.ident.span(), "Missing display string for this variant"))?; + // lets find all required format parameters let display_str = display.value(); - let mut params : Vec<&str> = Vec::new(); + let mut params: Vec<&str> = Vec::new(); let len = display_str.len(); let mut start = len; let mut iter = display_str.chars().enumerate().peekable(); - while let Some((i, c)) = iter.next() - { + while let Some((i, c)) = iter.next() { // we found a new opening brace - if start == len && c == '{' - { + if start == len && c == '{' { start = i + 1; } // we found a duplicate opening brace - else if start == i && c == '{' - { + else if start == i && c == '{' { start = len; } // we found a closing brace - else if start < i && c == '}' - { + else if start < i && c == '}' { match iter.peek() { - Some((_, '}')) => return Err(Error::new(display.span(), "Error parsing format string: curly braces not allowed inside parameter name")), + Some((_, '}')) => { + return Err(Error::new( + display.span(), + "Error parsing format string: curly braces not allowed inside parameter name" + )) + }, _ => params.push(&display_str[start..i]) }; start = len; } // we found a closing brace without content - else if start == i && c == '}' - { - return Err(Error::new(display.span(), "Error parsing format string: parameter name must not be empty")) + else if start == i && c == '}' { + return Err(Error::new( + display.span(), + "Error parsing format string: parameter name must not be empty" + )); } } - if start != len - { - return Err(Error::new(display.span(), "Error parsing format string: Unmatched opening brace")); + if start != len { + return Err(Error::new( + display.span(), + "Error parsing format string: Unmatched opening brace" + )); } - let params = params.into_iter().map(|name| format_ident!("{}{}", if self.is_named { "" } else { "arg" }, name)); - + let params = params + .into_iter() + .map(|name| format_ident!("{}{}", if self.is_named { "" } else { "arg" }, name)); + let fields_pat = self.fields_pat(); Ok(quote! { #enum_ident::#ident #fields_pat => write!(#formatter_ident, #display #(, #params = #params)*) }) } - - fn into_match_arm(self, krate : &TokenStream, enum_ident : &Ident) -> Result - { + + fn into_match_arm(self, krate: &TokenStream, enum_ident: &Ident) -> Result { let ident = &self.ident; let fields_pat = self.fields_pat(); let status = self.status.map(|status| { // the status might be relative to StatusCode, so let's fix that - if status.leading_colon.is_none() && status.segments.len() < 2 - { + if status.leading_colon.is_none() && status.segments.len() < 2 { let status_ident = status.segments.first().cloned().unwrap_or_else(|| path_segment("OK")); Path { leading_colon: Some(Default::default()), - segments: vec![path_segment("gotham_restful"), path_segment("gotham"), path_segment("hyper"), path_segment("StatusCode"), status_ident].into_iter().collect() + segments: vec![ + path_segment("gotham_restful"), + path_segment("gotham"), + path_segment("hyper"), + path_segment("StatusCode"), + status_ident, + ] + .into_iter() + .collect() } + } else { + status } - else { status } }); - + // the response will come directly from the from_ty if present let res = match (self.from_ty, status) { (Some((from_index, _)), None) => { @@ -198,14 +203,13 @@ impl ErrorVariant })), (None, None) => return Err(Error::new(ident.span(), "Missing #[status(code)] for this variant")) }; - + Ok(quote! { #enum_ident::#ident #fields_pat => #res }) } - - fn were(&self) -> Option - { + + fn were(&self) -> Option { match self.from_ty.as_ref() { Some((_, ty)) => Some(quote!( #ty : ::std::error::Error )), None => None @@ -213,22 +217,22 @@ impl ErrorVariant } } -pub fn expand_resource_error(input : DeriveInput) -> Result -{ +pub fn expand_resource_error(input: DeriveInput) -> Result { let krate = super::krate(); let ident = input.ident; let generics = input.generics; - + let inum = match input.data { Data::Enum(inum) => Ok(inum), Data::Struct(strukt) => Err(strukt.struct_token.span()), Data::Union(uni) => Err(uni.union_token.span()) - }.map_err(|span| Error::new(span, "#[derive(ResourceError)] only works for enums"))?; - let variants = inum.variants.into_iter() - .map(process_variant) - .collect_to_result()?; - - let display_impl = if variants.iter().any(|v| v.display.is_none()) { None } else { + } + .map_err(|span| Error::new(span, "#[derive(ResourceError)] only works for enums"))?; + let variants = inum.variants.into_iter().map(process_variant).collect_to_result()?; + + let display_impl = if variants.iter().any(|v| v.display.is_none()) { + None + } else { let were = generics.params.iter().filter_map(|param| match param { GenericParam::Type(ty) => { let ident = &ty.ident; @@ -237,7 +241,8 @@ pub fn expand_resource_error(input : DeriveInput) -> Result _ => None }); let formatter_ident = format_ident!("resource_error_display_formatter"); - let match_arms = variants.iter() + let match_arms = variants + .iter() .map(|v| v.to_display_match_arm(&formatter_ident, &ident)) .collect_to_result()?; Some(quote! { @@ -253,34 +258,39 @@ pub fn expand_resource_error(input : DeriveInput) -> Result } }) }; - - let mut from_impls : Vec = Vec::new(); - - for var in &variants - { + + let mut from_impls: Vec = Vec::new(); + + for var in &variants { let var_ident = &var.ident; let (from_index, from_ty) = match var.from_ty.as_ref() { Some(f) => f, None => continue }; let from_ident = &var.fields[*from_index].ident; - + let fields_pat = var.fields_pat(); - let fields_where = var.fields.iter().enumerate() + let fields_where = var + .fields + .iter() + .enumerate() .filter(|(i, _)| i != from_index) .map(|(_, field)| { let ty = &field.ty; quote!( #ty : Default ) }) .chain(iter::once(quote!( #from_ty : ::std::error::Error ))); - let fields_let = var.fields.iter().enumerate() + let fields_let = var + .fields + .iter() + .enumerate() .filter(|(i, _)| i != from_index) .map(|(_, field)| { let id = &field.ident; let ty = &field.ty; quote!( let #id : #ty = Default::default(); ) }); - + from_impls.push(quote! { impl #generics ::std::convert::From<#from_ty> for #ident #generics where #( #fields_where ),* @@ -293,20 +303,21 @@ pub fn expand_resource_error(input : DeriveInput) -> Result } }); } - + let were = variants.iter().filter_map(|variant| variant.were()).collect::>(); - let variants = variants.into_iter() + let variants = variants + .into_iter() .map(|variant| variant.into_match_arm(&krate, &ident)) - .collect_to_result()?; - + .collect_to_result()?; + Ok(quote! { #display_impl - + impl #generics #krate::IntoResponseError for #ident #generics where #( #were ),* { type Err = #krate::export::serde_json::Error; - + fn into_response_error(self) -> Result<#krate::Response, Self::Err> { match self { @@ -314,7 +325,7 @@ pub fn expand_resource_error(input : DeriveInput) -> Result } } } - + #( #from_impls )* }) } diff --git a/derive/src/util.rs b/derive/src/util.rs index d82dc31..aedb9e6 100644 --- a/derive/src/util.rs +++ b/derive/src/util.rs @@ -2,40 +2,38 @@ use proc_macro2::{Delimiter, TokenStream, TokenTree}; use std::iter; use syn::Error; -pub trait CollectToResult -{ +pub trait CollectToResult { type Item; - + fn collect_to_result(self) -> Result, Error>; } impl CollectToResult for I where - I : Iterator> + I: Iterator> { type Item = Item; - - fn collect_to_result(self) -> Result, Error> - { - self.fold(, Error>>::Ok(Vec::new()), |res, code| { - match (code, res) { - (Ok(code), Ok(mut codes)) => { codes.push(code); Ok(codes) }, - (Ok(_), Err(errors)) => Err(errors), - (Err(err), Ok(_)) => Err(err), - (Err(err), Err(mut errors)) => { errors.combine(err); Err(errors) } - } + + fn collect_to_result(self) -> Result, Error> { + self.fold(, Error>>::Ok(Vec::new()), |res, code| match (code, res) { + (Ok(code), Ok(mut codes)) => { + codes.push(code); + Ok(codes) + }, + (Ok(_), Err(errors)) => Err(errors), + (Err(err), Ok(_)) => Err(err), + (Err(err), Err(mut errors)) => { + errors.combine(err); + Err(errors) + } }) } } - -pub fn remove_parens(input : TokenStream) -> TokenStream -{ +pub fn remove_parens(input: TokenStream) -> TokenStream { let iter = input.into_iter().flat_map(|tt| { - if let TokenTree::Group(group) = &tt - { - if group.delimiter() == Delimiter::Parenthesis - { + if let TokenTree::Group(group) = &tt { + if group.delimiter() == Delimiter::Parenthesis { return Box::new(group.stream().into_iter()) as Box>; } } diff --git a/example/src/main.rs b/example/src/main.rs index f953e45..4689ebe 100644 --- a/example/src/main.rs +++ b/example/src/main.rs @@ -1,5 +1,7 @@ -#[macro_use] extern crate gotham_derive; -#[macro_use] extern crate log; +#[macro_use] +extern crate gotham_derive; +#[macro_use] +extern crate log; use fake::{faker::internet::en::Username, Fake}; use gotham::{ @@ -20,25 +22,19 @@ use serde::{Deserialize, Serialize}; #[derive(Resource)] #[resource(read_all, read, search, create, change_all, change, remove, remove_all)] -struct Users -{ -} +struct Users {} #[derive(Resource)] #[resource(ReadAll)] -struct Auth -{ -} +struct Auth {} #[derive(Deserialize, OpenapiType, Serialize, StateData, StaticResponseExtender)] -struct User -{ - username : String +struct User { + username: String } #[read_all(Users)] -fn read_all() -> Success>> -{ +fn read_all() -> Success>> { vec![Username().fake(), Username().fake()] .into_iter() .map(|username| Some(User { username })) @@ -47,113 +43,101 @@ fn read_all() -> Success>> } #[read(Users)] -fn read(id : u64) -> Success -{ - let username : String = Username().fake(); - User { username: format!("{}{}", username, id) }.into() +fn read(id: u64) -> Success { + let username: String = Username().fake(); + User { + username: format!("{}{}", username, id) + } + .into() } #[search(Users)] -fn search(query : User) -> Success -{ +fn search(query: User) -> Success { query.into() } #[create(Users)] -fn create(body : User) -{ +fn create(body: User) { info!("Created User: {}", body.username); } #[change_all(Users)] -fn update_all(body : Vec) -{ - info!("Changing all Users to {:?}", body.into_iter().map(|u| u.username).collect::>()); +fn update_all(body: Vec) { + info!( + "Changing all Users to {:?}", + body.into_iter().map(|u| u.username).collect::>() + ); } #[change(Users)] -fn update(id : u64, body : User) -{ +fn update(id: u64, body: User) { info!("Change User {} to {}", id, body.username); } #[remove_all(Users)] -fn remove_all() -{ +fn remove_all() { info!("Delete all Users"); } #[remove(Users)] -fn remove(id : u64) -{ +fn remove(id: u64) { info!("Delete User {}", id); } #[read_all(Auth)] -fn auth_read_all(auth : AuthStatus<()>) -> AuthSuccess -{ +fn auth_read_all(auth: AuthStatus<()>) -> AuthSuccess { match auth { AuthStatus::Authenticated(data) => Ok(format!("{:?}", data)), _ => Err(Forbidden) } } -const ADDR : &str = "127.0.0.1:18080"; +const ADDR: &str = "127.0.0.1:18080"; #[derive(Clone, Default)] struct Handler; -impl AuthHandler for Handler -{ - fn jwt_secret Option>(&self, _state : &mut State, _decode_data : F) -> Option> - { +impl AuthHandler for Handler { + fn jwt_secret Option>(&self, _state: &mut State, _decode_data: F) -> Option> { None } } -fn main() -{ +fn main() { let encoder = PatternEncoder::new("{d(%Y-%m-%d %H:%M:%S%.3f %Z)} [{l}] {M} - {m}\n"); let config = Config::builder() - .appender( - Appender::builder() - .build("stdout", Box::new( - ConsoleAppender::builder() - .encoder(Box::new(encoder)) - .build() - ))) + .appender(Appender::builder().build( + "stdout", + Box::new(ConsoleAppender::builder().encoder(Box::new(encoder)).build()) + )) .build(Root::builder().appender("stdout").build(LevelFilter::Info)) .unwrap(); log4rs::init_config(config).unwrap(); - + let cors = CorsConfig { origin: Origin::Copy, headers: vec![CONTENT_TYPE], credentials: true, ..Default::default() }; - + let auth = >::from_source(AuthSource::AuthorizationHeader); let logging = RequestLogger::new(log::Level::Info); - let (chain, pipelines) = single_pipeline( - new_pipeline() - .add(auth) - .add(logging) - .add(cors) - .build() - ); + let (chain, pipelines) = single_pipeline(new_pipeline().add(auth).add(logging).add(cors).build()); - gotham::start(ADDR, build_router(chain, pipelines, |route| { - let info = OpenapiInfo { - title: "Users Example".to_owned(), - version: "0.0.1".to_owned(), - urls: vec![format!("http://{}", ADDR)] - }; - route.with_openapi(info, |mut route| { - route.resource::("users"); - route.resource::("auth"); - route.get_openapi("openapi"); - }); - })); + gotham::start( + ADDR, + build_router(chain, pipelines, |route| { + let info = OpenapiInfo { + title: "Users Example".to_owned(), + version: "0.0.1".to_owned(), + urls: vec![format!("http://{}", ADDR)] + }; + route.with_openapi(info, |mut route| { + route.resource::("users"); + route.resource::("auth"); + route.get_openapi("openapi"); + }); + }) + ); println!("Gotham started on {} for testing", ADDR); } - diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..627d9e1 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,19 @@ +edition = "2018" +max_width = 125 +newline_style = "Unix" +unstable_features = true + +# always use tabs. +hard_tabs = true +tab_spaces = 4 + +# commas inbetween but not after +match_block_trailing_comma = true +trailing_comma = "Never" + +# misc +format_code_in_doc_comments = true +merge_imports = true +overflow_delimited_expr = true +use_field_init_shorthand = true +use_try_shorthand = true diff --git a/src/auth.rs b/src/auth.rs index 0888ac3..e4896e3 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,29 +1,24 @@ use crate::{AuthError, Forbidden, HeaderName}; use cookie::CookieJar; -use futures_util::{future, future::{FutureExt, TryFutureExt}}; +use futures_util::{ + future, + future::{FutureExt, TryFutureExt} +}; use gotham::{ handler::HandlerFuture, - hyper::header::{AUTHORIZATION, HeaderMap}, + hyper::header::{HeaderMap, AUTHORIZATION}, middleware::{Middleware, NewMiddleware}, state::{FromState, State} }; -use jsonwebtoken::{ - errors::ErrorKind, - DecodingKey -}; +use jsonwebtoken::{errors::ErrorKind, DecodingKey}; use serde::de::DeserializeOwned; -use std::{ - marker::PhantomData, - panic::RefUnwindSafe, - pin::Pin -}; +use std::{marker::PhantomData, panic::RefUnwindSafe, pin::Pin}; pub use jsonwebtoken::Validation as AuthValidation; /// The authentication status returned by the auth middleware for each request. #[derive(Debug, StateData)] -pub enum AuthStatus -{ +pub enum AuthStatus { /// The auth status is unknown. Unknown, /// The request has been performed without any kind of authentication. @@ -38,10 +33,9 @@ pub enum AuthStatus impl Clone for AuthStatus where - T : Clone + Send + 'static + T: Clone + Send + 'static { - fn clone(&self) -> Self - { + fn clone(&self) -> Self { match self { Self::Unknown => Self::Unknown, Self::Unauthenticated => Self::Unauthenticated, @@ -52,16 +46,10 @@ where } } -impl Copy for AuthStatus -where - T : Copy + Send + 'static -{ -} +impl Copy for AuthStatus where T: Copy + Send + 'static {} -impl AuthStatus -{ - pub fn ok(self) -> Result - { +impl AuthStatus { + pub fn ok(self) -> Result { match self { Self::Authenticated(data) => Ok(data), _ => Err(Forbidden) @@ -71,8 +59,7 @@ impl AuthStatus /// The source of the authentication token in the request. #[derive(Clone, Debug, StateData)] -pub enum AuthSource -{ +pub enum AuthSource { /// Take the token from a cookie with the given name. Cookie(String), /// Take the token from a header with the given name. @@ -100,36 +87,29 @@ impl AuthHandler for CustomAuthHandler { } ``` */ -pub trait AuthHandler -{ +pub trait AuthHandler { /// Return the SHA256-HMAC secret used to verify the JWT token. - fn jwt_secret Option>(&self, state : &mut State, decode_data : F) -> Option>; + fn jwt_secret Option>(&self, state: &mut State, decode_data: F) -> Option>; } /// An `AuthHandler` returning always the same secret. See `AuthMiddleware` for a usage example. #[derive(Clone, Debug)] -pub struct StaticAuthHandler -{ - secret : Vec +pub struct StaticAuthHandler { + secret: Vec } -impl StaticAuthHandler -{ - pub fn from_vec(secret : Vec) -> Self - { +impl StaticAuthHandler { + pub fn from_vec(secret: Vec) -> Self { Self { secret } } - - pub fn from_array(secret : &[u8]) -> Self - { + + pub fn from_array(secret: &[u8]) -> Self { Self::from_vec(secret.to_vec()) } } -impl AuthHandler for StaticAuthHandler -{ - fn jwt_secret Option>(&self, _state : &mut State, _decode_data : F) -> Option> - { +impl AuthHandler for StaticAuthHandler { + fn jwt_secret Option>(&self, _state: &mut State, _decode_data: F) -> Option> { Some(self.secret.clone()) } } @@ -173,19 +153,18 @@ fn main() { ``` */ #[derive(Debug)] -pub struct AuthMiddleware -{ - source : AuthSource, - validation : AuthValidation, - handler : Handler, - _data : PhantomData +pub struct AuthMiddleware { + source: AuthSource, + validation: AuthValidation, + handler: Handler, + _data: PhantomData } impl Clone for AuthMiddleware -where Handler : Clone +where + Handler: Clone { - fn clone(&self) -> Self - { + fn clone(&self) -> Self { Self { source: self.source.clone(), validation: self.validation.clone(), @@ -197,11 +176,10 @@ where Handler : Clone impl AuthMiddleware where - Data : DeserializeOwned + Send, - Handler : AuthHandler + Default + Data: DeserializeOwned + Send, + Handler: AuthHandler + Default { - pub fn from_source(source : AuthSource) -> Self - { + pub fn from_source(source: AuthSource) -> Self { Self { source, validation: Default::default(), @@ -213,11 +191,10 @@ where impl AuthMiddleware where - Data : DeserializeOwned + Send, - Handler : AuthHandler + Data: DeserializeOwned + Send, + Handler: AuthHandler { - pub fn new(source : AuthSource, validation : AuthValidation, handler : Handler) -> Self - { + pub fn new(source: AuthSource, validation: AuthValidation, handler: Handler) -> Self { Self { source, validation, @@ -225,59 +202,52 @@ where _data: Default::default() } } - - fn auth_status(&self, state : &mut State) -> AuthStatus - { + + fn auth_status(&self, state: &mut State) -> AuthStatus { // extract the provided token, if any let token = match &self.source { - AuthSource::Cookie(name) => { - CookieJar::try_borrow_from(&state) - .and_then(|jar| jar.get(&name)) - .map(|cookie| cookie.value().to_owned()) - }, - AuthSource::Header(name) => { - HeaderMap::try_borrow_from(&state) - .and_then(|map| map.get(name)) - .and_then(|header| header.to_str().ok()) - .map(|value| value.to_owned()) - }, - AuthSource::AuthorizationHeader => { - HeaderMap::try_borrow_from(&state) - .and_then(|map| map.get(AUTHORIZATION)) - .and_then(|header| header.to_str().ok()) - .and_then(|value| value.split_whitespace().nth(1)) - .map(|value| value.to_owned()) - } + AuthSource::Cookie(name) => CookieJar::try_borrow_from(&state) + .and_then(|jar| jar.get(&name)) + .map(|cookie| cookie.value().to_owned()), + AuthSource::Header(name) => HeaderMap::try_borrow_from(&state) + .and_then(|map| map.get(name)) + .and_then(|header| header.to_str().ok()) + .map(|value| value.to_owned()), + AuthSource::AuthorizationHeader => HeaderMap::try_borrow_from(&state) + .and_then(|map| map.get(AUTHORIZATION)) + .and_then(|header| header.to_str().ok()) + .and_then(|value| value.split_whitespace().nth(1)) + .map(|value| value.to_owned()) }; - + // unauthed if no token let token = match token { Some(token) => token, None => return AuthStatus::Unauthenticated }; - + // get the secret from the handler, possibly decoding claims ourselves let secret = self.handler.jwt_secret(state, || { let b64 = token.split('.').nth(1)?; let raw = base64::decode_config(b64, base64::URL_SAFE_NO_PAD).ok()?; serde_json::from_slice(&raw).ok()? }); - + // unknown if no secret let secret = match secret { Some(secret) => secret, None => return AuthStatus::Unknown }; - + // validate the token - let data : Data = match jsonwebtoken::decode(&token, &DecodingKey::from_secret(&secret), &self.validation) { + let data: Data = match jsonwebtoken::decode(&token, &DecodingKey::from_secret(&secret), &self.validation) { Ok(data) => data.claims, Err(e) => match dbg!(e.into_kind()) { ErrorKind::ExpiredSignature => return AuthStatus::Expired, _ => return AuthStatus::Invalid } }; - + // we found a valid token AuthStatus::Authenticated(data) } @@ -285,20 +255,20 @@ where impl Middleware for AuthMiddleware where - Data : DeserializeOwned + Send + 'static, - Handler : AuthHandler + Data: DeserializeOwned + Send + 'static, + Handler: AuthHandler { - fn call(self, mut state : State, chain : Chain) -> Pin> + fn call(self, mut state: State, chain: Chain) -> Pin> where - Chain : FnOnce(State) -> Pin> + Chain: FnOnce(State) -> Pin> { // put the source in our state, required for e.g. openapi state.put(self.source.clone()); - + // put the status in our state let status = self.auth_status(&mut state); state.put(status); - + // call the rest of the chain chain(state).and_then(|(state, res)| future::ok((state, res))).boxed() } @@ -306,45 +276,40 @@ where impl NewMiddleware for AuthMiddleware where - Self : Clone + Middleware + Sync + RefUnwindSafe + Self: Clone + Middleware + Sync + RefUnwindSafe { type Instance = Self; - - fn new_middleware(&self) -> Result - { - let c : Self = self.clone(); + + fn new_middleware(&self) -> Result { + let c: Self = self.clone(); Ok(c) } } #[cfg(test)] -mod test -{ +mod test { use super::*; use cookie::Cookie; use std::fmt::Debug; - + // 256-bit random string - const JWT_SECRET : &'static [u8; 32] = b"Lyzsfnta0cdxyF0T9y6VGxp3jpgoMUuW"; - + const JWT_SECRET: &'static [u8; 32] = b"Lyzsfnta0cdxyF0T9y6VGxp3jpgoMUuW"; + // some known tokens const VALID_TOKEN : &'static str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtc3JkMCIsInN1YiI6ImdvdGhhbS1yZXN0ZnVsIiwiaWF0IjoxNTc3ODM2ODAwLCJleHAiOjQxMDI0NDQ4MDB9.8h8Ax-nnykqEQ62t7CxmM3ja6NzUQ4L0MLOOzddjLKk"; const EXPIRED_TOKEN : &'static str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtc3JkMCIsInN1YiI6ImdvdGhhbS1yZXN0ZnVsIiwiaWF0IjoxNTc3ODM2ODAwLCJleHAiOjE1Nzc4MzcxMDB9.eV1snaGLYrJ7qUoMk74OvBY3WUU9M0Je5HTU2xtX1v0"; const INVALID_TOKEN : &'static str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtc3JkMCIsInN1YiI6ImdvdGhhbS1yZXN0ZnVsIiwiaWF0IjoxNTc3ODM2ODAwLCJleHAiOjQxMDI0NDQ4MDB9"; - + #[derive(Debug, Deserialize, PartialEq)] - struct TestData - { - iss : String, - sub : String, - iat : u64, - exp : u64 + struct TestData { + iss: String, + sub: String, + iat: u64, + exp: u64 } - - impl Default for TestData - { - fn default() -> Self - { + + impl Default for TestData { + fn default() -> Self { Self { iss: "msrd0".to_owned(), sub: "gotham-restful".to_owned(), @@ -353,20 +318,17 @@ mod test } } } - + #[derive(Default)] struct NoneAuthHandler; - impl AuthHandler for NoneAuthHandler - { - fn jwt_secret Option>(&self, _state : &mut State, _decode_data : F) -> Option> - { + impl AuthHandler for NoneAuthHandler { + fn jwt_secret Option>(&self, _state: &mut State, _decode_data: F) -> Option> { None } } - + #[test] - fn test_auth_middleware_none_secret() - { + fn test_auth_middleware_none_secret() { let middleware = >::from_source(AuthSource::AuthorizationHeader); State::with_new(|mut state| { let mut headers = HeaderMap::new(); @@ -375,22 +337,21 @@ mod test middleware.auth_status(&mut state); }); } - + #[derive(Default)] struct TestAssertingHandler; impl AuthHandler for TestAssertingHandler - where T : Debug + Default + PartialEq + where + T: Debug + Default + PartialEq { - fn jwt_secret Option>(&self, _state : &mut State, decode_data : F) -> Option> - { + fn jwt_secret Option>(&self, _state: &mut State, decode_data: F) -> Option> { assert_eq!(decode_data(), Some(T::default())); Some(JWT_SECRET.to_vec()) } } - + #[test] - fn test_auth_middleware_decode_data() - { + fn test_auth_middleware_decode_data() { let middleware = >::from_source(AuthSource::AuthorizationHeader); State::with_new(|mut state| { let mut headers = HeaderMap::new(); @@ -399,16 +360,16 @@ mod test middleware.auth_status(&mut state); }); } - - fn new_middleware(source : AuthSource) -> AuthMiddleware - where T : DeserializeOwned + Send + + fn new_middleware(source: AuthSource) -> AuthMiddleware + where + T: DeserializeOwned + Send { AuthMiddleware::new(source, Default::default(), StaticAuthHandler::from_array(JWT_SECRET)) } - + #[test] - fn test_auth_middleware_no_token() - { + fn test_auth_middleware_no_token() { let middleware = new_middleware::(AuthSource::AuthorizationHeader); State::with_new(|mut state| { let status = middleware.auth_status(&mut state); @@ -418,10 +379,9 @@ mod test }; }); } - + #[test] - fn test_auth_middleware_expired_token() - { + fn test_auth_middleware_expired_token() { let middleware = new_middleware::(AuthSource::AuthorizationHeader); State::with_new(|mut state| { let mut headers = HeaderMap::new(); @@ -434,10 +394,9 @@ mod test }; }); } - + #[test] - fn test_auth_middleware_invalid_token() - { + fn test_auth_middleware_invalid_token() { let middleware = new_middleware::(AuthSource::AuthorizationHeader); State::with_new(|mut state| { let mut headers = HeaderMap::new(); @@ -450,10 +409,9 @@ mod test }; }); } - + #[test] - fn test_auth_middleware_auth_header_token() - { + fn test_auth_middleware_auth_header_token() { let middleware = new_middleware::(AuthSource::AuthorizationHeader); State::with_new(|mut state| { let mut headers = HeaderMap::new(); @@ -466,10 +424,9 @@ mod test }; }) } - + #[test] - fn test_auth_middleware_header_token() - { + fn test_auth_middleware_header_token() { let header_name = "x-znoiprwmvfexju"; let middleware = new_middleware::(AuthSource::Header(HeaderName::from_static(header_name))); State::with_new(|mut state| { @@ -483,10 +440,9 @@ mod test }; }) } - + #[test] - fn test_auth_middleware_cookie_token() - { + fn test_auth_middleware_cookie_token() { let cookie_name = "znoiprwmvfexju"; let middleware = new_middleware::(AuthSource::Cookie(cookie_name.to_owned())); State::with_new(|mut state| { diff --git a/src/cors.rs b/src/cors.rs index 46af65c..c17e28e 100644 --- a/src/cors.rs +++ b/src/cors.rs @@ -4,22 +4,19 @@ use gotham::{ helpers::http::response::create_empty_response, hyper::{ header::{ - ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_METHODS, - ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_MAX_AGE, ACCESS_CONTROL_REQUEST_METHOD, ORIGIN, VARY, - HeaderMap, HeaderName, HeaderValue + HeaderMap, HeaderName, HeaderValue, ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_HEADERS, + ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_MAX_AGE, + ACCESS_CONTROL_REQUEST_METHOD, ORIGIN, VARY }, Body, Method, Response, StatusCode }, middleware::Middleware, pipeline::chain::PipelineHandleChain, router::builder::*, - state::{FromState, State}, + state::{FromState, State} }; use itertools::Itertools; -use std::{ - panic::RefUnwindSafe, - pin::Pin -}; +use std::{panic::RefUnwindSafe, pin::Pin}; /** Specify the allowed origins of the request. It is up to the browser to check the validity of the @@ -27,8 +24,7 @@ origin. This, when sent to the browser, will indicate whether or not the request allowed to make the request. */ #[derive(Clone, Debug)] -pub enum Origin -{ +pub enum Origin { /// Do not send any `Access-Control-Allow-Origin` headers. None, /// Send `Access-Control-Allow-Origin: *`. Note that browser will not send credentials. @@ -39,19 +35,15 @@ pub enum Origin Copy } -impl Default for Origin -{ - fn default() -> Self - { +impl Default for Origin { + fn default() -> Self { Self::None } } -impl Origin -{ +impl Origin { /// Get the header value for the `Access-Control-Allow-Origin` header. - fn header_value(&self, state : &State) -> Option - { + fn header_value(&self, state: &State) -> Option { match self { Self::None => None, Self::Star => Some("*".parse().unwrap()), @@ -126,23 +118,21 @@ gotham::start("127.0.0.1:8080", build_router((), pipeline_set, |route| { [`State`]: ../gotham/state/struct.State.html */ #[derive(Clone, Debug, Default, NewMiddleware, StateData)] -pub struct CorsConfig -{ +pub struct CorsConfig { /// The allowed origins. - pub origin : Origin, + pub origin: Origin, /// The allowed headers. - pub headers : Vec, + pub headers: Vec, /// The amount of seconds that the preflight request can be cached. - pub max_age : u64, + pub max_age: u64, /// Whether or not the request may be made with supplying credentials. - pub credentials : bool + pub credentials: bool } -impl Middleware for CorsConfig -{ - fn call(self, mut state : State, chain : Chain) -> Pin> +impl Middleware for CorsConfig { + fn call(self, mut state: State, chain: Chain) -> Pin> where - Chain : FnOnce(State) -> Pin> + Chain: FnOnce(State) -> Pin> { state.put(self); chain(state) @@ -161,35 +151,31 @@ For further information on CORS, read https://developer.mozilla.org/en-US/docs/W [`CorsConfig`]: ./struct.CorsConfig.html */ -pub fn handle_cors(state : &State, res : &mut Response) -{ +pub fn handle_cors(state: &State, res: &mut Response) { let config = CorsConfig::try_borrow_from(state); let headers = res.headers_mut(); - + // non-preflight requests require the Access-Control-Allow-Origin header - if let Some(header) = config.and_then(|cfg| cfg.origin.header_value(state)) - { + if let Some(header) = config.and_then(|cfg| cfg.origin.header_value(state)) { headers.insert(ACCESS_CONTROL_ALLOW_ORIGIN, header); } // if the origin is copied over, we should tell the browser by specifying the Vary header - if matches!(config.map(|cfg| &cfg.origin), Some(Origin::Copy)) - { + if matches!(config.map(|cfg| &cfg.origin), Some(Origin::Copy)) { let vary = headers.get(VARY).map(|vary| format!("{},Origin", vary.to_str().unwrap())); headers.insert(VARY, vary.as_deref().unwrap_or("Origin").parse().unwrap()); } // if we allow credentials, tell the browser - if config.map(|cfg| cfg.credentials).unwrap_or(false) - { + if config.map(|cfg| cfg.credentials).unwrap_or(false) { headers.insert(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true".parse().unwrap()); } } /// Add CORS routing for your path. This is required for handling preflight requests. -/// +/// /// Example: -/// +/// /// ```rust,no_run /// # use gotham::{hyper::{Body, Method, Response}, router::builder::*}; /// # use gotham_restful::*; @@ -206,16 +192,15 @@ pub fn handle_cors(state : &State, res : &mut Response) /// ``` pub trait CorsRoute where - C : PipelineHandleChain

+ Copy + Send + Sync + 'static, - P : RefUnwindSafe + Send + Sync + 'static + C: PipelineHandleChain

+ Copy + Send + Sync + 'static, + P: RefUnwindSafe + Send + Sync + 'static { /// Handle a preflight request on `path` for `method`. To configure the behaviour, use /// [`CorsConfig`](struct.CorsConfig.html). - fn cors(&mut self, path : &str, method : Method); + fn cors(&mut self, path: &str, method: Method); } -fn cors_preflight_handler(state : State) -> (State, Response) -{ +fn cors_preflight_handler(state: State) -> (State, Response) { let config = CorsConfig::try_borrow_from(&state); // prepare the response @@ -223,43 +208,40 @@ fn cors_preflight_handler(state : State) -> (State, Response) let headers = res.headers_mut(); // copy the request method over to the response - let method = HeaderMap::borrow_from(&state).get(ACCESS_CONTROL_REQUEST_METHOD).unwrap().clone(); + let method = HeaderMap::borrow_from(&state) + .get(ACCESS_CONTROL_REQUEST_METHOD) + .unwrap() + .clone(); headers.insert(ACCESS_CONTROL_ALLOW_METHODS, method); // if we allow any headers, put them in - if let Some(hdrs) = config.map(|cfg| &cfg.headers) - { - if hdrs.len() > 0 - { + if let Some(hdrs) = config.map(|cfg| &cfg.headers) { + if hdrs.len() > 0 { // TODO do we want to return all headers or just those asked by the browser? headers.insert(ACCESS_CONTROL_ALLOW_HEADERS, hdrs.iter().join(",").parse().unwrap()); } } // set the max age for the preflight cache - if let Some(age) = config.map(|cfg| cfg.max_age) - { + if let Some(age) = config.map(|cfg| cfg.max_age) { headers.insert(ACCESS_CONTROL_MAX_AGE, age.into()); } // make sure the browser knows that this request was based on the method headers.insert(VARY, "Access-Control-Request-Method".parse().unwrap()); - + handle_cors(&state, &mut res); (state, res) } impl CorsRoute for D where - D : DrawRoutes, - C : PipelineHandleChain

+ Copy + Send + Sync + 'static, - P : RefUnwindSafe + Send + Sync + 'static + D: DrawRoutes, + C: PipelineHandleChain

+ Copy + Send + Sync + 'static, + P: RefUnwindSafe + Send + Sync + 'static { - fn cors(&mut self, path : &str, method : Method) - { + fn cors(&mut self, path: &str, method: Method) { let matcher = AccessControlRequestMethodMatcher::new(method); - self.options(path) - .extend_route_matcher(matcher) - .to(cors_preflight_handler); + self.options(path).extend_route_matcher(matcher).to(cors_preflight_handler); } } diff --git a/src/lib.rs b/src/lib.rs index 8a9ea87..ab08c98 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -263,7 +263,7 @@ type Repo = gotham_middleware_diesel::Repo; fn main() { let repo = Repo::new(&env::var("DATABASE_URL").unwrap()); let diesel = DieselMiddleware::new(repo); - + let (chain, pipelines) = single_pipeline(new_pipeline().add(diesel).build()); gotham::start("127.0.0.1:8080", build_router(chain, pipelines, |route| { route.resource::("foo"); @@ -347,7 +347,7 @@ struct Foo; # Examples -There is a lack of good examples, but there is currently a collection of code in the [example] +There is a lack of good examples, but there is currently a collection of code in the [example] directory, that might help you. Any help writing more examples is highly appreciated. # License @@ -370,9 +370,12 @@ Licensed under your option of: // weird proc macro issue extern crate self as gotham_restful; -#[macro_use] extern crate gotham_derive; -#[macro_use] extern crate log; -#[macro_use] extern crate serde; +#[macro_use] +extern crate gotham_derive; +#[macro_use] +extern crate log; +#[macro_use] +extern crate serde; #[doc(no_inline)] pub use gotham; @@ -388,15 +391,14 @@ pub use gotham_restful_derive::*; /// Not public API #[doc(hidden)] -pub mod export -{ +pub mod export { pub use futures_util::future::FutureExt; - + pub use serde_json; - + #[cfg(feature = "database")] pub use gotham_middleware_diesel::Repo; - + #[cfg(feature = "openapi")] pub use indexmap::IndexMap; #[cfg(feature = "openapi")] @@ -406,24 +408,12 @@ pub mod export #[cfg(feature = "auth")] mod auth; #[cfg(feature = "auth")] -pub use auth::{ - AuthHandler, - AuthMiddleware, - AuthSource, - AuthStatus, - AuthValidation, - StaticAuthHandler -}; +pub use auth::{AuthHandler, AuthMiddleware, AuthSource, AuthStatus, AuthValidation, StaticAuthHandler}; #[cfg(feature = "cors")] mod cors; #[cfg(feature = "cors")] -pub use cors::{ - handle_cors, - CorsConfig, - CorsRoute, - Origin -}; +pub use cors::{handle_cors, CorsConfig, CorsRoute, Origin}; pub mod matcher; @@ -438,16 +428,8 @@ pub use openapi::{ mod resource; pub use resource::{ - Resource, - ResourceMethod, - ResourceReadAll, - ResourceRead, - ResourceSearch, - ResourceCreate, - ResourceChangeAll, - ResourceChange, - ResourceRemoveAll, - ResourceRemove + Resource, ResourceChange, ResourceChangeAll, ResourceCreate, ResourceMethod, ResourceRead, ResourceReadAll, + ResourceRemove, ResourceRemoveAll, ResourceSearch }; mod response; @@ -455,22 +437,14 @@ pub use response::Response; mod result; pub use result::{ - AuthError, - AuthError::Forbidden, - AuthErrorOrOther, - AuthResult, - AuthSuccess, - IntoResponseError, - NoContent, - Raw, - ResourceResult, - Success + AuthError, AuthError::Forbidden, AuthErrorOrOther, AuthResult, AuthSuccess, IntoResponseError, NoContent, Raw, + ResourceResult, Success }; mod routing; -pub use routing::{DrawResources, DrawResourceRoutes}; #[cfg(feature = "openapi")] pub use routing::WithOpenapi; +pub use routing::{DrawResourceRoutes, DrawResources}; mod types; pub use types::*; diff --git a/src/matcher/access_control_request_method.rs b/src/matcher/access_control_request_method.rs index a5e03f2..7668e37 100644 --- a/src/matcher/access_control_request_method.rs +++ b/src/matcher/access_control_request_method.rs @@ -1,13 +1,16 @@ use gotham::{ - hyper::{header::{ACCESS_CONTROL_REQUEST_METHOD, HeaderMap}, Method, StatusCode}, + hyper::{ + header::{HeaderMap, ACCESS_CONTROL_REQUEST_METHOD}, + Method, StatusCode + }, router::{non_match::RouteNonMatch, route::matcher::RouteMatcher}, state::{FromState, State} }; /// A route matcher that checks whether the value of the `Access-Control-Request-Method` header matches the defined value. -/// +/// /// Usage: -/// +/// /// ```rust /// # use gotham::{helpers::http::response::create_empty_response, /// # hyper::{header::ACCESS_CONTROL_ALLOW_METHODS, Method, StatusCode}, @@ -15,44 +18,38 @@ use gotham::{ /// # }; /// # use gotham_restful::matcher::AccessControlRequestMethodMatcher; /// let matcher = AccessControlRequestMethodMatcher::new(Method::PUT); -/// +/// /// # build_simple_router(|route| { /// // use the matcher for your request -/// route.options("/foo") -/// .extend_route_matcher(matcher) -/// .to(|state| { -/// // we know that this is a CORS preflight for a PUT request -/// let mut res = create_empty_response(&state, StatusCode::NO_CONTENT); -/// res.headers_mut().insert(ACCESS_CONTROL_ALLOW_METHODS, "PUT".parse().unwrap()); -/// (state, res) +/// route.options("/foo").extend_route_matcher(matcher).to(|state| { +/// // we know that this is a CORS preflight for a PUT request +/// let mut res = create_empty_response(&state, StatusCode::NO_CONTENT); +/// res.headers_mut().insert(ACCESS_CONTROL_ALLOW_METHODS, "PUT".parse().unwrap()); +/// (state, res) /// }); /// # }); /// ``` #[derive(Clone, Debug)] -pub struct AccessControlRequestMethodMatcher -{ - method : Method +pub struct AccessControlRequestMethodMatcher { + method: Method } -impl AccessControlRequestMethodMatcher -{ +impl AccessControlRequestMethodMatcher { /// Construct a new matcher that matches if the `Access-Control-Request-Method` header matches `method`. /// Note that during matching the method is normalized according to the fetch specification, that is, /// byte-uppercased. This means that when using a custom `method` instead of a predefined one, make sure /// it is uppercased or this matcher will never succeed. - pub fn new(method : Method) -> Self - { + pub fn new(method: Method) -> Self { Self { method } } } -impl RouteMatcher for AccessControlRequestMethodMatcher -{ - fn is_match(&self, state : &State) -> Result<(), RouteNonMatch> - { +impl RouteMatcher for AccessControlRequestMethodMatcher { + fn is_match(&self, state: &State) -> Result<(), RouteNonMatch> { // according to the fetch specification, methods should be normalized by byte-uppercase // https://fetch.spec.whatwg.org/#concept-method - match HeaderMap::borrow_from(state).get(ACCESS_CONTROL_REQUEST_METHOD) + match HeaderMap::borrow_from(state) + .get(ACCESS_CONTROL_REQUEST_METHOD) .and_then(|value| value.to_str().ok()) .and_then(|str| str.to_ascii_uppercase().parse::().ok()) { @@ -62,19 +59,17 @@ impl RouteMatcher for AccessControlRequestMethodMatcher } } - #[cfg(test)] -mod test -{ +mod test { use super::*; - fn with_state(accept : Option<&str>, block : F) - where F : FnOnce(&mut State) -> () + fn with_state(accept: Option<&str>, block: F) + where + F: FnOnce(&mut State) -> () { State::with_new(|state| { let mut headers = HeaderMap::new(); - if let Some(acc) = accept - { + if let Some(acc) = accept { headers.insert(ACCESS_CONTROL_REQUEST_METHOD, acc.parse().unwrap()); } state.put(headers); @@ -83,23 +78,20 @@ mod test } #[test] - fn no_acrm_header() - { + fn no_acrm_header() { let matcher = AccessControlRequestMethodMatcher::new(Method::PUT); with_state(None, |state| assert!(matcher.is_match(&state).is_err())); } #[test] - fn correct_acrm_header() - { + fn correct_acrm_header() { let matcher = AccessControlRequestMethodMatcher::new(Method::PUT); with_state(Some("PUT"), |state| assert!(matcher.is_match(&state).is_ok())); with_state(Some("put"), |state| assert!(matcher.is_match(&state).is_ok())); } #[test] - fn incorrect_acrm_header() - { + fn incorrect_acrm_header() { let matcher = AccessControlRequestMethodMatcher::new(Method::PUT); with_state(Some("DELETE"), |state| assert!(matcher.is_match(&state).is_err())); } diff --git a/src/matcher/mod.rs b/src/matcher/mod.rs index cc7e734..153bdfe 100644 --- a/src/matcher/mod.rs +++ b/src/matcher/mod.rs @@ -2,4 +2,3 @@ mod access_control_request_method; #[cfg(feature = "cors")] pub use access_control_request_method::AccessControlRequestMethodMatcher; - diff --git a/src/openapi/builder.rs b/src/openapi/builder.rs index bf81caa..f582015 100644 --- a/src/openapi/builder.rs +++ b/src/openapi/builder.rs @@ -1,29 +1,26 @@ -use crate::{OpenapiType, OpenapiSchema}; +use crate::{OpenapiSchema, OpenapiType}; use indexmap::IndexMap; use openapiv3::{ - Components, OpenAPI, PathItem, ReferenceOr, ReferenceOr::Item, ReferenceOr::Reference, Schema, - Server + Components, OpenAPI, PathItem, ReferenceOr, + ReferenceOr::{Item, Reference}, + Schema, Server }; use std::sync::{Arc, RwLock}; #[derive(Clone, Debug)] -pub struct OpenapiInfo -{ - pub title : String, - pub version : String, - pub urls : Vec +pub struct OpenapiInfo { + pub title: String, + pub version: String, + pub urls: Vec } #[derive(Clone, Debug)] -pub struct OpenapiBuilder -{ - pub openapi : Arc> +pub struct OpenapiBuilder { + pub openapi: Arc> } -impl OpenapiBuilder -{ - pub fn new(info : OpenapiInfo) -> Self - { +impl OpenapiBuilder { + pub fn new(info: OpenapiInfo) -> Self { Self { openapi: Arc::new(RwLock::new(OpenAPI { openapi: "3.0.2".to_string(), @@ -32,18 +29,22 @@ impl OpenapiBuilder version: info.version, ..Default::default() }, - servers: info.urls.into_iter() - .map(|url| Server { url, ..Default::default() }) + servers: info + .urls + .into_iter() + .map(|url| Server { + url, + ..Default::default() + }) .collect(), ..Default::default() })) } } - + /// Remove path from the OpenAPI spec, or return an empty one if not included. This is handy if you need to /// modify the path and add it back after the modification - pub fn remove_path(&mut self, path : &str) -> PathItem - { + pub fn remove_path(&mut self, path: &str) -> PathItem { let mut openapi = self.openapi.write().unwrap(); match openapi.paths.swap_remove(path) { Some(Item(item)) => item, @@ -51,16 +52,14 @@ impl OpenapiBuilder } } - pub fn add_path(&mut self, path : Path, item : PathItem) - { + pub fn add_path(&mut self, path: Path, item: PathItem) { let mut openapi = self.openapi.write().unwrap(); openapi.paths.insert(path.to_string(), Item(item)); } - fn add_schema_impl(&mut self, name : String, mut schema : OpenapiSchema) - { + fn add_schema_impl(&mut self, name: String, mut schema: OpenapiSchema) { self.add_schema_dependencies(&mut schema.dependencies); - + let mut openapi = self.openapi.write().unwrap(); match &mut openapi.components { Some(comp) => { @@ -74,25 +73,23 @@ impl OpenapiBuilder }; } - fn add_schema_dependencies(&mut self, dependencies : &mut IndexMap) - { - let keys : Vec = dependencies.keys().map(|k| k.to_string()).collect(); - for dep in keys - { + fn add_schema_dependencies(&mut self, dependencies: &mut IndexMap) { + let keys: Vec = dependencies.keys().map(|k| k.to_string()).collect(); + for dep in keys { let dep_schema = dependencies.swap_remove(&dep); - if let Some(dep_schema) = dep_schema - { + if let Some(dep_schema) = dep_schema { self.add_schema_impl(dep, dep_schema); } } } - - pub fn add_schema(&mut self) -> ReferenceOr - { + + pub fn add_schema(&mut self) -> ReferenceOr { let mut schema = T::schema(); match schema.name.clone() { Some(name) => { - let reference = Reference { reference: format!("#/components/schemas/{}", name) }; + let reference = Reference { + reference: format!("#/components/schemas/{}", name) + }; self.add_schema_impl(name, schema); reference }, @@ -104,59 +101,57 @@ impl OpenapiBuilder } } - #[cfg(test)] #[allow(dead_code)] -mod test -{ +mod test { use super::*; - + #[derive(OpenapiType)] - struct Message - { - msg : String + struct Message { + msg: String } - + #[derive(OpenapiType)] - struct Messages - { - msgs : Vec + struct Messages { + msgs: Vec } - - fn info() -> OpenapiInfo - { + + fn info() -> OpenapiInfo { OpenapiInfo { title: "TEST CASE".to_owned(), version: "1.2.3".to_owned(), urls: vec!["http://localhost:1234".to_owned(), "https://example.org".to_owned()] } } - - fn openapi(builder : OpenapiBuilder) -> OpenAPI - { + + fn openapi(builder: OpenapiBuilder) -> OpenAPI { Arc::try_unwrap(builder.openapi).unwrap().into_inner().unwrap() } - + #[test] - fn new_builder() - { + fn new_builder() { let info = info(); let builder = OpenapiBuilder::new(info.clone()); let openapi = openapi(builder); - + assert_eq!(info.title, openapi.info.title); assert_eq!(info.version, openapi.info.version); assert_eq!(info.urls.len(), openapi.servers.len()); } - + #[test] - fn add_schema() - { + fn add_schema() { let mut builder = OpenapiBuilder::new(info()); builder.add_schema::>(); let openapi = openapi(builder); - - assert_eq!(openapi.components.clone().unwrap_or_default().schemas["Message"] , ReferenceOr::Item(Message ::schema().into_schema())); - assert_eq!(openapi.components.clone().unwrap_or_default().schemas["Messages"], ReferenceOr::Item(Messages::schema().into_schema())); + + assert_eq!( + openapi.components.clone().unwrap_or_default().schemas["Message"], + ReferenceOr::Item(Message::schema().into_schema()) + ); + assert_eq!( + openapi.components.clone().unwrap_or_default().schemas["Messages"], + ReferenceOr::Item(Messages::schema().into_schema()) + ); } } diff --git a/src/openapi/handler.rs b/src/openapi/handler.rs index 2054b0d..359f1f3 100644 --- a/src/openapi/handler.rs +++ b/src/openapi/handler.rs @@ -15,40 +15,34 @@ use std::{ }; #[derive(Clone)] -pub struct OpenapiHandler -{ - openapi : Arc> +pub struct OpenapiHandler { + openapi: Arc> } -impl OpenapiHandler -{ - pub fn new(openapi : Arc>) -> Self - { +impl OpenapiHandler { + pub fn new(openapi: Arc>) -> Self { Self { openapi } } } -impl NewHandler for OpenapiHandler -{ +impl NewHandler for OpenapiHandler { type Instance = Self; - - fn new_handler(&self) -> Result - { + + fn new_handler(&self) -> Result { Ok(self.clone()) } } #[cfg(feature = "auth")] -fn get_security(state : &mut State) -> IndexMap> -{ +fn get_security(state: &mut State) -> IndexMap> { use crate::AuthSource; use gotham::state::FromState; - + let source = match AuthSource::try_borrow_from(state) { Some(source) => source, None => return Default::default() }; - + let security_scheme = match source { AuthSource::Cookie(name) => SecurityScheme::APIKey { location: APIKeyLocation::Cookie, @@ -63,38 +57,35 @@ fn get_security(state : &mut State) -> IndexMap> = Default::default(); + + let mut security_schemes: IndexMap> = Default::default(); security_schemes.insert(SECURITY_NAME.to_owned(), ReferenceOr::Item(security_scheme)); - + security_schemes } #[cfg(not(feature = "auth"))] -fn get_security(state : &mut State) -> (Vec, IndexMap>) -{ +fn get_security(state: &mut State) -> (Vec, IndexMap>) { Default::default() } -impl Handler for OpenapiHandler -{ - fn handle(self, mut state : State) -> Pin> - { +impl Handler for OpenapiHandler { + fn handle(self, mut state: State) -> Pin> { let openapi = match self.openapi.read() { Ok(openapi) => openapi, Err(e) => { error!("Unable to acquire read lock for the OpenAPI specification: {}", e); let res = create_response(&state, crate::StatusCode::INTERNAL_SERVER_ERROR, TEXT_PLAIN, ""); - return future::ok((state, res)).boxed() + return future::ok((state, res)).boxed(); } }; - + let mut openapi = openapi.clone(); let security_schemes = get_security(&mut state); let mut components = openapi.components.unwrap_or_default(); components.security_schemes = security_schemes; openapi.components = Some(components); - + match serde_json::to_string(&openapi) { Ok(body) => { let res = create_response(&state, crate::StatusCode::OK, APPLICATION_JSON, body); diff --git a/src/openapi/mod.rs b/src/openapi/mod.rs index 141ea22..500d190 100644 --- a/src/openapi/mod.rs +++ b/src/openapi/mod.rs @@ -1,5 +1,4 @@ - -const SECURITY_NAME : &str = "authToken"; +const SECURITY_NAME: &str = "authToken"; pub mod builder; pub mod handler; diff --git a/src/openapi/operation.rs b/src/openapi/operation.rs index fc06e43..a70e258 100644 --- a/src/openapi/operation.rs +++ b/src/openapi/operation.rs @@ -1,32 +1,21 @@ -use crate::{ - resource::*, - result::*, - OpenapiSchema, - RequestBody -}; use super::SECURITY_NAME; +use crate::{resource::*, result::*, OpenapiSchema, RequestBody}; use indexmap::IndexMap; use mime::Mime; use openapiv3::{ - MediaType, Operation, Parameter, ParameterData, ParameterSchemaOrContent, ReferenceOr, - ReferenceOr::Item, RequestBody as OARequestBody, Response, Responses, Schema, SchemaKind, - StatusCode, Type + MediaType, Operation, Parameter, ParameterData, ParameterSchemaOrContent, ReferenceOr, ReferenceOr::Item, + RequestBody as OARequestBody, Response, Responses, Schema, SchemaKind, StatusCode, Type }; - #[derive(Default)] -struct OperationParams<'a> -{ - path_params : Vec<(&'a str, ReferenceOr)>, - query_params : Option +struct OperationParams<'a> { + path_params: Vec<(&'a str, ReferenceOr)>, + query_params: Option } -impl<'a> OperationParams<'a> -{ - fn add_path_params(&self, params : &mut Vec>) - { - for param in &self.path_params - { +impl<'a> OperationParams<'a> { + fn add_path_params(&self, params: &mut Vec>) { + for param in &self.path_params { params.push(Item(Parameter::Path { parameter_data: ParameterData { name: (*param).0.to_string(), @@ -37,13 +26,12 @@ impl<'a> OperationParams<'a> example: None, examples: IndexMap::new() }, - style: Default::default(), + style: Default::default() })); } } - - fn add_query_params(self, params : &mut Vec>) - { + + fn add_query_params(self, params: &mut Vec>) { let query_params = match self.query_params { Some(qp) => qp.schema, None => return @@ -52,8 +40,7 @@ impl<'a> OperationParams<'a> SchemaKind::Type(Type::Object(ty)) => ty, _ => panic!("Query Parameters needs to be a plain struct") }; - for (name, schema) in query_params.properties - { + for (name, schema) in query_params.properties { let required = query_params.required.contains(&name); params.push(Item(Parameter::Query { parameter_data: ParameterData { @@ -71,32 +58,28 @@ impl<'a> OperationParams<'a> })) } } - - fn into_params(self) -> Vec> - { - let mut params : Vec> = Vec::new(); + + fn into_params(self) -> Vec> { + let mut params: Vec> = Vec::new(); self.add_path_params(&mut params); self.add_query_params(&mut params); params } } -pub struct OperationDescription<'a> -{ - operation_id : Option, - default_status : crate::StatusCode, - accepted_types : Option>, - schema : ReferenceOr, - params : OperationParams<'a>, - body_schema : Option>, - supported_types : Option>, - requires_auth : bool +pub struct OperationDescription<'a> { + operation_id: Option, + default_status: crate::StatusCode, + accepted_types: Option>, + schema: ReferenceOr, + params: OperationParams<'a>, + body_schema: Option>, + supported_types: Option>, + requires_auth: bool } -impl<'a> OperationDescription<'a> -{ - pub fn new(schema : ReferenceOr) -> Self - { +impl<'a> OperationDescription<'a> { + pub fn new(schema: ReferenceOr) -> Self { Self { operation_id: Handler::operation_id(), default_status: Handler::Res::default_status(), @@ -108,32 +91,26 @@ impl<'a> OperationDescription<'a> requires_auth: Handler::wants_auth() } } - - pub fn add_path_param(mut self, name : &'a str, schema : ReferenceOr) -> Self - { + + pub fn add_path_param(mut self, name: &'a str, schema: ReferenceOr) -> Self { self.params.path_params.push((name, schema)); self } - - pub fn with_query_params(mut self, params : OpenapiSchema) -> Self - { + + pub fn with_query_params(mut self, params: OpenapiSchema) -> Self { self.params.query_params = Some(params); self } - - pub fn with_body(mut self, schema : ReferenceOr) -> Self - { + + pub fn with_body(mut self, schema: ReferenceOr) -> Self { self.body_schema = Some(schema); self.supported_types = Body::supported_types(); self } - - - fn schema_to_content(types : Vec, schema : ReferenceOr) -> IndexMap - { - let mut content : IndexMap = IndexMap::new(); - for ty in types - { + + fn schema_to_content(types: Vec, schema: ReferenceOr) -> IndexMap { + let mut content: IndexMap = IndexMap::new(); + for ty in types { content.insert(ty.to_string(), MediaType { schema: Some(schema.clone()), ..Default::default() @@ -141,36 +118,47 @@ impl<'a> OperationDescription<'a> } content } - - pub fn into_operation(self) -> Operation - { + + pub fn into_operation(self) -> Operation { // this is unfortunately neccessary to prevent rust from complaining about partially moving self let (operation_id, default_status, accepted_types, schema, params, body_schema, supported_types, requires_auth) = ( - self.operation_id, self.default_status, self.accepted_types, self.schema, self.params, self.body_schema, self.supported_types, self.requires_auth); - + self.operation_id, + self.default_status, + self.accepted_types, + self.schema, + self.params, + self.body_schema, + self.supported_types, + self.requires_auth + ); + let content = Self::schema_to_content(accepted_types.or_all_types(), schema); - - let mut responses : IndexMap> = IndexMap::new(); - responses.insert(StatusCode::Code(default_status.as_u16()), Item(Response { - description: default_status.canonical_reason().map(|d| d.to_string()).unwrap_or_default(), - content, - ..Default::default() - })); - - let request_body = body_schema.map(|schema| Item(OARequestBody { - description: None, - content: Self::schema_to_content(supported_types.or_all_types(), schema), - required: true - })); - + + let mut responses: IndexMap> = IndexMap::new(); + responses.insert( + StatusCode::Code(default_status.as_u16()), + Item(Response { + description: default_status.canonical_reason().map(|d| d.to_string()).unwrap_or_default(), + content, + ..Default::default() + }) + ); + + let request_body = body_schema.map(|schema| { + Item(OARequestBody { + description: None, + content: Self::schema_to_content(supported_types.or_all_types(), schema), + required: true + }) + }); + let mut security = Vec::new(); - if requires_auth - { + if requires_auth { let mut sec = IndexMap::new(); sec.insert(SECURITY_NAME.to_owned(), Vec::new()); security.push(sec); } - + Operation { tags: Vec::new(), operation_id, @@ -187,25 +175,21 @@ impl<'a> OperationDescription<'a> } } - #[cfg(test)] -mod test -{ - use crate::{OpenapiType, ResourceResult}; +mod test { use super::*; - + use crate::{OpenapiType, ResourceResult}; + #[test] - fn no_content_schema_to_content() - { + fn no_content_schema_to_content() { let types = NoContent::accepted_types(); let schema = ::schema(); let content = OperationDescription::schema_to_content(types.or_all_types(), Item(schema.into_schema())); assert!(content.is_empty()); } - + #[test] - fn raw_schema_to_content() - { + fn raw_schema_to_content() { let types = Raw::<&str>::accepted_types(); let schema = as OpenapiType>::schema(); let content = OperationDescription::schema_to_content(types.or_all_types(), Item(schema.into_schema())); diff --git a/src/openapi/router.rs b/src/openapi/router.rs index 6dd7a13..19ce1a6 100644 --- a/src/openapi/router.rs +++ b/src/openapi/router.rs @@ -1,40 +1,30 @@ -use crate::{ - resource::*, - routing::*, - OpenapiType, -}; use super::{builder::OpenapiBuilder, handler::OpenapiHandler, operation::OperationDescription}; -use gotham::{ - pipeline::chain::PipelineHandleChain, - router::builder::* -}; +use crate::{resource::*, routing::*, OpenapiType}; +use gotham::{pipeline::chain::PipelineHandleChain, router::builder::*}; use std::panic::RefUnwindSafe; /// This trait adds the `get_openapi` method to an OpenAPI-aware router. -pub trait GetOpenapi -{ - fn get_openapi(&mut self, path : &str); +pub trait GetOpenapi { + fn get_openapi(&mut self, path: &str); } #[derive(Debug)] -pub struct OpenapiRouter<'a, D> -{ - pub(crate) router : &'a mut D, - pub(crate) scope : Option<&'a str>, - pub(crate) openapi_builder : &'a mut OpenapiBuilder +pub struct OpenapiRouter<'a, D> { + pub(crate) router: &'a mut D, + pub(crate) scope: Option<&'a str>, + pub(crate) openapi_builder: &'a mut OpenapiBuilder } macro_rules! implOpenapiRouter { ($implType:ident) => { - impl<'a, 'b, C, P> OpenapiRouter<'a, $implType<'b, C, P>> where - C : PipelineHandleChain

+ Copy + Send + Sync + 'static, - P : RefUnwindSafe + Send + Sync + 'static + C: PipelineHandleChain

+ Copy + Send + Sync + 'static, + P: RefUnwindSafe + Send + Sync + 'static { - pub fn scope(&mut self, path : &str, callback : F) + pub fn scope(&mut self, path: &str, callback: F) where - F : FnOnce(&mut OpenapiRouter<'_, ScopeBuilder<'_, C, P>>) + F: FnOnce(&mut OpenapiRouter<'_, ScopeBuilder<'_, C, P>>) { let mut openapi_builder = self.openapi_builder.clone(); let new_scope = self.scope.map(|scope| format!("{}/{}", scope, path).replace("//", "/")); @@ -48,107 +38,120 @@ macro_rules! implOpenapiRouter { }); } } - + impl<'a, 'b, C, P> GetOpenapi for OpenapiRouter<'a, $implType<'b, C, P>> where - C : PipelineHandleChain

+ Copy + Send + Sync + 'static, - P : RefUnwindSafe + Send + Sync + 'static + C: PipelineHandleChain

+ Copy + Send + Sync + 'static, + P: RefUnwindSafe + Send + Sync + 'static { - fn get_openapi(&mut self, path : &str) - { - self.router.get(path).to_new_handler(OpenapiHandler::new(self.openapi_builder.openapi.clone())); + fn get_openapi(&mut self, path: &str) { + self.router + .get(path) + .to_new_handler(OpenapiHandler::new(self.openapi_builder.openapi.clone())); } } - + impl<'a, 'b, C, P> DrawResources for OpenapiRouter<'a, $implType<'b, C, P>> where - C : PipelineHandleChain

+ Copy + Send + Sync + 'static, - P : RefUnwindSafe + Send + Sync + 'static + C: PipelineHandleChain

+ Copy + Send + Sync + 'static, + P: RefUnwindSafe + Send + Sync + 'static { - fn resource(&mut self, path : &str) - { + fn resource(&mut self, path: &str) { R::setup((self, path)); } } impl<'a, 'b, C, P> DrawResourceRoutes for (&mut OpenapiRouter<'a, $implType<'b, C, P>>, &str) where - C : PipelineHandleChain

+ Copy + Send + Sync + 'static, - P : RefUnwindSafe + Send + Sync + 'static + C: PipelineHandleChain

+ Copy + Send + Sync + 'static, + P: RefUnwindSafe + Send + Sync + 'static { - fn read_all(&mut self) - { + fn read_all(&mut self) { let schema = (self.0).openapi_builder.add_schema::(); - + let path = format!("{}/{}", self.0.scope.unwrap_or_default(), self.1); let mut item = (self.0).openapi_builder.remove_path(&path); item.get = Some(OperationDescription::new::(schema).into_operation()); (self.0).openapi_builder.add_path(path, item); - + (&mut *(self.0).router, self.1).read_all::() } - - fn read(&mut self) - { + + fn read(&mut self) { let schema = (self.0).openapi_builder.add_schema::(); let id_schema = (self.0).openapi_builder.add_schema::(); let path = format!("{}/{}/{{id}}", self.0.scope.unwrap_or_default(), self.1); let mut item = (self.0).openapi_builder.remove_path(&path); - item.get = Some(OperationDescription::new::(schema).add_path_param("id", id_schema).into_operation()); + item.get = Some( + OperationDescription::new::(schema) + .add_path_param("id", id_schema) + .into_operation() + ); (self.0).openapi_builder.add_path(path, item); - + (&mut *(self.0).router, self.1).read::() } - - fn search(&mut self) - { + + fn search(&mut self) { let schema = (self.0).openapi_builder.add_schema::(); - + let path = format!("{}/{}/search", self.0.scope.unwrap_or_default(), self.1); let mut item = (self.0).openapi_builder.remove_path(&path); - item.get = Some(OperationDescription::new::(schema).with_query_params(Handler::Query::schema()).into_operation()); + item.get = Some( + OperationDescription::new::(schema) + .with_query_params(Handler::Query::schema()) + .into_operation() + ); (self.0).openapi_builder.add_path(path, item); - + (&mut *(self.0).router, self.1).search::() } - - fn create(&mut self) + + fn create(&mut self) where - Handler::Res : 'static, - Handler::Body : 'static + Handler::Res: 'static, + Handler::Body: 'static { let schema = (self.0).openapi_builder.add_schema::(); let body_schema = (self.0).openapi_builder.add_schema::(); let path = format!("{}/{}", self.0.scope.unwrap_or_default(), self.1); let mut item = (self.0).openapi_builder.remove_path(&path); - item.post = Some(OperationDescription::new::(schema).with_body::(body_schema).into_operation()); + item.post = Some( + OperationDescription::new::(schema) + .with_body::(body_schema) + .into_operation() + ); (self.0).openapi_builder.add_path(path, item); - + (&mut *(self.0).router, self.1).create::() } - - fn change_all(&mut self) + + fn change_all(&mut self) where - Handler::Res : 'static, - Handler::Body : 'static + Handler::Res: 'static, + Handler::Body: 'static { let schema = (self.0).openapi_builder.add_schema::(); let body_schema = (self.0).openapi_builder.add_schema::(); let path = format!("{}/{}", self.0.scope.unwrap_or_default(), self.1); let mut item = (self.0).openapi_builder.remove_path(&path); - item.put = Some(OperationDescription::new::(schema).with_body::(body_schema).into_operation()); + item.put = Some( + OperationDescription::new::(schema) + .with_body::(body_schema) + .into_operation() + ); (self.0).openapi_builder.add_path(path, item); - + (&mut *(self.0).router, self.1).change_all::() } - - fn change(&mut self) + + fn change(&mut self) where - Handler::Res : 'static, - Handler::Body : 'static + Handler::Res: 'static, + Handler::Body: 'static { let schema = (self.0).openapi_builder.add_schema::(); let id_schema = (self.0).openapi_builder.add_schema::(); @@ -156,39 +159,45 @@ macro_rules! implOpenapiRouter { let path = format!("{}/{}/{{id}}", self.0.scope.unwrap_or_default(), self.1); let mut item = (self.0).openapi_builder.remove_path(&path); - item.put = Some(OperationDescription::new::(schema).add_path_param("id", id_schema).with_body::(body_schema).into_operation()); + item.put = Some( + OperationDescription::new::(schema) + .add_path_param("id", id_schema) + .with_body::(body_schema) + .into_operation() + ); (self.0).openapi_builder.add_path(path, item); - + (&mut *(self.0).router, self.1).change::() } - - fn remove_all(&mut self) - { + + fn remove_all(&mut self) { let schema = (self.0).openapi_builder.add_schema::(); let path = format!("{}/{}", self.0.scope.unwrap_or_default(), self.1); let mut item = (self.0).openapi_builder.remove_path(&path); item.delete = Some(OperationDescription::new::(schema).into_operation()); (self.0).openapi_builder.add_path(path, item); - + (&mut *(self.0).router, self.1).remove_all::() } - - fn remove(&mut self) - { + + fn remove(&mut self) { let schema = (self.0).openapi_builder.add_schema::(); let id_schema = (self.0).openapi_builder.add_schema::(); let path = format!("{}/{}/{{id}}", self.0.scope.unwrap_or_default(), self.1); let mut item = (self.0).openapi_builder.remove_path(&path); - item.delete = Some(OperationDescription::new::(schema).add_path_param("id", id_schema).into_operation()); + item.delete = Some( + OperationDescription::new::(schema) + .add_path_param("id", id_schema) + .into_operation() + ); (self.0).openapi_builder.add_path(path, item); - + (&mut *(self.0).router, self.1).remove::() } } - - } + }; } implOpenapiRouter!(RouterBuilder); diff --git a/src/openapi/types.rs b/src/openapi/types.rs index c7ff5e4..e506d7a 100644 --- a/src/openapi/types.rs +++ b/src/openapi/types.rs @@ -1,18 +1,17 @@ #[cfg(feature = "chrono")] -use chrono::{ - Date, DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, Utc -}; +use chrono::{Date, DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, Utc}; use indexmap::IndexMap; use openapiv3::{ - AdditionalProperties, ArrayType, IntegerType, NumberFormat, NumberType, ObjectType, ReferenceOr::Item, - ReferenceOr::Reference, Schema, SchemaData, SchemaKind, StringType, Type, VariantOrUnknownOrEmpty + AdditionalProperties, ArrayType, IntegerType, NumberFormat, NumberType, ObjectType, + ReferenceOr::{Item, Reference}, + Schema, SchemaData, SchemaKind, StringType, Type, VariantOrUnknownOrEmpty }; -#[cfg(feature = "uuid")] -use uuid::Uuid; use std::{ collections::{BTreeSet, HashMap, HashSet}, hash::BuildHasher }; +#[cfg(feature = "uuid")] +use uuid::Uuid; /** This struct needs to be available for every type that can be part of an OpenAPI Spec. It is @@ -22,26 +21,23 @@ for your type, simply derive from [`OpenapiType`]. [`OpenapiType`]: trait.OpenapiType.html */ #[derive(Debug, Clone, PartialEq)] -pub struct OpenapiSchema -{ +pub struct OpenapiSchema { /// The name of this schema. If it is None, the schema will be inlined. - pub name : Option, + pub name: Option, /// Whether this particular schema is nullable. Note that there is no guarantee that this will /// make it into the final specification, it might just be interpreted as a hint to make it /// an optional parameter. - pub nullable : bool, + pub nullable: bool, /// The actual OpenAPI schema. - pub schema : SchemaKind, + pub schema: SchemaKind, /// Other schemas that this schema depends on. They will be included in the final OpenAPI Spec /// along with this schema. - pub dependencies : IndexMap + pub dependencies: IndexMap } -impl OpenapiSchema -{ +impl OpenapiSchema { /// Create a new schema that has no name. - pub fn new(schema : SchemaKind) -> Self - { + pub fn new(schema: SchemaKind) -> Self { Self { name: None, nullable: false, @@ -49,10 +45,9 @@ impl OpenapiSchema dependencies: IndexMap::new() } } - + /// Convert this schema to an `openapiv3::Schema` that can be serialized to the OpenAPI Spec. - pub fn into_schema(self) -> Schema - { + pub fn into_schema(self) -> Schema { Schema { schema_data: SchemaData { nullable: self.nullable, @@ -80,15 +75,12 @@ struct MyResponse { [`OpenapiSchema`]: struct.OpenapiSchema.html */ -pub trait OpenapiType -{ +pub trait OpenapiType { fn schema() -> OpenapiSchema; } -impl OpenapiType for () -{ - fn schema() -> OpenapiSchema - { +impl OpenapiType for () { + fn schema() -> OpenapiSchema { OpenapiSchema::new(SchemaKind::Type(Type::Object(ObjectType { additional_properties: Some(AdditionalProperties::Any(false)), ..Default::default() @@ -96,11 +88,9 @@ impl OpenapiType for () } } -impl OpenapiType for bool -{ - fn schema() -> OpenapiSchema - { - OpenapiSchema::new(SchemaKind::Type(Type::Boolean{})) +impl OpenapiType for bool { + fn schema() -> OpenapiSchema { + OpenapiSchema::new(SchemaKind::Type(Type::Boolean {})) } } @@ -114,7 +104,7 @@ macro_rules! int_types { } } )*}; - + (unsigned $($int_ty:ty),*) => {$( impl OpenapiType for $int_ty { @@ -127,7 +117,7 @@ macro_rules! int_types { } } )*}; - + (bits = $bits:expr, $($int_ty:ty),*) => {$( impl OpenapiType for $int_ty { @@ -140,7 +130,7 @@ macro_rules! int_types { } } )*}; - + (unsigned bits = $bits:expr, $($int_ty:ty),*) => {$( impl OpenapiType for $int_ty { @@ -203,7 +193,7 @@ macro_rules! str_types { fn schema() -> OpenapiSchema { use openapiv3::StringFormat; - + OpenapiSchema::new(SchemaKind::Type(Type::String(StringType { format: VariantOrUnknownOrEmpty::Item(StringFormat::$format), ..Default::default() @@ -211,7 +201,7 @@ macro_rules! str_types { } } )*}; - + (format_str = $format:expr, $($str_ty:ty),*) => {$( impl OpenapiType for $str_ty { @@ -231,26 +221,32 @@ str_types!(String, &str); #[cfg(feature = "chrono")] str_types!(format = Date, Date, Date, Date, NaiveDate); #[cfg(feature = "chrono")] -str_types!(format = DateTime, DateTime, DateTime, DateTime, NaiveDateTime); +str_types!( + format = DateTime, + DateTime, + DateTime, + DateTime, + NaiveDateTime +); #[cfg(feature = "uuid")] str_types!(format_str = "uuid", Uuid); -impl OpenapiType for Option -{ - fn schema() -> OpenapiSchema - { +impl OpenapiType for Option { + fn schema() -> OpenapiSchema { let schema = T::schema(); let mut dependencies = schema.dependencies.clone(); let schema = match schema.name.clone() { Some(name) => { - let reference = Reference { reference: format!("#/components/schemas/{}", name) }; + let reference = Reference { + reference: format!("#/components/schemas/{}", name) + }; dependencies.insert(name, schema); SchemaKind::AllOf { all_of: vec![reference] } }, None => schema.schema }; - + OpenapiSchema { nullable: true, name: None, @@ -260,22 +256,22 @@ impl OpenapiType for Option } } -impl OpenapiType for Vec -{ - fn schema() -> OpenapiSchema - { +impl OpenapiType for Vec { + fn schema() -> OpenapiSchema { let schema = T::schema(); let mut dependencies = schema.dependencies.clone(); - + let items = match schema.name.clone() { Some(name) => { - let reference = Reference { reference: format!("#/components/schemas/{}", name) }; + let reference = Reference { + reference: format!("#/components/schemas/{}", name) + }; dependencies.insert(name, schema); reference }, None => Item(Box::new(schema.into_schema())) }; - + OpenapiSchema { nullable: false, name: None, @@ -290,38 +286,34 @@ impl OpenapiType for Vec } } -impl OpenapiType for BTreeSet -{ - fn schema() -> OpenapiSchema - { +impl OpenapiType for BTreeSet { + fn schema() -> OpenapiSchema { as OpenapiType>::schema() } } -impl OpenapiType for HashSet -{ - fn schema() -> OpenapiSchema - { +impl OpenapiType for HashSet { + fn schema() -> OpenapiSchema { as OpenapiType>::schema() } } -impl OpenapiType for HashMap -{ - fn schema() -> OpenapiSchema - { +impl OpenapiType for HashMap { + fn schema() -> OpenapiSchema { let schema = T::schema(); let mut dependencies = schema.dependencies.clone(); - + let items = Box::new(match schema.name.clone() { Some(name) => { - let reference = Reference { reference: format!("#/components/schemas/{}", name) }; + let reference = Reference { + reference: format!("#/components/schemas/{}", name) + }; dependencies.insert(name, schema); reference }, None => Item(schema.into_schema()) }); - + OpenapiSchema { nullable: false, name: None, @@ -334,10 +326,8 @@ impl OpenapiType for HashMap } } -impl OpenapiType for serde_json::Value -{ - fn schema() -> OpenapiSchema - { +impl OpenapiType for serde_json::Value { + fn schema() -> OpenapiSchema { OpenapiSchema { nullable: true, name: None, @@ -347,15 +337,13 @@ impl OpenapiType for serde_json::Value } } - #[cfg(test)] -mod test -{ +mod test { use super::*; use serde_json::Value; - + type Unit = (); - + macro_rules! assert_schema { ($ty:ident $(<$($generic:ident),+>)* => $json:expr) => { paste::item! { @@ -369,7 +357,7 @@ mod test } }; } - + assert_schema!(Unit => r#"{"type":"object","additionalProperties":false}"#); assert_schema!(bool => r#"{"type":"boolean"}"#); assert_schema!(isize => r#"{"type":"integer"}"#); @@ -386,7 +374,7 @@ mod test assert_schema!(u128 => r#"{"type":"integer","format":"int128","minimum":0}"#); assert_schema!(f32 => r#"{"type":"number","format":"float"}"#); assert_schema!(f64 => r#"{"type":"number","format":"double"}"#); - + assert_schema!(String => r#"{"type":"string"}"#); #[cfg(feature = "chrono")] assert_schema!(Date => r#"{"type":"string","format":"date"}"#); @@ -406,7 +394,7 @@ mod test assert_schema!(NaiveDateTime => r#"{"type":"string","format":"date-time"}"#); #[cfg(feature = "uuid")] assert_schema!(Uuid => r#"{"type":"string","format":"uuid"}"#); - + assert_schema!(Option => r#"{"nullable":true,"type":"string"}"#); assert_schema!(Vec => r#"{"type":"array","items":{"type":"string"}}"#); assert_schema!(BTreeSet => r#"{"type":"array","items":{"type":"string"}}"#); diff --git a/src/resource.rs b/src/resource.rs index daedd3d..7784992 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -1,22 +1,14 @@ use crate::{DrawResourceRoutes, RequestBody, ResourceID, ResourceResult, ResourceType}; -use gotham::{ - extractor::QueryStringExtractor, - hyper::Body, - state::State -}; -use std::{ - future::Future, - pin::Pin -}; +use gotham::{extractor::QueryStringExtractor, hyper::Body, state::State}; +use std::{future::Future, pin::Pin}; /// This trait must be implemented for every resource. It allows you to register the different /// methods that can be handled by this resource to be registered with the underlying router. /// /// It is not recommended to implement this yourself, rather just use `#[derive(Resource)]`. -pub trait Resource -{ +pub trait Resource { /// Register all methods handled by this resource with the underlying router. - fn setup(route : D); + fn setup(route: D); } /// A common trait for every resource method. It defines the return type as well as some general @@ -25,94 +17,83 @@ pub trait Resource /// It is not recommended to implement this yourself. Rather, just write your handler method and /// annotate it with `#[(YourResource)]`, where `` is one of the supported /// resource methods. -pub trait ResourceMethod -{ - type Res : ResourceResult + Send + 'static; - +pub trait ResourceMethod { + type Res: ResourceResult + Send + 'static; + #[cfg(feature = "openapi")] - fn operation_id() -> Option - { + fn operation_id() -> Option { None } - - fn wants_auth() -> bool - { + + fn wants_auth() -> bool { false } } /// The read_all [`ResourceMethod`](trait.ResourceMethod.html). -pub trait ResourceReadAll : ResourceMethod -{ +pub trait ResourceReadAll: ResourceMethod { /// Handle a GET request on the Resource root. - fn read_all(state : State) -> Pin + Send>>; + fn read_all(state: State) -> Pin + Send>>; } /// The read [`ResourceMethod`](trait.ResourceMethod.html). -pub trait ResourceRead : ResourceMethod -{ +pub trait ResourceRead: ResourceMethod { /// The ID type to be parsed from the request path. - type ID : ResourceID + 'static; - + type ID: ResourceID + 'static; + /// Handle a GET request on the Resource with an id. - fn read(state : State, id : Self::ID) -> Pin + Send>>; + fn read(state: State, id: Self::ID) -> Pin + Send>>; } /// The search [`ResourceMethod`](trait.ResourceMethod.html). -pub trait ResourceSearch : ResourceMethod -{ +pub trait ResourceSearch: ResourceMethod { /// The Query type to be parsed from the request parameters. - type Query : ResourceType + QueryStringExtractor + Sync; - + type Query: ResourceType + QueryStringExtractor + Sync; + /// Handle a GET request on the Resource with additional search parameters. - fn search(state : State, query : Self::Query) -> Pin + Send>>; + fn search(state: State, query: Self::Query) -> Pin + Send>>; } /// The create [`ResourceMethod`](trait.ResourceMethod.html). -pub trait ResourceCreate : ResourceMethod -{ +pub trait ResourceCreate: ResourceMethod { /// The Body type to be parsed from the request body. - type Body : RequestBody; - + type Body: RequestBody; + /// Handle a POST request on the Resource root. - fn create(state : State, body : Self::Body) -> Pin + Send>>; + fn create(state: State, body: Self::Body) -> Pin + Send>>; } /// The change_all [`ResourceMethod`](trait.ResourceMethod.html). -pub trait ResourceChangeAll : ResourceMethod -{ +pub trait ResourceChangeAll: ResourceMethod { /// The Body type to be parsed from the request body. - type Body : RequestBody; - + type Body: RequestBody; + /// Handle a PUT request on the Resource root. - fn change_all(state : State, body : Self::Body) -> Pin + Send>>; + fn change_all(state: State, body: Self::Body) -> Pin + Send>>; } /// The change [`ResourceMethod`](trait.ResourceMethod.html). -pub trait ResourceChange : ResourceMethod -{ +pub trait ResourceChange: ResourceMethod { /// The Body type to be parsed from the request body. - type Body : RequestBody; + type Body: RequestBody; /// The ID type to be parsed from the request path. - type ID : ResourceID + 'static; - + type ID: ResourceID + 'static; + /// Handle a PUT request on the Resource with an id. - fn change(state : State, id : Self::ID, body : Self::Body) -> Pin + Send>>; + fn change(state: State, id: Self::ID, body: Self::Body) -> Pin + Send>>; } /// The remove_all [`ResourceMethod`](trait.ResourceMethod.html). -pub trait ResourceRemoveAll : ResourceMethod -{ +pub trait ResourceRemoveAll: ResourceMethod { /// Handle a DELETE request on the Resource root. - fn remove_all(state : State) -> Pin + Send>>; + fn remove_all(state: State) -> Pin + Send>>; } /// The remove [`ResourceMethod`](trait.ResourceMethod.html). -pub trait ResourceRemove : ResourceMethod -{ +pub trait ResourceRemove: ResourceMethod { /// The ID type to be parsed from the request path. - type ID : ResourceID + 'static; - + type ID: ResourceID + 'static; + /// Handle a DELETE request on the Resource with an id. - fn remove(state : State, id : Self::ID) -> Pin + Send>>; + fn remove(state: State, id: Self::ID) -> Pin + Send>>; } diff --git a/src/response.rs b/src/response.rs index dbbf8c7..d542a1a 100644 --- a/src/response.rs +++ b/src/response.rs @@ -3,62 +3,55 @@ use mime::{Mime, APPLICATION_JSON}; /// A response, used to create the final gotham response from. #[derive(Debug)] -pub struct Response -{ - pub status : StatusCode, - pub body : Body, - pub mime : Option +pub struct Response { + pub status: StatusCode, + pub body: Body, + pub mime: Option } -impl Response -{ +impl Response { /// Create a new `Response` from raw data. - pub fn new>(status : StatusCode, body : B, mime : Option) -> Self - { + pub fn new>(status: StatusCode, body: B, mime: Option) -> Self { Self { status, body: body.into(), mime } } - + /// Create a `Response` with mime type json from already serialized data. - pub fn json>(status : StatusCode, body : B) -> Self - { + pub fn json>(status: StatusCode, body: B) -> Self { Self { status, body: body.into(), mime: Some(APPLICATION_JSON) } } - + /// Create a _204 No Content_ `Response`. - pub fn no_content() -> Self - { + pub fn no_content() -> Self { Self { status: StatusCode::NO_CONTENT, body: Body::empty(), mime: None } } - + /// Create an empty _403 Forbidden_ `Response`. - pub fn forbidden() -> Self - { + pub fn forbidden() -> Self { Self { status: StatusCode::FORBIDDEN, body: Body::empty(), mime: None } } - + #[cfg(test)] - pub(crate) fn full_body(mut self) -> Result, ::Error> - { + pub(crate) fn full_body(mut self) -> Result, ::Error> { use futures_executor::block_on; use gotham::hyper::body::to_bytes; - - let bytes : &[u8] = &block_on(to_bytes(&mut self.body))?; + + let bytes: &[u8] = &block_on(to_bytes(&mut self.body))?; Ok(bytes.to_vec()) } -} \ No newline at end of file +} diff --git a/src/result/auth_result.rs b/src/result/auth_result.rs index 10f0183..6aab5a8 100644 --- a/src/result/auth_result.rs +++ b/src/result/auth_result.rs @@ -1,6 +1,5 @@ use gotham_restful_derive::ResourceError; - /** This is an error type that always yields a _403 Forbidden_ response. This type is best used in combination with [`AuthSuccess`] or [`AuthResult`]. @@ -9,8 +8,7 @@ combination with [`AuthSuccess`] or [`AuthResult`]. [`AuthResult`]: type.AuthResult.html */ #[derive(Debug, Clone, Copy, ResourceError)] -pub enum AuthError -{ +pub enum AuthError { #[status(FORBIDDEN)] #[display("Forbidden")] Forbidden @@ -57,8 +55,7 @@ error, or delegates to another error type. This type is best used with [`AuthRes [`AuthResult`]: type.AuthResult.html */ #[derive(Debug, ResourceError)] -pub enum AuthErrorOrOther -{ +pub enum AuthErrorOrOther { #[status(FORBIDDEN)] #[display("Forbidden")] Forbidden, @@ -67,10 +64,8 @@ pub enum AuthErrorOrOther Other(E) } -impl From for AuthErrorOrOther -{ - fn from(err : AuthError) -> Self - { +impl From for AuthErrorOrOther { + fn from(err: AuthError) -> Self { match err { AuthError::Forbidden => Self::Forbidden } @@ -80,10 +75,9 @@ impl From for AuthErrorOrOther impl From for AuthErrorOrOther where // TODO https://gitlab.com/msrd0/gotham-restful/-/issues/20 - F : std::error::Error + Into + F: std::error::Error + Into { - fn from(err : F) -> Self - { + fn from(err: F) -> Self { Self::Other(err.into()) } } diff --git a/src/result/mod.rs b/src/result/mod.rs index 314b9ac..100ba55 100644 --- a/src/result/mod.rs +++ b/src/result/mod.rs @@ -1,13 +1,13 @@ -use crate::Response; #[cfg(feature = "openapi")] use crate::OpenapiSchema; +use crate::Response; use futures_util::future::FutureExt; use mime::{Mime, STAR_STAR}; use serde::Serialize; use std::{ error::Error, - future::Future, fmt::{Debug, Display}, + future::Future, pin::Pin }; @@ -27,67 +27,54 @@ pub use result::IntoResponseError; mod success; pub use success::Success; - -pub(crate) trait OrAllTypes -{ +pub(crate) trait OrAllTypes { fn or_all_types(self) -> Vec; } -impl OrAllTypes for Option> -{ - fn or_all_types(self) -> Vec - { +impl OrAllTypes for Option> { + fn or_all_types(self) -> Vec { self.unwrap_or_else(|| vec![STAR_STAR]) } } - /// A trait provided to convert a resource's result to json. -pub trait ResourceResult -{ - type Err : Error + Send + 'static; - +pub trait ResourceResult { + type Err: Error + Send + Sync + 'static; + /// Turn this into a response that can be returned to the browser. This api will likely /// change in the future. fn into_response(self) -> Pin> + Send>>; - + /// Return a list of supported mime types. - fn accepted_types() -> Option> - { + fn accepted_types() -> Option> { None } - + #[cfg(feature = "openapi")] fn schema() -> OpenapiSchema; - + #[cfg(feature = "openapi")] - fn default_status() -> crate::StatusCode - { + fn default_status() -> crate::StatusCode { crate::StatusCode::OK } } #[cfg(feature = "openapi")] -impl crate::OpenapiType for Res -{ - fn schema() -> OpenapiSchema - { +impl crate::OpenapiType for Res { + fn schema() -> OpenapiSchema { Self::schema() } } /// The default json returned on an 500 Internal Server Error. #[derive(Debug, Serialize)] -pub(crate) struct ResourceError -{ - error : bool, - message : String +pub(crate) struct ResourceError { + error: bool, + message: String } -impl From for ResourceError -{ - fn from(message : T) -> Self - { +impl From for ResourceError { + fn from(message: T) -> Self { Self { error: true, message: message.to_string() @@ -95,27 +82,26 @@ impl From for ResourceError } } -fn into_response_helper(create_response : F) -> Pin> + Send>> +fn into_response_helper(create_response: F) -> Pin> + Send>> where - Err : Send + 'static, - F : FnOnce() -> Result + Err: Send + 'static, + F: FnOnce() -> Result { let res = create_response(); async move { res }.boxed() } #[cfg(feature = "errorlog")] -fn errorlog(e : E) -{ +fn errorlog(e: E) { error!("The handler encountered an error: {}", e); } #[cfg(not(feature = "errorlog"))] -fn errorlog(_e : E) {} +fn errorlog(_e: E) {} -fn handle_error(e : E) -> Pin> + Send>> +fn handle_error(e: E) -> Pin> + Send>> where - E : Display + IntoResponseError + E: Display + IntoResponseError { into_response_helper(|| { errorlog(&e); @@ -123,67 +109,55 @@ where }) } - impl ResourceResult for Pin + Send>> where - Res : ResourceResult + 'static + Res: ResourceResult + 'static { type Err = Res::Err; - - fn into_response(self) -> Pin> + Send>> - { - self.then(|result| { - result.into_response() - }).boxed() + + fn into_response(self) -> Pin> + Send>> { + self.then(|result| result.into_response()).boxed() } - - fn accepted_types() -> Option> - { + + fn accepted_types() -> Option> { Res::accepted_types() } - + #[cfg(feature = "openapi")] - fn schema() -> OpenapiSchema - { + fn schema() -> OpenapiSchema { Res::schema() } - + #[cfg(feature = "openapi")] - fn default_status() -> crate::StatusCode - { + fn default_status() -> crate::StatusCode { Res::default_status() } } - - #[cfg(test)] -mod test -{ +mod test { use super::*; use futures_executor::block_on; use thiserror::Error; - + #[derive(Debug, Default, Deserialize, Serialize)] #[cfg_attr(feature = "openapi", derive(crate::OpenapiType))] - struct Msg - { - msg : String + struct Msg { + msg: String } - + #[derive(Debug, Default, Error)] #[error("An Error")] struct MsgError; - + #[test] - fn result_from_future() - { + fn result_from_future() { let nc = NoContent::default(); let res = block_on(nc.into_response()).unwrap(); - + let fut_nc = async move { NoContent::default() }.boxed(); let fut_res = block_on(fut_nc.into_response()).unwrap(); - + assert_eq!(res.status, fut_res.status); assert_eq!(res.mime, fut_res.mime); assert_eq!(res.full_body().unwrap(), fut_res.full_body().unwrap()); diff --git a/src/result/no_content.rs b/src/result/no_content.rs index 3377b66..0c4fe05 100644 --- a/src/result/no_content.rs +++ b/src/result/no_content.rs @@ -1,14 +1,10 @@ -use super::{ResourceResult, handle_error}; +use super::{handle_error, ResourceResult}; use crate::{IntoResponseError, Response}; #[cfg(feature = "openapi")] use crate::{OpenapiSchema, OpenapiType}; use futures_util::{future, future::FutureExt}; use mime::Mime; -use std::{ - fmt::Display, - future::Future, - pin::Pin -}; +use std::{fmt::Display, future::Future, pin::Pin}; /** This is the return type of a resource that doesn't actually return something. It will result @@ -35,104 +31,89 @@ fn read_all(_state: &mut State) { #[derive(Clone, Copy, Debug, Default)] pub struct NoContent; -impl From<()> for NoContent -{ - fn from(_ : ()) -> Self - { +impl From<()> for NoContent { + fn from(_: ()) -> Self { Self {} } } -impl ResourceResult for NoContent -{ +impl ResourceResult for NoContent { // TODO this shouldn't be a serde_json::Error type Err = serde_json::Error; // just for easier handling of `Result` - + /// This will always be a _204 No Content_ together with an empty string. - fn into_response(self) -> Pin> + Send>> - { + fn into_response(self) -> Pin> + Send>> { future::ok(Response::no_content()).boxed() } - - fn accepted_types() -> Option> - { + + fn accepted_types() -> Option> { Some(Vec::new()) } - + /// Returns the schema of the `()` type. #[cfg(feature = "openapi")] - fn schema() -> OpenapiSchema - { + fn schema() -> OpenapiSchema { <()>::schema() } - + /// This will always be a _204 No Content_ #[cfg(feature = "openapi")] - fn default_status() -> crate::StatusCode - { + fn default_status() -> crate::StatusCode { crate::StatusCode::NO_CONTENT } } impl ResourceResult for Result where - E : Display + IntoResponseError + E: Display + IntoResponseError { type Err = serde_json::Error; - - fn into_response(self) -> Pin> + Send>> - { + + fn into_response(self) -> Pin> + Send>> { match self { Ok(nc) => nc.into_response(), Err(e) => handle_error(e) } } - - fn accepted_types() -> Option> - { + + fn accepted_types() -> Option> { NoContent::accepted_types() } - + #[cfg(feature = "openapi")] - fn schema() -> OpenapiSchema - { + fn schema() -> OpenapiSchema { ::schema() } - + #[cfg(feature = "openapi")] - fn default_status() -> crate::StatusCode - { + fn default_status() -> crate::StatusCode { NoContent::default_status() } } - #[cfg(test)] -mod test -{ +mod test { use super::*; use futures_executor::block_on; use gotham::hyper::StatusCode; use thiserror::Error; - + #[derive(Debug, Default, Error)] #[error("An Error")] struct MsgError; - + #[test] - fn no_content_has_empty_response() - { + fn no_content_has_empty_response() { let no_content = NoContent::default(); let res = block_on(no_content.into_response()).expect("didn't expect error response"); assert_eq!(res.status, StatusCode::NO_CONTENT); assert_eq!(res.mime, None); assert_eq!(res.full_body().unwrap(), &[] as &[u8]); } - + #[test] - fn no_content_result() - { - let no_content : Result = Ok(NoContent::default()); + fn no_content_result() { + let no_content: Result = Ok(NoContent::default()); let res = block_on(no_content.into_response()).expect("didn't expect error response"); assert_eq!(res.status, StatusCode::NO_CONTENT); assert_eq!(res.mime, None); diff --git a/src/result/raw.rs b/src/result/raw.rs index a44e15a..bf1997f 100644 --- a/src/result/raw.rs +++ b/src/result/raw.rs @@ -1,7 +1,7 @@ -use super::{IntoResponseError, ResourceResult, handle_error}; -use crate::{FromBody, RequestBody, ResourceType, Response, StatusCode}; +use super::{handle_error, IntoResponseError, ResourceResult}; #[cfg(feature = "openapi")] use crate::OpenapiSchema; +use crate::{FromBody, RequestBody, ResourceType, Response, StatusCode}; use futures_core::future::Future; use futures_util::{future, future::FutureExt}; use gotham::hyper::body::{Body, Bytes}; @@ -9,11 +9,7 @@ use mime::Mime; #[cfg(feature = "openapi")] use openapiv3::{SchemaKind, StringFormat, StringType, Type, VariantOrUnknownOrEmpty}; use serde_json::error::Error as SerdeJsonError; -use std::{ - convert::Infallible, - fmt::Display, - pin::Pin -}; +use std::{convert::Infallible, fmt::Display, pin::Pin}; /** This type can be used both as a raw request body, as well as as a raw response. However, all types @@ -43,44 +39,37 @@ fn create(body : Raw>) -> Raw> { [`OpenapiType`]: trait.OpenapiType.html */ #[derive(Debug)] -pub struct Raw -{ - pub raw : T, - pub mime : Mime +pub struct Raw { + pub raw: T, + pub mime: Mime } -impl Raw -{ - pub fn new(raw : T, mime : Mime) -> Self - { +impl Raw { + pub fn new(raw: T, mime: Mime) -> Self { Self { raw, mime } } } impl AsMut for Raw where - T : AsMut + T: AsMut { - fn as_mut(&mut self) -> &mut U - { + fn as_mut(&mut self) -> &mut U { self.raw.as_mut() } } impl AsRef for Raw where - T : AsRef + T: AsRef { - fn as_ref(&self) -> &U - { + fn as_ref(&self) -> &U { self.raw.as_ref() } } -impl Clone for Raw -{ - fn clone(&self) -> Self - { +impl Clone for Raw { + fn clone(&self) -> Self { Self { raw: self.raw.clone(), mime: self.mime.clone() @@ -88,36 +77,28 @@ impl Clone for Raw } } -impl From<&'a [u8]>> FromBody for Raw -{ +impl From<&'a [u8]>> FromBody for Raw { type Err = Infallible; - - fn from_body(body : Bytes, mime : Mime) -> Result - { + + fn from_body(body: Bytes, mime: Mime) -> Result { Ok(Self::new(body.as_ref().into(), mime)) } } -impl RequestBody for Raw -where - Raw : FromBody + ResourceType -{ -} +impl RequestBody for Raw where Raw: FromBody + ResourceType {} -impl> ResourceResult for Raw +impl> ResourceResult for Raw where - Self : Send + Self: Send { type Err = SerdeJsonError; // just for easier handling of `Result, E>` - - fn into_response(self) -> Pin> + Send>> - { + + fn into_response(self) -> Pin> + Send>> { future::ok(Response::new(StatusCode::OK, self.raw, Some(self.mime.clone()))).boxed() } - + #[cfg(feature = "openapi")] - fn schema() -> OpenapiSchema - { + fn schema() -> OpenapiSchema { OpenapiSchema::new(SchemaKind::Type(Type::String(StringType { format: VariantOrUnknownOrEmpty::Item(StringFormat::Binary), ..Default::default() @@ -127,37 +108,32 @@ where impl ResourceResult for Result, E> where - Raw : ResourceResult, - E : Display + IntoResponseError as ResourceResult>::Err> + Raw: ResourceResult, + E: Display + IntoResponseError as ResourceResult>::Err> { type Err = E::Err; - - fn into_response(self) -> Pin> + Send>> - { + + fn into_response(self) -> Pin> + Send>> { match self { Ok(raw) => raw.into_response(), Err(e) => handle_error(e) } } - + #[cfg(feature = "openapi")] - fn schema() -> OpenapiSchema - { + fn schema() -> OpenapiSchema { as ResourceResult>::schema() } } - #[cfg(test)] -mod test -{ +mod test { use super::*; use futures_executor::block_on; use mime::TEXT_PLAIN; - + #[test] - fn raw_response() - { + fn raw_response() { let msg = "Test"; let raw = Raw::new(msg, TEXT_PLAIN); let res = block_on(raw.into_response()).expect("didn't expect error response"); diff --git a/src/result/result.rs b/src/result/result.rs index 5de2e44..71c969a 100644 --- a/src/result/result.rs +++ b/src/result/result.rs @@ -1,106 +1,95 @@ -use super::{ResourceResult, handle_error, into_response_helper}; -use crate::{ - result::ResourceError, - Response, ResponseBody, StatusCode -}; +use super::{handle_error, into_response_helper, ResourceResult}; #[cfg(feature = "openapi")] use crate::OpenapiSchema; +use crate::{result::ResourceError, Response, ResponseBody, StatusCode}; use futures_core::future::Future; use mime::{Mime, APPLICATION_JSON}; -use std::{ - error::Error, - fmt::Display, - pin::Pin -}; +use std::{error::Error, fmt::Display, pin::Pin}; + +pub trait IntoResponseError { + type Err: Error + Send + 'static; -pub trait IntoResponseError -{ - type Err : Error + Send + 'static; - fn into_response_error(self) -> Result; } -impl IntoResponseError for E -{ +impl IntoResponseError for E { type Err = serde_json::Error; - - fn into_response_error(self) -> Result - { - let err : ResourceError = self.into(); - Ok(Response::json(StatusCode::INTERNAL_SERVER_ERROR, serde_json::to_string(&err)?)) + + fn into_response_error(self) -> Result { + let err: ResourceError = self.into(); + Ok(Response::json( + StatusCode::INTERNAL_SERVER_ERROR, + serde_json::to_string(&err)? + )) } } impl ResourceResult for Result where - R : ResponseBody, - E : Display + IntoResponseError + R: ResponseBody, + E: Display + IntoResponseError { type Err = E::Err; - - fn into_response(self) -> Pin> + Send>> - { + + fn into_response(self) -> Pin> + Send>> { match self { Ok(r) => into_response_helper(|| Ok(Response::json(StatusCode::OK, serde_json::to_string(&r)?))), Err(e) => handle_error(e) } } - - fn accepted_types() -> Option> - { + + fn accepted_types() -> Option> { Some(vec![APPLICATION_JSON]) } - + #[cfg(feature = "openapi")] - fn schema() -> OpenapiSchema - { + fn schema() -> OpenapiSchema { R::schema() } } - #[cfg(test)] -mod test -{ +mod test { use super::*; use crate::result::OrAllTypes; use futures_executor::block_on; use thiserror::Error; - + #[derive(Debug, Default, Deserialize, Serialize)] #[cfg_attr(feature = "openapi", derive(crate::OpenapiType))] - struct Msg - { - msg : String + struct Msg { + msg: String } - + #[derive(Debug, Default, Error)] #[error("An Error")] struct MsgError; - + #[test] - fn result_ok() - { - let ok : Result = Ok(Msg::default()); + fn result_ok() { + let ok: Result = Ok(Msg::default()); let res = block_on(ok.into_response()).expect("didn't expect error response"); assert_eq!(res.status, StatusCode::OK); assert_eq!(res.mime, Some(APPLICATION_JSON)); assert_eq!(res.full_body().unwrap(), r#"{"msg":""}"#.as_bytes()); } - + #[test] - fn result_err() - { - let err : Result = Err(MsgError::default()); + fn result_err() { + let err: Result = Err(MsgError::default()); let res = block_on(err.into_response()).expect("didn't expect error response"); assert_eq!(res.status, StatusCode::INTERNAL_SERVER_ERROR); assert_eq!(res.mime, Some(APPLICATION_JSON)); - assert_eq!(res.full_body().unwrap(), format!(r#"{{"error":true,"message":"{}"}}"#, MsgError::default()).as_bytes()); + assert_eq!( + res.full_body().unwrap(), + format!(r#"{{"error":true,"message":"{}"}}"#, MsgError::default()).as_bytes() + ); } - + #[test] - fn success_accepts_json() - { - assert!(>::accepted_types().or_all_types().contains(&APPLICATION_JSON)) + fn success_accepts_json() { + assert!(>::accepted_types() + .or_all_types() + .contains(&APPLICATION_JSON)) } } diff --git a/src/result/success.rs b/src/result/success.rs index dffd740..43cdd6f 100644 --- a/src/result/success.rs +++ b/src/result/success.rs @@ -1,14 +1,14 @@ -use super::{ResourceResult, into_response_helper}; -use crate::{Response, ResponseBody}; +use super::{into_response_helper, ResourceResult}; #[cfg(feature = "openapi")] use crate::OpenapiSchema; +use crate::{Response, ResponseBody}; use gotham::hyper::StatusCode; use mime::{Mime, APPLICATION_JSON}; use std::{ fmt::Debug, future::Future, - pin::Pin, - ops::{Deref, DerefMut} + ops::{Deref, DerefMut}, + pin::Pin }; /** @@ -45,119 +45,95 @@ fn read_all(_state: &mut State) -> Success { #[derive(Debug)] pub struct Success(T); -impl AsMut for Success -{ - fn as_mut(&mut self) -> &mut T - { +impl AsMut for Success { + fn as_mut(&mut self) -> &mut T { &mut self.0 } } -impl AsRef for Success -{ - fn as_ref(&self) -> &T - { +impl AsRef for Success { + fn as_ref(&self) -> &T { &self.0 } } -impl Deref for Success -{ +impl Deref for Success { type Target = T; - - fn deref(&self) -> &T - { + + fn deref(&self) -> &T { &self.0 } } -impl DerefMut for Success -{ - fn deref_mut(&mut self) -> &mut T - { +impl DerefMut for Success { + fn deref_mut(&mut self) -> &mut T { &mut self.0 } } -impl From for Success -{ - fn from(t : T) -> Self - { +impl From for Success { + fn from(t: T) -> Self { Self(t) } } -impl Clone for Success -{ - fn clone(&self) -> Self - { +impl Clone for Success { + fn clone(&self) -> Self { Self(self.0.clone()) } } -impl Copy for Success -{ -} +impl Copy for Success {} -impl Default for Success -{ - fn default() -> Self - { +impl Default for Success { + fn default() -> Self { Self(T::default()) } } -impl ResourceResult for Success +impl ResourceResult for Success where - Self : Send + Self: Send { type Err = serde_json::Error; - - fn into_response(self) -> Pin> + Send>> - { + + fn into_response(self) -> Pin> + Send>> { into_response_helper(|| Ok(Response::json(StatusCode::OK, serde_json::to_string(self.as_ref())?))) } - - fn accepted_types() -> Option> - { + + fn accepted_types() -> Option> { Some(vec![APPLICATION_JSON]) } - + #[cfg(feature = "openapi")] - fn schema() -> OpenapiSchema - { + fn schema() -> OpenapiSchema { T::schema() } } - #[cfg(test)] -mod test -{ +mod test { use super::*; use crate::result::OrAllTypes; use futures_executor::block_on; - + #[derive(Debug, Default, Serialize)] #[cfg_attr(feature = "openapi", derive(crate::OpenapiType))] - struct Msg - { - msg : String + struct Msg { + msg: String } - + #[test] - fn success_always_successfull() - { - let success : Success = Msg::default().into(); + fn success_always_successfull() { + let success: Success = Msg::default().into(); let res = block_on(success.into_response()).expect("didn't expect error response"); assert_eq!(res.status, StatusCode::OK); assert_eq!(res.mime, Some(APPLICATION_JSON)); assert_eq!(res.full_body().unwrap(), r#"{"msg":""}"#.as_bytes()); } - + #[test] - fn success_accepts_json() - { + fn success_accepts_json() { assert!(>::accepted_types().or_all_types().contains(&APPLICATION_JSON)) } } diff --git a/src/routing.rs b/src/routing.rs index 5610379..5e952f6 100644 --- a/src/routing.rs +++ b/src/routing.rs @@ -1,22 +1,21 @@ -use crate::{ - resource::*, - result::{ResourceError, ResourceResult}, - RequestBody, - Response, - StatusCode -}; -#[cfg(feature = "cors")] -use crate::CorsRoute; #[cfg(feature = "openapi")] use crate::openapi::{ builder::{OpenapiBuilder, OpenapiInfo}, router::OpenapiRouter }; +#[cfg(feature = "cors")] +use crate::CorsRoute; +use crate::{ + resource::*, + result::{ResourceError, ResourceResult}, + RequestBody, Response, StatusCode +}; use futures_util::{future, future::FutureExt}; use gotham::{ - handler::{HandlerError, HandlerFuture, IntoHandlerError}, + handler::{HandlerError, HandlerFuture}, helpers::http::response::{create_empty_response, create_response}, + hyper::{body::to_bytes, header::CONTENT_TYPE, Body, HeaderMap, Method}, pipeline::chain::PipelineHandleChain, router::{ builder::*, @@ -25,99 +24,84 @@ use gotham::{ }, state::{FromState, State} }; -use gotham::hyper::{ - body::to_bytes, - header::CONTENT_TYPE, - Body, - HeaderMap, - Method -}; use mime::{Mime, APPLICATION_JSON}; -use std::{ - future::Future, - panic::RefUnwindSafe, - pin::Pin -}; +use std::{future::Future, panic::RefUnwindSafe, pin::Pin}; /// Allow us to extract an id from a path. #[derive(Deserialize, StateData, StaticResponseExtender)] -struct PathExtractor -{ - id : ID +struct PathExtractor { + id: ID } /// This trait adds the `with_openapi` method to gotham's routing. It turns the default /// router into one that will only allow RESTful resources, but record them and generate /// an OpenAPI specification on request. #[cfg(feature = "openapi")] -pub trait WithOpenapi -{ - fn with_openapi(&mut self, info : OpenapiInfo, block : F) +pub trait WithOpenapi { + fn with_openapi(&mut self, info: OpenapiInfo, block: F) where - F : FnOnce(OpenapiRouter<'_, D>); + F: FnOnce(OpenapiRouter<'_, D>); } /// This trait adds the `resource` method to gotham's routing. It allows you to register /// any RESTful `Resource` with a path. -pub trait DrawResources -{ - fn resource(&mut self, path : &str); +pub trait DrawResources { + fn resource(&mut self, path: &str); } /// This trait allows to draw routes within an resource. Use this only inside the /// `Resource::setup` method. -pub trait DrawResourceRoutes -{ - fn read_all(&mut self); - - fn read(&mut self); - - fn search(&mut self); - - fn create(&mut self) +pub trait DrawResourceRoutes { + fn read_all(&mut self); + + fn read(&mut self); + + fn search(&mut self); + + fn create(&mut self) where - Handler::Res : 'static, - Handler::Body : 'static; - - fn change_all(&mut self) + Handler::Res: 'static, + Handler::Body: 'static; + + fn change_all(&mut self) where - Handler::Res : 'static, - Handler::Body : 'static; - - fn change(&mut self) + Handler::Res: 'static, + Handler::Body: 'static; + + fn change(&mut self) where - Handler::Res : 'static, - Handler::Body : 'static; - - fn remove_all(&mut self); - - fn remove(&mut self); + Handler::Res: 'static, + Handler::Body: 'static; + + fn remove_all(&mut self); + + fn remove(&mut self); } -fn response_from(res : Response, state : &State) -> gotham::hyper::Response -{ +fn response_from(res: Response, state: &State) -> gotham::hyper::Response { let mut r = create_empty_response(state, res.status); - if let Some(mime) = res.mime - { + if let Some(mime) = res.mime { r.headers_mut().insert(CONTENT_TYPE, mime.as_ref().parse().unwrap()); } - + let method = Method::borrow_from(state); - if method != Method::HEAD - { + if method != Method::HEAD { *r.body_mut() = res.body; } - + #[cfg(feature = "cors")] crate::cors::handle_cors(state, &mut r); - + r } -async fn to_handler_future(state : State, get_result : F) -> Result<(State, gotham::hyper::Response), (State, HandlerError)> +async fn to_handler_future( + state: State, + get_result: F +) -> Result<(State, gotham::hyper::Response), (State, HandlerError)> where - F : FnOnce(State) -> Pin + Send>>, - R : ResourceResult + F: FnOnce(State) -> Pin + Send>>, + R: ResourceResult { let (state, res) = get_result(state).await; let res = res.into_response().await; @@ -126,67 +110,70 @@ where let r = response_from(res, &state); Ok((state, r)) }, - Err(e) => Err((state, e.into_handler_error())) + Err(e) => Err((state, e.into())) } } -async fn body_to_res(mut state : State, get_result : F) -> (State, Result, HandlerError>) +async fn body_to_res( + mut state: State, + get_result: F +) -> (State, Result, HandlerError>) where - B : RequestBody, - F : FnOnce(State, B) -> Pin + Send>>, - R : ResourceResult + B: RequestBody, + F: FnOnce(State, B) -> Pin + Send>>, + R: ResourceResult { let body = to_bytes(Body::take_from(&mut state)).await; - + let body = match body { Ok(body) => body, - Err(e) => return (state, Err(e.into_handler_error())) + Err(e) => return (state, Err(e.into())) }; - - let content_type : Mime = match HeaderMap::borrow_from(&state).get(CONTENT_TYPE) { + + let content_type: Mime = match HeaderMap::borrow_from(&state).get(CONTENT_TYPE) { Some(content_type) => content_type.to_str().unwrap().parse().unwrap(), None => { let res = create_empty_response(&state, StatusCode::UNSUPPORTED_MEDIA_TYPE); - return (state, Ok(res)) + return (state, Ok(res)); } }; - + let res = { let body = match B::from_body(body, content_type) { Ok(body) => body, Err(e) => { - let error : ResourceError = e.into(); + let error: ResourceError = e.into(); let res = match serde_json::to_string(&error) { Ok(json) => { let res = create_response(&state, StatusCode::BAD_REQUEST, APPLICATION_JSON, json); Ok(res) }, - Err(e) => Err(e.into_handler_error()) + Err(e) => Err(e.into()) }; - return (state, res) + return (state, res); } }; get_result(state, body) }; - + let (state, res) = res.await; let res = res.into_response().await; - + let res = match res { Ok(res) => { let r = response_from(res, &state); Ok(r) }, - Err(e) => Err(e.into_handler_error()) + Err(e) => Err(e.into()) }; (state, res) } -fn handle_with_body(state : State, get_result : F) -> Pin> +fn handle_with_body(state: State, get_result: F) -> Pin> where - B : RequestBody + 'static, - F : FnOnce(State, B) -> Pin + Send>> + Send + 'static, - R : ResourceResult + Send + 'static + B: RequestBody + 'static, + F: FnOnce(State, B) -> Pin + Send>> + Send + 'static, + R: ResourceResult + Send + 'static { body_to_res(state, get_result) .then(|(state, res)| match res { @@ -196,78 +183,70 @@ where .boxed() } -fn read_all_handler(state : State) -> Pin> -{ +fn read_all_handler(state: State) -> Pin> { to_handler_future(state, |state| Handler::read_all(state)).boxed() } -fn read_handler(state : State) -> Pin> -{ +fn read_handler(state: State) -> Pin> { let id = { - let path : &PathExtractor = PathExtractor::borrow_from(&state); + let path: &PathExtractor = PathExtractor::borrow_from(&state); path.id.clone() }; to_handler_future(state, |state| Handler::read(state, id)).boxed() } -fn search_handler(mut state : State) -> Pin> -{ +fn search_handler(mut state: State) -> Pin> { let query = Handler::Query::take_from(&mut state); to_handler_future(state, |state| Handler::search(state, query)).boxed() } -fn create_handler(state : State) -> Pin> +fn create_handler(state: State) -> Pin> where - Handler::Res : 'static, - Handler::Body : 'static + Handler::Res: 'static, + Handler::Body: 'static { handle_with_body::(state, |state, body| Handler::create(state, body)) } -fn change_all_handler(state : State) -> Pin> +fn change_all_handler(state: State) -> Pin> where - Handler::Res : 'static, - Handler::Body : 'static + Handler::Res: 'static, + Handler::Body: 'static { handle_with_body::(state, |state, body| Handler::change_all(state, body)) } -fn change_handler(state : State) -> Pin> +fn change_handler(state: State) -> Pin> where - Handler::Res : 'static, - Handler::Body : 'static + Handler::Res: 'static, + Handler::Body: 'static { let id = { - let path : &PathExtractor = PathExtractor::borrow_from(&state); + let path: &PathExtractor = PathExtractor::borrow_from(&state); path.id.clone() }; handle_with_body::(state, |state, body| Handler::change(state, id, body)) } -fn remove_all_handler(state : State) -> Pin> -{ +fn remove_all_handler(state: State) -> Pin> { to_handler_future(state, |state| Handler::remove_all(state)).boxed() } -fn remove_handler(state : State) -> Pin> -{ +fn remove_handler(state: State) -> Pin> { let id = { - let path : &PathExtractor = PathExtractor::borrow_from(&state); + let path: &PathExtractor = PathExtractor::borrow_from(&state); path.id.clone() }; to_handler_future(state, |state| Handler::remove(state, id)).boxed() } #[derive(Clone)] -struct MaybeMatchAcceptHeader -{ - matcher : Option +struct MaybeMatchAcceptHeader { + matcher: Option } -impl RouteMatcher for MaybeMatchAcceptHeader -{ - fn is_match(&self, state : &State) -> Result<(), RouteNonMatch> - { +impl RouteMatcher for MaybeMatchAcceptHeader { + fn is_match(&self, state: &State) -> Result<(), RouteNonMatch> { match &self.matcher { Some(matcher) => matcher.is_match(state), None => Ok(()) @@ -275,10 +254,8 @@ impl RouteMatcher for MaybeMatchAcceptHeader } } -impl From>> for MaybeMatchAcceptHeader -{ - fn from(types : Option>) -> Self - { +impl From>> for MaybeMatchAcceptHeader { + fn from(types: Option>) -> Self { let types = match types { Some(types) if types.is_empty() => None, types => types @@ -290,15 +267,12 @@ impl From>> for MaybeMatchAcceptHeader } #[derive(Clone)] -struct MaybeMatchContentTypeHeader -{ - matcher : Option +struct MaybeMatchContentTypeHeader { + matcher: Option } -impl RouteMatcher for MaybeMatchContentTypeHeader -{ - fn is_match(&self, state : &State) -> Result<(), RouteNonMatch> - { +impl RouteMatcher for MaybeMatchContentTypeHeader { + fn is_match(&self, state: &State) -> Result<(), RouteNonMatch> { match &self.matcher { Some(matcher) => matcher.is_match(state), None => Ok(()) @@ -306,10 +280,8 @@ impl RouteMatcher for MaybeMatchContentTypeHeader } } -impl From>> for MaybeMatchContentTypeHeader -{ - fn from(types : Option>) -> Self - { +impl From>> for MaybeMatchContentTypeHeader { + fn from(types: Option>) -> Self { Self { matcher: types.map(|types| ContentTypeHeaderRouteMatcher::new(types).allow_no_type()) } @@ -318,16 +290,15 @@ impl From>> for MaybeMatchContentTypeHeader macro_rules! implDrawResourceRoutes { ($implType:ident) => { - #[cfg(feature = "openapi")] impl<'a, C, P> WithOpenapi for $implType<'a, C, P> where - C : PipelineHandleChain

+ Copy + Send + Sync + 'static, - P : RefUnwindSafe + Send + Sync + 'static + C: PipelineHandleChain

+ Copy + Send + Sync + 'static, + P: RefUnwindSafe + Send + Sync + 'static { - fn with_openapi(&mut self, info : OpenapiInfo, block : F) + fn with_openapi(&mut self, info: OpenapiInfo, block: F) where - F : FnOnce(OpenapiRouter<'_, $implType<'a, C, P>>) + F: FnOnce(OpenapiRouter<'_, $implType<'a, C, P>>) { let router = OpenapiRouter { router: self, @@ -337,58 +308,58 @@ macro_rules! implDrawResourceRoutes { block(router); } } - + impl<'a, C, P> DrawResources for $implType<'a, C, P> where - C : PipelineHandleChain

+ Copy + Send + Sync + 'static, - P : RefUnwindSafe + Send + Sync + 'static + C: PipelineHandleChain

+ Copy + Send + Sync + 'static, + P: RefUnwindSafe + Send + Sync + 'static { - fn resource(&mut self, path : &str) - { + fn resource(&mut self, path: &str) { R::setup((self, path)); } } - + #[allow(clippy::redundant_closure)] // doesn't work because of type parameters impl<'a, C, P> DrawResourceRoutes for (&mut $implType<'a, C, P>, &str) where - C : PipelineHandleChain

+ Copy + Send + Sync + 'static, - P : RefUnwindSafe + Send + Sync + 'static + C: PipelineHandleChain

+ Copy + Send + Sync + 'static, + P: RefUnwindSafe + Send + Sync + 'static { - fn read_all(&mut self) - { - let matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into(); - self.0.get(&self.1) + fn read_all(&mut self) { + let matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into(); + self.0 + .get(&self.1) .extend_route_matcher(matcher) .to(|state| read_all_handler::(state)); } - fn read(&mut self) - { - let matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into(); - self.0.get(&format!("{}/:id", self.1)) + fn read(&mut self) { + let matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into(); + self.0 + .get(&format!("{}/:id", self.1)) .extend_route_matcher(matcher) .with_path_extractor::>() .to(|state| read_handler::(state)); } - - fn search(&mut self) - { - let matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into(); - self.0.get(&format!("{}/search", self.1)) + + fn search(&mut self) { + let matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into(); + self.0 + .get(&format!("{}/search", self.1)) .extend_route_matcher(matcher) .with_query_string_extractor::() .to(|state| search_handler::(state)); } - - fn create(&mut self) + + fn create(&mut self) where - Handler::Res : Send + 'static, - Handler::Body : 'static + Handler::Res: Send + 'static, + Handler::Body: 'static { - let accept_matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into(); - let content_matcher : MaybeMatchContentTypeHeader = Handler::Body::supported_types().into(); - self.0.post(&self.1) + let accept_matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into(); + let content_matcher: MaybeMatchContentTypeHeader = Handler::Body::supported_types().into(); + self.0 + .post(&self.1) .extend_route_matcher(accept_matcher) .extend_route_matcher(content_matcher) .to(|state| create_handler::(state)); @@ -396,14 +367,15 @@ macro_rules! implDrawResourceRoutes { self.0.cors(&self.1, Method::POST); } - fn change_all(&mut self) + fn change_all(&mut self) where - Handler::Res : Send + 'static, - Handler::Body : 'static + Handler::Res: Send + 'static, + Handler::Body: 'static { - let accept_matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into(); - let content_matcher : MaybeMatchContentTypeHeader = Handler::Body::supported_types().into(); - self.0.put(&self.1) + let accept_matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into(); + let content_matcher: MaybeMatchContentTypeHeader = Handler::Body::supported_types().into(); + self.0 + .put(&self.1) .extend_route_matcher(accept_matcher) .extend_route_matcher(content_matcher) .to(|state| change_all_handler::(state)); @@ -411,15 +383,16 @@ macro_rules! implDrawResourceRoutes { self.0.cors(&self.1, Method::PUT); } - fn change(&mut self) + fn change(&mut self) where - Handler::Res : Send + 'static, - Handler::Body : 'static + Handler::Res: Send + 'static, + Handler::Body: 'static { - let accept_matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into(); - let content_matcher : MaybeMatchContentTypeHeader = Handler::Body::supported_types().into(); + let accept_matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into(); + let content_matcher: MaybeMatchContentTypeHeader = Handler::Body::supported_types().into(); let path = format!("{}/:id", self.1); - self.0.put(&path) + self.0 + .put(&path) .extend_route_matcher(accept_matcher) .extend_route_matcher(content_matcher) .with_path_extractor::>() @@ -428,21 +401,21 @@ macro_rules! implDrawResourceRoutes { self.0.cors(&path, Method::PUT); } - fn remove_all(&mut self) - { - let matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into(); - self.0.delete(&self.1) + fn remove_all(&mut self) { + let matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into(); + self.0 + .delete(&self.1) .extend_route_matcher(matcher) .to(|state| remove_all_handler::(state)); #[cfg(feature = "cors")] self.0.cors(&self.1, Method::DELETE); } - fn remove(&mut self) - { - let matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into(); + fn remove(&mut self) { + let matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into(); let path = format!("{}/:id", self.1); - self.0.delete(&path) + self.0 + .delete(&path) .extend_route_matcher(matcher) .with_path_extractor::>() .to(|state| remove_handler::(state)); @@ -450,7 +423,7 @@ macro_rules! implDrawResourceRoutes { self.0.cors(&path, Method::POST); } } - } + }; } implDrawResourceRoutes!(RouterBuilder); diff --git a/src/types.rs b/src/types.rs index 576f7e9..030c02c 100644 --- a/src/types.rs +++ b/src/types.rs @@ -4,43 +4,26 @@ use crate::OpenapiType; use gotham::hyper::body::Bytes; use mime::{Mime, APPLICATION_JSON}; use serde::{de::DeserializeOwned, Serialize}; -use std::{ - error::Error, - panic::RefUnwindSafe -}; +use std::{error::Error, panic::RefUnwindSafe}; #[cfg(not(feature = "openapi"))] -pub trait ResourceType -{ -} +pub trait ResourceType {} #[cfg(not(feature = "openapi"))] -impl ResourceType for T -{ -} +impl ResourceType for T {} #[cfg(feature = "openapi")] -pub trait ResourceType : OpenapiType -{ -} +pub trait ResourceType: OpenapiType {} #[cfg(feature = "openapi")] -impl ResourceType for T -{ -} - +impl ResourceType for T {} /// A type that can be used inside a response body. Implemented for every type that is /// serializable with serde. If the `openapi` feature is used, it must also be of type /// `OpenapiType`. -pub trait ResponseBody : ResourceType + Serialize -{ -} - -impl ResponseBody for T -{ -} +pub trait ResponseBody: ResourceType + Serialize {} +impl ResponseBody for T {} /** This trait should be implemented for every type that can be built from an HTTP request body @@ -64,28 +47,24 @@ struct RawImage { [`Bytes`]: ../bytes/struct.Bytes.html [`Mime`]: ../mime/struct.Mime.html */ -pub trait FromBody : Sized -{ +pub trait FromBody: Sized { /// The error type returned by the conversion if it was unsuccessfull. When using the derive /// macro, there is no way to trigger an error, so `Infallible` is used here. However, this /// might change in the future. - type Err : Error; - + type Err: Error; + /// Perform the conversion. - fn from_body(body : Bytes, content_type : Mime) -> Result; + fn from_body(body: Bytes, content_type: Mime) -> Result; } -impl FromBody for T -{ +impl FromBody for T { type Err = serde_json::Error; - - fn from_body(body : Bytes, _content_type : Mime) -> Result - { + + fn from_body(body: Bytes, _content_type: Mime) -> Result { serde_json::from_slice(&body) } } - /** A type that can be used inside a request body. Implemented for every type that is deserializable with serde. If the `openapi` feature is used, it must also be of type [`OpenapiType`]. @@ -108,19 +87,15 @@ struct RawImage { [`FromBody`]: trait.FromBody.html [`OpenapiType`]: trait.OpenapiType.html */ -pub trait RequestBody : ResourceType + FromBody -{ +pub trait RequestBody: ResourceType + FromBody { /// Return all types that are supported as content types. Use `None` if all types are supported. - fn supported_types() -> Option> - { + fn supported_types() -> Option> { None } } -impl RequestBody for T -{ - fn supported_types() -> Option> - { +impl RequestBody for T { + fn supported_types() -> Option> { Some(vec![APPLICATION_JSON]) } } @@ -128,10 +103,6 @@ impl RequestBody for T /// A type than can be used as a parameter to a resource method. Implemented for every type /// that is deserialize and thread-safe. If the `openapi` feature is used, it must also be of /// type `OpenapiType`. -pub trait ResourceID : ResourceType + DeserializeOwned + Clone + RefUnwindSafe + Send + Sync -{ -} +pub trait ResourceID: ResourceType + DeserializeOwned + Clone + RefUnwindSafe + Send + Sync {} -impl ResourceID for T -{ -} +impl ResourceID for T {} diff --git a/tests/async_methods.rs b/tests/async_methods.rs index 42cbc25..9de8465 100644 --- a/tests/async_methods.rs +++ b/tests/async_methods.rs @@ -1,16 +1,15 @@ -#[macro_use] extern crate gotham_derive; +#[macro_use] +extern crate gotham_derive; -use gotham::{ - router::builder::*, - test::TestServer -}; +use gotham::{router::builder::*, test::TestServer}; use gotham_restful::*; use mime::{APPLICATION_JSON, TEXT_PLAIN}; use serde::Deserialize; -mod util { include!("util/mod.rs"); } -use util::{test_get_response, test_post_response, test_put_response, test_delete_response}; - +mod util { + include!("util/mod.rs"); +} +use util::{test_delete_response, test_get_response, test_post_response, test_put_response}; #[derive(Resource)] #[resource(read_all, read, search, create, change_all, change, remove_all, remove)] @@ -19,88 +18,96 @@ struct FooResource; #[derive(Deserialize)] #[cfg_attr(feature = "openapi", derive(OpenapiType))] #[allow(dead_code)] -struct FooBody -{ - data : String +struct FooBody { + data: String } #[derive(Deserialize, StateData, StaticResponseExtender)] #[cfg_attr(feature = "openapi", derive(OpenapiType))] #[allow(dead_code)] -struct FooSearch -{ - query : String +struct FooSearch { + query: String } -const READ_ALL_RESPONSE : &[u8] = b"1ARwwSPVyOKpJKrYwqGgECPVWDl1BqajAAj7g7WJ3e"; +const READ_ALL_RESPONSE: &[u8] = b"1ARwwSPVyOKpJKrYwqGgECPVWDl1BqajAAj7g7WJ3e"; #[read_all(FooResource)] -async fn read_all() -> Raw<&'static [u8]> -{ +async fn read_all() -> Raw<&'static [u8]> { Raw::new(READ_ALL_RESPONSE, TEXT_PLAIN) } -const READ_RESPONSE : &[u8] = b"FEReHoeBKU17X2bBpVAd1iUvktFL43CDu0cFYHdaP9"; +const READ_RESPONSE: &[u8] = b"FEReHoeBKU17X2bBpVAd1iUvktFL43CDu0cFYHdaP9"; #[read(FooResource)] -async fn read(_id : u64) -> Raw<&'static [u8]> -{ +async fn read(_id: u64) -> Raw<&'static [u8]> { Raw::new(READ_RESPONSE, TEXT_PLAIN) } -const SEARCH_RESPONSE : &[u8] = b"AWqcQUdBRHXKh3at4u79mdupOAfEbnTcx71ogCVF0E"; +const SEARCH_RESPONSE: &[u8] = b"AWqcQUdBRHXKh3at4u79mdupOAfEbnTcx71ogCVF0E"; #[search(FooResource)] -async fn search(_body : FooSearch) -> Raw<&'static [u8]> -{ +async fn search(_body: FooSearch) -> Raw<&'static [u8]> { Raw::new(SEARCH_RESPONSE, TEXT_PLAIN) } -const CREATE_RESPONSE : &[u8] = b"y6POY7wOMAB0jBRBw0FJT7DOpUNbhmT8KdpQPLkI83"; +const CREATE_RESPONSE: &[u8] = b"y6POY7wOMAB0jBRBw0FJT7DOpUNbhmT8KdpQPLkI83"; #[create(FooResource)] -async fn create(_body : FooBody) -> Raw<&'static [u8]> -{ +async fn create(_body: FooBody) -> Raw<&'static [u8]> { Raw::new(CREATE_RESPONSE, TEXT_PLAIN) } -const CHANGE_ALL_RESPONSE : &[u8] = b"QlbYg8gHE9OQvvk3yKjXJLTSXlIrg9mcqhfMXJmQkv"; +const CHANGE_ALL_RESPONSE: &[u8] = b"QlbYg8gHE9OQvvk3yKjXJLTSXlIrg9mcqhfMXJmQkv"; #[change_all(FooResource)] -async fn change_all(_body : FooBody) -> Raw<&'static [u8]> -{ +async fn change_all(_body: FooBody) -> Raw<&'static [u8]> { Raw::new(CHANGE_ALL_RESPONSE, TEXT_PLAIN) } -const CHANGE_RESPONSE : &[u8] = b"qGod55RUXkT1lgPO8h0uVM6l368O2S0GrwENZFFuRu"; +const CHANGE_RESPONSE: &[u8] = b"qGod55RUXkT1lgPO8h0uVM6l368O2S0GrwENZFFuRu"; #[change(FooResource)] -async fn change(_id : u64, _body : FooBody) -> Raw<&'static [u8]> -{ +async fn change(_id: u64, _body: FooBody) -> Raw<&'static [u8]> { Raw::new(CHANGE_RESPONSE, TEXT_PLAIN) } -const REMOVE_ALL_RESPONSE : &[u8] = b"Y36kZ749MRk2Nem4BedJABOZiZWPLOtiwLfJlGTwm5"; +const REMOVE_ALL_RESPONSE: &[u8] = b"Y36kZ749MRk2Nem4BedJABOZiZWPLOtiwLfJlGTwm5"; #[remove_all(FooResource)] -async fn remove_all() -> Raw<&'static [u8]> -{ +async fn remove_all() -> Raw<&'static [u8]> { Raw::new(REMOVE_ALL_RESPONSE, TEXT_PLAIN) } -const REMOVE_RESPONSE : &[u8] = b"CwRzBrKErsVZ1N7yeNfjZuUn1MacvgBqk4uPOFfDDq"; +const REMOVE_RESPONSE: &[u8] = b"CwRzBrKErsVZ1N7yeNfjZuUn1MacvgBqk4uPOFfDDq"; #[remove(FooResource)] -async fn remove(_id : u64) -> Raw<&'static [u8]> -{ +async fn remove(_id: u64) -> Raw<&'static [u8]> { Raw::new(REMOVE_RESPONSE, TEXT_PLAIN) } #[test] -fn async_methods() -{ +fn async_methods() { let server = TestServer::new(build_simple_router(|router| { router.resource::("foo"); - })).unwrap(); - + })) + .unwrap(); + test_get_response(&server, "http://localhost/foo", READ_ALL_RESPONSE); test_get_response(&server, "http://localhost/foo/1", READ_RESPONSE); test_get_response(&server, "http://localhost/foo/search?query=hello+world", SEARCH_RESPONSE); - test_post_response(&server, "http://localhost/foo", r#"{"data":"hello world"}"#, APPLICATION_JSON, CREATE_RESPONSE); - test_put_response(&server, "http://localhost/foo", r#"{"data":"hello world"}"#, APPLICATION_JSON, CHANGE_ALL_RESPONSE); - test_put_response(&server, "http://localhost/foo/1", r#"{"data":"hello world"}"#, APPLICATION_JSON, CHANGE_RESPONSE); + test_post_response( + &server, + "http://localhost/foo", + r#"{"data":"hello world"}"#, + APPLICATION_JSON, + CREATE_RESPONSE + ); + test_put_response( + &server, + "http://localhost/foo", + r#"{"data":"hello world"}"#, + APPLICATION_JSON, + CHANGE_ALL_RESPONSE + ); + test_put_response( + &server, + "http://localhost/foo/1", + r#"{"data":"hello world"}"#, + APPLICATION_JSON, + CHANGE_RESPONSE + ); test_delete_response(&server, "http://localhost/foo", REMOVE_ALL_RESPONSE); test_delete_response(&server, "http://localhost/foo/1", REMOVE_RESPONSE); } diff --git a/tests/cors_handling.rs b/tests/cors_handling.rs index 80ad346..35d5841 100644 --- a/tests/cors_handling.rs +++ b/tests/cors_handling.rs @@ -5,7 +5,7 @@ use gotham::{ router::builder::*, test::{Server, TestRequest, TestServer} }; -use gotham_restful::{CorsConfig, DrawResources, Origin, Raw, Resource, change_all, read_all}; +use gotham_restful::{change_all, read_all, CorsConfig, DrawResources, Origin, Raw, Resource}; use itertools::Itertools; use mime::TEXT_PLAIN; @@ -14,70 +14,108 @@ use mime::TEXT_PLAIN; struct FooResource; #[read_all(FooResource)] -fn read_all() -{ -} +fn read_all() {} #[change_all(FooResource)] -fn change_all(_body : Raw>) -{ -} +fn change_all(_body: Raw>) {} -fn test_server(cfg : CorsConfig) -> TestServer -{ +fn test_server(cfg: CorsConfig) -> TestServer { let (chain, pipeline) = single_pipeline(new_pipeline().add(cfg).build()); - TestServer::new(build_router(chain, pipeline, |router| { - router.resource::("/foo") - })).unwrap() + TestServer::new(build_router(chain, pipeline, |router| router.resource::("/foo"))).unwrap() } -fn test_response(req : TestRequest, origin : Option<&str>, vary : Option<&str>, credentials : bool) +fn test_response(req: TestRequest, origin: Option<&str>, vary: Option<&str>, credentials: bool) where - TS : Server + 'static, - C : Connect + Clone + Send + Sync + 'static + TS: Server + 'static, + C: Connect + Clone + Send + Sync + 'static { - let res = req.with_header(ORIGIN, "http://example.org".parse().unwrap()).perform().unwrap(); + let res = req + .with_header(ORIGIN, "http://example.org".parse().unwrap()) + .perform() + .unwrap(); assert_eq!(res.status(), StatusCode::NO_CONTENT); let headers = res.headers(); println!("{}", headers.keys().join(",")); - assert_eq!(headers.get(ACCESS_CONTROL_ALLOW_ORIGIN).and_then(|value| value.to_str().ok()).as_deref(), origin); + assert_eq!( + headers + .get(ACCESS_CONTROL_ALLOW_ORIGIN) + .and_then(|value| value.to_str().ok()) + .as_deref(), + origin + ); assert_eq!(headers.get(VARY).and_then(|value| value.to_str().ok()).as_deref(), vary); - assert_eq!(headers.get(ACCESS_CONTROL_ALLOW_CREDENTIALS).and_then(|value| value.to_str().ok()).map(|value| value == "true").unwrap_or(false), credentials); + assert_eq!( + headers + .get(ACCESS_CONTROL_ALLOW_CREDENTIALS) + .and_then(|value| value.to_str().ok()) + .map(|value| value == "true") + .unwrap_or(false), + credentials + ); assert!(headers.get(ACCESS_CONTROL_MAX_AGE).is_none()); } -fn test_preflight(server : &TestServer, method : &str, origin : Option<&str>, vary : &str, credentials : bool, max_age : u64) -{ - let res = server.client().options("http://example.org/foo") +fn test_preflight(server: &TestServer, method: &str, origin: Option<&str>, vary: &str, credentials: bool, max_age: u64) { + let res = server + .client() + .options("http://example.org/foo") .with_header(ACCESS_CONTROL_REQUEST_METHOD, method.parse().unwrap()) .with_header(ORIGIN, "http://example.org".parse().unwrap()) - .perform().unwrap(); + .perform() + .unwrap(); assert_eq!(res.status(), StatusCode::NO_CONTENT); let headers = res.headers(); println!("{}", headers.keys().join(",")); - assert_eq!(headers.get(ACCESS_CONTROL_ALLOW_METHODS).and_then(|value| value.to_str().ok()).as_deref(), Some(method)); - assert_eq!(headers.get(ACCESS_CONTROL_ALLOW_ORIGIN).and_then(|value| value.to_str().ok()).as_deref(), origin); + assert_eq!( + headers + .get(ACCESS_CONTROL_ALLOW_METHODS) + .and_then(|value| value.to_str().ok()) + .as_deref(), + Some(method) + ); + assert_eq!( + headers + .get(ACCESS_CONTROL_ALLOW_ORIGIN) + .and_then(|value| value.to_str().ok()) + .as_deref(), + origin + ); assert_eq!(headers.get(VARY).and_then(|value| value.to_str().ok()).as_deref(), Some(vary)); - assert_eq!(headers.get(ACCESS_CONTROL_ALLOW_CREDENTIALS).and_then(|value| value.to_str().ok()).map(|value| value == "true").unwrap_or(false), credentials); - assert_eq!(headers.get(ACCESS_CONTROL_MAX_AGE).and_then(|value| value.to_str().ok()).and_then(|value| value.parse().ok()), Some(max_age)); + assert_eq!( + headers + .get(ACCESS_CONTROL_ALLOW_CREDENTIALS) + .and_then(|value| value.to_str().ok()) + .map(|value| value == "true") + .unwrap_or(false), + credentials + ); + assert_eq!( + headers + .get(ACCESS_CONTROL_MAX_AGE) + .and_then(|value| value.to_str().ok()) + .and_then(|value| value.parse().ok()), + Some(max_age) + ); } - #[test] -fn cors_origin_none() -{ +fn cors_origin_none() { let cfg = Default::default(); let server = test_server(cfg); test_preflight(&server, "PUT", None, "Access-Control-Request-Method", false, 0); - + test_response(server.client().get("http://example.org/foo"), None, None, false); - test_response(server.client().put("http://example.org/foo", Body::empty(), TEXT_PLAIN), None, None, false); + test_response( + server.client().put("http://example.org/foo", Body::empty(), TEXT_PLAIN), + None, + None, + false + ); } #[test] -fn cors_origin_star() -{ +fn cors_origin_star() { let cfg = CorsConfig { origin: Origin::Star, ..Default::default() @@ -85,44 +123,80 @@ fn cors_origin_star() let server = test_server(cfg); test_preflight(&server, "PUT", Some("*"), "Access-Control-Request-Method", false, 0); - + test_response(server.client().get("http://example.org/foo"), Some("*"), None, false); - test_response(server.client().put("http://example.org/foo", Body::empty(), TEXT_PLAIN), Some("*"), None, false); + test_response( + server.client().put("http://example.org/foo", Body::empty(), TEXT_PLAIN), + Some("*"), + None, + false + ); } #[test] -fn cors_origin_single() -{ +fn cors_origin_single() { let cfg = CorsConfig { origin: Origin::Single("https://foo.com".to_owned()), ..Default::default() }; let server = test_server(cfg); - test_preflight(&server, "PUT", Some("https://foo.com"), "Access-Control-Request-Method", false, 0); - - test_response(server.client().get("http://example.org/foo"), Some("https://foo.com"), None, false); - test_response(server.client().put("http://example.org/foo", Body::empty(), TEXT_PLAIN), Some("https://foo.com"), None, false); + test_preflight( + &server, + "PUT", + Some("https://foo.com"), + "Access-Control-Request-Method", + false, + 0 + ); + + test_response( + server.client().get("http://example.org/foo"), + Some("https://foo.com"), + None, + false + ); + test_response( + server.client().put("http://example.org/foo", Body::empty(), TEXT_PLAIN), + Some("https://foo.com"), + None, + false + ); } #[test] -fn cors_origin_copy() -{ +fn cors_origin_copy() { let cfg = CorsConfig { origin: Origin::Copy, ..Default::default() }; let server = test_server(cfg); - test_preflight(&server, "PUT", Some("http://example.org"), "Access-Control-Request-Method,Origin", false, 0); - - test_response(server.client().get("http://example.org/foo"), Some("http://example.org"), Some("Origin"), false); - test_response(server.client().put("http://example.org/foo", Body::empty(), TEXT_PLAIN), Some("http://example.org"), Some("Origin"), false); + test_preflight( + &server, + "PUT", + Some("http://example.org"), + "Access-Control-Request-Method,Origin", + false, + 0 + ); + + test_response( + server.client().get("http://example.org/foo"), + Some("http://example.org"), + Some("Origin"), + false + ); + test_response( + server.client().put("http://example.org/foo", Body::empty(), TEXT_PLAIN), + Some("http://example.org"), + Some("Origin"), + false + ); } #[test] -fn cors_credentials() -{ +fn cors_credentials() { let cfg = CorsConfig { origin: Origin::None, credentials: true, @@ -131,14 +205,18 @@ fn cors_credentials() let server = test_server(cfg); test_preflight(&server, "PUT", None, "Access-Control-Request-Method", true, 0); - + test_response(server.client().get("http://example.org/foo"), None, None, true); - test_response(server.client().put("http://example.org/foo", Body::empty(), TEXT_PLAIN), None, None, true); + test_response( + server.client().put("http://example.org/foo", Body::empty(), TEXT_PLAIN), + None, + None, + true + ); } #[test] -fn cors_max_age() -{ +fn cors_max_age() { let cfg = CorsConfig { origin: Origin::None, max_age: 31536000, @@ -147,7 +225,12 @@ fn cors_max_age() let server = test_server(cfg); test_preflight(&server, "PUT", None, "Access-Control-Request-Method", false, 31536000); - + test_response(server.client().get("http://example.org/foo"), None, None, false); - test_response(server.client().put("http://example.org/foo", Body::empty(), TEXT_PLAIN), None, None, false); + test_response( + server.client().put("http://example.org/foo", Body::empty(), TEXT_PLAIN), + None, + None, + false + ); } diff --git a/tests/custom_request_body.rs b/tests/custom_request_body.rs index 95fa748..2a5baeb 100644 --- a/tests/custom_request_body.rs +++ b/tests/custom_request_body.rs @@ -1,13 +1,8 @@ -use gotham::{ - hyper::header::CONTENT_TYPE, - router::builder::*, - test::TestServer -}; +use gotham::{hyper::header::CONTENT_TYPE, router::builder::*, test::TestServer}; use gotham_restful::*; use mime::TEXT_PLAIN; - -const RESPONSE : &[u8] = b"This is the only valid response."; +const RESPONSE: &[u8] = b"This is the only valid response."; #[derive(Resource)] #[resource(create)] @@ -21,23 +16,24 @@ struct Foo { } #[create(FooResource)] -fn create(body : Foo) -> Raw> { +fn create(body: Foo) -> Raw> { Raw::new(body.content, body.content_type) } - #[test] -fn custom_request_body() -{ +fn custom_request_body() { let server = TestServer::new(build_simple_router(|router| { router.resource::("foo"); - })).unwrap(); - - let res = server.client() + })) + .unwrap(); + + let res = server + .client() .post("http://localhost/foo", RESPONSE, TEXT_PLAIN) - .perform().unwrap(); + .perform() + .unwrap(); assert_eq!(res.headers().get(CONTENT_TYPE).unwrap().to_str().unwrap(), "text/plain"); let res = res.read_body().unwrap(); - let body : &[u8] = res.as_ref(); + let body: &[u8] = res.as_ref(); assert_eq!(body, RESPONSE); } diff --git a/tests/openapi_specification.rs b/tests/openapi_specification.rs index 2171526..c5411af 100644 --- a/tests/openapi_specification.rs +++ b/tests/openapi_specification.rs @@ -1,6 +1,7 @@ #![cfg(all(feature = "auth", feature = "chrono", feature = "openapi"))] -#[macro_use] extern crate gotham_derive; +#[macro_use] +extern crate gotham_derive; use chrono::{NaiveDate, NaiveDateTime}; use gotham::{ @@ -13,10 +14,11 @@ use mime::IMAGE_PNG; use serde::{Deserialize, Serialize}; #[allow(dead_code)] -mod util { include!("util/mod.rs"); } +mod util { + include!("util/mod.rs"); +} use util::{test_get_response, test_openapi_response}; - const IMAGE_RESPONSE : &[u8] = b"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUA/wA0XsCoAAAAAXRSTlN/gFy0ywAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII="; #[derive(Resource)] @@ -28,71 +30,59 @@ struct ImageResource; struct Image(Vec); #[read(ImageResource, operation_id = "getImage")] -fn get_image(_id : u64) -> Raw<&'static [u8]> -{ +fn get_image(_id: u64) -> Raw<&'static [u8]> { Raw::new(IMAGE_RESPONSE, "image/png;base64".parse().unwrap()) } #[change(ImageResource, operation_id = "setImage")] -fn set_image(_id : u64, _image : Image) -{ -} - +fn set_image(_id: u64, _image: Image) {} #[derive(Resource)] #[resource(read, search)] struct SecretResource; #[derive(Deserialize, Clone)] -struct AuthData -{ - sub : String, - iat : u64, - exp : u64 +struct AuthData { + sub: String, + iat: u64, + exp: u64 } type AuthStatus = gotham_restful::AuthStatus; #[derive(OpenapiType, Serialize)] -struct Secret -{ - code : f32 +struct Secret { + code: f32 } #[derive(OpenapiType, Serialize)] -struct Secrets -{ - secrets : Vec +struct Secrets { + secrets: Vec } #[derive(Deserialize, OpenapiType, StateData, StaticResponseExtender)] -struct SecretQuery -{ - date : NaiveDate, - hour : Option, - minute : Option +struct SecretQuery { + date: NaiveDate, + hour: Option, + minute: Option } #[read(SecretResource)] -fn read_secret(auth : AuthStatus, _id : NaiveDateTime) -> AuthSuccess -{ +fn read_secret(auth: AuthStatus, _id: NaiveDateTime) -> AuthSuccess { auth.ok()?; Ok(Secret { code: 4.2 }) } #[search(SecretResource)] -fn search_secret(auth : AuthStatus, _query : SecretQuery) -> AuthSuccess -{ +fn search_secret(auth: AuthStatus, _query: SecretQuery) -> AuthSuccess { auth.ok()?; Ok(Secrets { secrets: vec![Secret { code: 4.2 }, Secret { code: 3.14 }] }) } - #[test] -fn openapi_supports_scope() -{ +fn openapi_supports_scope() { let info = OpenapiInfo { title: "This is just a test".to_owned(), version: "1.2.3".to_owned(), @@ -110,7 +100,8 @@ fn openapi_supports_scope() router.get_openapi("openapi"); router.resource::("secret"); }); - })).unwrap(); - + })) + .unwrap(); + test_openapi_response(&server, "http://localhost/openapi", "tests/openapi_specification.json"); } diff --git a/tests/openapi_supports_scope.rs b/tests/openapi_supports_scope.rs index d126bb8..8d8e298 100644 --- a/tests/openapi_supports_scope.rs +++ b/tests/openapi_supports_scope.rs @@ -1,32 +1,27 @@ #![cfg(feature = "openapi")] -use gotham::{ - router::builder::*, - test::TestServer -}; +use gotham::{router::builder::*, test::TestServer}; use gotham_restful::*; use mime::TEXT_PLAIN; #[allow(dead_code)] -mod util { include!("util/mod.rs"); } +mod util { + include!("util/mod.rs"); +} use util::{test_get_response, test_openapi_response}; - -const RESPONSE : &[u8] = b"This is the only valid response."; +const RESPONSE: &[u8] = b"This is the only valid response."; #[derive(Resource)] #[resource(read_all)] struct FooResource; #[read_all(FooResource)] -fn read_all() -> Raw<&'static [u8]> -{ +fn read_all() -> Raw<&'static [u8]> { Raw::new(RESPONSE, TEXT_PLAIN) } - #[test] -fn openapi_supports_scope() -{ +fn openapi_supports_scope() { let info = OpenapiInfo { title: "Test".to_owned(), version: "1.2.3".to_owned(), @@ -44,8 +39,9 @@ fn openapi_supports_scope() }); router.resource::("foo4"); }); - })).unwrap(); - + })) + .unwrap(); + test_get_response(&server, "http://localhost/foo1", RESPONSE); test_get_response(&server, "http://localhost/bar/foo2", RESPONSE); test_get_response(&server, "http://localhost/bar/baz/foo3", RESPONSE); diff --git a/tests/sync_methods.rs b/tests/sync_methods.rs index a13ec19..211f5f3 100644 --- a/tests/sync_methods.rs +++ b/tests/sync_methods.rs @@ -1,16 +1,15 @@ -#[macro_use] extern crate gotham_derive; +#[macro_use] +extern crate gotham_derive; -use gotham::{ - router::builder::*, - test::TestServer -}; +use gotham::{router::builder::*, test::TestServer}; use gotham_restful::*; use mime::{APPLICATION_JSON, TEXT_PLAIN}; use serde::Deserialize; -mod util { include!("util/mod.rs"); } -use util::{test_get_response, test_post_response, test_put_response, test_delete_response}; - +mod util { + include!("util/mod.rs"); +} +use util::{test_delete_response, test_get_response, test_post_response, test_put_response}; #[derive(Resource)] #[resource(read_all, read, search, create, change_all, change, remove_all, remove)] @@ -19,88 +18,96 @@ struct FooResource; #[derive(Deserialize)] #[cfg_attr(feature = "openapi", derive(OpenapiType))] #[allow(dead_code)] -struct FooBody -{ - data : String +struct FooBody { + data: String } #[derive(Deserialize, StateData, StaticResponseExtender)] #[cfg_attr(feature = "openapi", derive(OpenapiType))] #[allow(dead_code)] -struct FooSearch -{ - query : String +struct FooSearch { + query: String } -const READ_ALL_RESPONSE : &[u8] = b"1ARwwSPVyOKpJKrYwqGgECPVWDl1BqajAAj7g7WJ3e"; +const READ_ALL_RESPONSE: &[u8] = b"1ARwwSPVyOKpJKrYwqGgECPVWDl1BqajAAj7g7WJ3e"; #[read_all(FooResource)] -fn read_all() -> Raw<&'static [u8]> -{ +fn read_all() -> Raw<&'static [u8]> { Raw::new(READ_ALL_RESPONSE, TEXT_PLAIN) } -const READ_RESPONSE : &[u8] = b"FEReHoeBKU17X2bBpVAd1iUvktFL43CDu0cFYHdaP9"; +const READ_RESPONSE: &[u8] = b"FEReHoeBKU17X2bBpVAd1iUvktFL43CDu0cFYHdaP9"; #[read(FooResource)] -fn read(_id : u64) -> Raw<&'static [u8]> -{ +fn read(_id: u64) -> Raw<&'static [u8]> { Raw::new(READ_RESPONSE, TEXT_PLAIN) } -const SEARCH_RESPONSE : &[u8] = b"AWqcQUdBRHXKh3at4u79mdupOAfEbnTcx71ogCVF0E"; +const SEARCH_RESPONSE: &[u8] = b"AWqcQUdBRHXKh3at4u79mdupOAfEbnTcx71ogCVF0E"; #[search(FooResource)] -fn search(_body : FooSearch) -> Raw<&'static [u8]> -{ +fn search(_body: FooSearch) -> Raw<&'static [u8]> { Raw::new(SEARCH_RESPONSE, TEXT_PLAIN) } -const CREATE_RESPONSE : &[u8] = b"y6POY7wOMAB0jBRBw0FJT7DOpUNbhmT8KdpQPLkI83"; +const CREATE_RESPONSE: &[u8] = b"y6POY7wOMAB0jBRBw0FJT7DOpUNbhmT8KdpQPLkI83"; #[create(FooResource)] -fn create(_body : FooBody) -> Raw<&'static [u8]> -{ +fn create(_body: FooBody) -> Raw<&'static [u8]> { Raw::new(CREATE_RESPONSE, TEXT_PLAIN) } -const CHANGE_ALL_RESPONSE : &[u8] = b"QlbYg8gHE9OQvvk3yKjXJLTSXlIrg9mcqhfMXJmQkv"; +const CHANGE_ALL_RESPONSE: &[u8] = b"QlbYg8gHE9OQvvk3yKjXJLTSXlIrg9mcqhfMXJmQkv"; #[change_all(FooResource)] -fn change_all(_body : FooBody) -> Raw<&'static [u8]> -{ +fn change_all(_body: FooBody) -> Raw<&'static [u8]> { Raw::new(CHANGE_ALL_RESPONSE, TEXT_PLAIN) } -const CHANGE_RESPONSE : &[u8] = b"qGod55RUXkT1lgPO8h0uVM6l368O2S0GrwENZFFuRu"; +const CHANGE_RESPONSE: &[u8] = b"qGod55RUXkT1lgPO8h0uVM6l368O2S0GrwENZFFuRu"; #[change(FooResource)] -fn change(_id : u64, _body : FooBody) -> Raw<&'static [u8]> -{ +fn change(_id: u64, _body: FooBody) -> Raw<&'static [u8]> { Raw::new(CHANGE_RESPONSE, TEXT_PLAIN) } -const REMOVE_ALL_RESPONSE : &[u8] = b"Y36kZ749MRk2Nem4BedJABOZiZWPLOtiwLfJlGTwm5"; +const REMOVE_ALL_RESPONSE: &[u8] = b"Y36kZ749MRk2Nem4BedJABOZiZWPLOtiwLfJlGTwm5"; #[remove_all(FooResource)] -fn remove_all() -> Raw<&'static [u8]> -{ +fn remove_all() -> Raw<&'static [u8]> { Raw::new(REMOVE_ALL_RESPONSE, TEXT_PLAIN) } -const REMOVE_RESPONSE : &[u8] = b"CwRzBrKErsVZ1N7yeNfjZuUn1MacvgBqk4uPOFfDDq"; +const REMOVE_RESPONSE: &[u8] = b"CwRzBrKErsVZ1N7yeNfjZuUn1MacvgBqk4uPOFfDDq"; #[remove(FooResource)] -fn remove(_id : u64) -> Raw<&'static [u8]> -{ +fn remove(_id: u64) -> Raw<&'static [u8]> { Raw::new(REMOVE_RESPONSE, TEXT_PLAIN) } #[test] -fn sync_methods() -{ +fn sync_methods() { let server = TestServer::new(build_simple_router(|router| { router.resource::("foo"); - })).unwrap(); - + })) + .unwrap(); + test_get_response(&server, "http://localhost/foo", READ_ALL_RESPONSE); test_get_response(&server, "http://localhost/foo/1", READ_RESPONSE); test_get_response(&server, "http://localhost/foo/search?query=hello+world", SEARCH_RESPONSE); - test_post_response(&server, "http://localhost/foo", r#"{"data":"hello world"}"#, APPLICATION_JSON, CREATE_RESPONSE); - test_put_response(&server, "http://localhost/foo", r#"{"data":"hello world"}"#, APPLICATION_JSON, CHANGE_ALL_RESPONSE); - test_put_response(&server, "http://localhost/foo/1", r#"{"data":"hello world"}"#, APPLICATION_JSON, CHANGE_RESPONSE); + test_post_response( + &server, + "http://localhost/foo", + r#"{"data":"hello world"}"#, + APPLICATION_JSON, + CREATE_RESPONSE + ); + test_put_response( + &server, + "http://localhost/foo", + r#"{"data":"hello world"}"#, + APPLICATION_JSON, + CHANGE_ALL_RESPONSE + ); + test_put_response( + &server, + "http://localhost/foo/1", + r#"{"data":"hello world"}"#, + APPLICATION_JSON, + CHANGE_RESPONSE + ); test_delete_response(&server, "http://localhost/foo", REMOVE_ALL_RESPONSE); test_delete_response(&server, "http://localhost/foo/1", REMOVE_RESPONSE); } diff --git a/tests/trybuild_ui.rs b/tests/trybuild_ui.rs index b5a572a..8f2d20b 100644 --- a/tests/trybuild_ui.rs +++ b/tests/trybuild_ui.rs @@ -2,10 +2,9 @@ use trybuild::TestCases; #[test] #[ignore] -fn trybuild_ui() -{ +fn trybuild_ui() { let t = TestCases::new(); - + // always enabled t.compile_fail("tests/ui/from_body_enum.rs"); t.compile_fail("tests/ui/method_async_state.rs"); @@ -16,10 +15,9 @@ fn trybuild_ui() t.compile_fail("tests/ui/method_too_many_args.rs"); t.compile_fail("tests/ui/method_unsafe.rs"); t.compile_fail("tests/ui/resource_unknown_method.rs"); - + // require the openapi feature - if cfg!(feature = "openapi") - { + if cfg!(feature = "openapi") { t.compile_fail("tests/ui/openapi_type_enum_with_fields.rs"); t.compile_fail("tests/ui/openapi_type_nullable_non_bool.rs"); t.compile_fail("tests/ui/openapi_type_rename_non_string.rs");