mirror of
https://gitlab.com/msrd0/gotham-restful.git
synced 2025-02-23 04:52:28 +00:00
update to gotham 0.5 and start using rustfmt
This commit is contained in:
parent
5317e50961
commit
d55b0897e9
39 changed files with 1798 additions and 2095 deletions
11
Cargo.toml
11
Cargo.toml
|
@ -20,12 +20,12 @@ gitlab = { repository = "msrd0/gotham-restful", branch = "master" }
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = { version = "0.12.1", optional = true }
|
base64 = { version = "0.12.1", optional = true }
|
||||||
chrono = { version = "0.4.11", features = ["serde"], 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-core = "0.3.5"
|
||||||
futures-util = "0.3.5"
|
futures-util = "0.3.5"
|
||||||
gotham = { version = "0.5.0-rc.1", default-features = false }
|
gotham = { version = "0.5.0", default-features = false }
|
||||||
gotham_derive = "0.5.0-rc.1"
|
gotham_derive = "0.5.0"
|
||||||
gotham_middleware_diesel = { version = "0.1.2", optional = true }
|
gotham_middleware_diesel = { version = "0.2.0", optional = true }
|
||||||
gotham_restful_derive = { version = "0.1.0-rc0" }
|
gotham_restful_derive = { version = "0.1.0-rc0" }
|
||||||
indexmap = { version = "1.3.2", optional = true }
|
indexmap = { version = "1.3.2", optional = true }
|
||||||
itertools = "0.9.0"
|
itertools = "0.9.0"
|
||||||
|
@ -40,7 +40,7 @@ uuid = { version = "0.8.1", optional = true }
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
diesel = { version = "1.4.4", features = ["postgres"] }
|
diesel = { version = "1.4.4", features = ["postgres"] }
|
||||||
futures-executor = "0.3.5"
|
futures-executor = "0.3.5"
|
||||||
paste = "0.1.12"
|
paste = "1.0"
|
||||||
thiserror = "1.0.18"
|
thiserror = "1.0.18"
|
||||||
trybuild = "1.0.27"
|
trybuild = "1.0.27"
|
||||||
|
|
||||||
|
@ -56,5 +56,6 @@ openapi = ["gotham_restful_derive/openapi", "indexmap", "openapiv3"]
|
||||||
all-features = true
|
all-features = true
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
|
gotham = { path = "../gotham/gotham" }
|
||||||
gotham_restful = { path = "." }
|
gotham_restful = { path = "." }
|
||||||
gotham_restful_derive = { path = "./derive" }
|
gotham_restful_derive = { path = "./derive" }
|
||||||
|
|
|
@ -1,50 +1,42 @@
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote};
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use syn::{
|
use syn::{spanned::Spanned, Data, DeriveInput, Error, Field, Fields, Ident, Result, Type};
|
||||||
spanned::Spanned,
|
|
||||||
Data,
|
|
||||||
DeriveInput,
|
|
||||||
Error,
|
|
||||||
Field,
|
|
||||||
Fields,
|
|
||||||
Ident,
|
|
||||||
Result,
|
|
||||||
Type
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ParsedFields
|
struct ParsedFields {
|
||||||
{
|
fields: Vec<(Ident, Type)>,
|
||||||
fields : Vec<(Ident, Type)>,
|
named: bool
|
||||||
named : bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParsedFields
|
impl ParsedFields {
|
||||||
{
|
fn from_named<I>(fields: I) -> Self
|
||||||
fn from_named<I>(fields : I) -> Self
|
|
||||||
where
|
where
|
||||||
I : Iterator<Item = Field>
|
I: Iterator<Item = Field>
|
||||||
{
|
{
|
||||||
let fields = fields.map(|field| (field.ident.unwrap(), field.ty)).collect();
|
let fields = fields.map(|field| (field.ident.unwrap(), field.ty)).collect();
|
||||||
Self { fields, named: true }
|
Self { fields, named: true }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_unnamed<I>(fields : I) -> Self
|
fn from_unnamed<I>(fields: I) -> Self
|
||||||
where
|
where
|
||||||
I : Iterator<Item = Field>
|
I: Iterator<Item = Field>
|
||||||
{
|
{
|
||||||
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 }
|
Self { fields, named: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_unit() -> Self
|
fn from_unit() -> Self {
|
||||||
{
|
Self {
|
||||||
Self { fields: Vec::new(), named: false }
|
fields: Vec::new(),
|
||||||
|
named: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expand_from_body(input : DeriveInput) -> Result<TokenStream>
|
pub fn expand_from_body(input: DeriveInput) -> Result<TokenStream> {
|
||||||
{
|
|
||||||
let krate = super::krate();
|
let krate = super::krate();
|
||||||
let ident = input.ident;
|
let ident = input.ident;
|
||||||
let generics = input.generics;
|
let generics = input.generics;
|
||||||
|
@ -53,7 +45,8 @@ pub fn expand_from_body(input : DeriveInput) -> Result<TokenStream>
|
||||||
Data::Enum(inum) => Err(inum.enum_token.span()),
|
Data::Enum(inum) => Err(inum.enum_token.span()),
|
||||||
Data::Struct(strukt) => Ok(strukt),
|
Data::Struct(strukt) => Ok(strukt),
|
||||||
Data::Union(uni) => Err(uni.union_token.span())
|
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 {
|
let fields = match strukt.fields {
|
||||||
Fields::Named(named) => ParsedFields::from_named(named.named.into_iter()),
|
Fields::Named(named) => ParsedFields::from_named(named.named.into_iter()),
|
||||||
|
@ -66,8 +59,7 @@ pub fn expand_from_body(input : DeriveInput) -> Result<TokenStream>
|
||||||
let mut body_ident = format_ident!("_body");
|
let mut body_ident = format_ident!("_body");
|
||||||
let mut type_ident = format_ident!("_type");
|
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();
|
body_ident = body_field.0.clone();
|
||||||
let body_ty = &body_field.1;
|
let body_ty = &body_field.1;
|
||||||
where_clause = quote! {
|
where_clause = quote! {
|
||||||
|
@ -81,8 +73,7 @@ pub fn expand_from_body(input : DeriveInput) -> Result<TokenStream>
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(type_field) = fields.fields.get(1)
|
if let Some(type_field) = fields.fields.get(1) {
|
||||||
{
|
|
||||||
type_ident = type_field.0.clone();
|
type_ident = type_field.0.clone();
|
||||||
let type_ty = &type_field.1;
|
let type_ty = &type_field.1;
|
||||||
where_clause = quote! {
|
where_clause = quote! {
|
||||||
|
@ -95,8 +86,7 @@ pub fn expand_from_body(input : DeriveInput) -> Result<TokenStream>
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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_ident = &field.0;
|
||||||
let field_ty = &field.1;
|
let field_ty = &field.1;
|
||||||
where_clause = quote! {
|
where_clause = quote! {
|
||||||
|
@ -109,7 +99,7 @@ pub fn expand_from_body(input : DeriveInput) -> Result<TokenStream>
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
let ctor = if fields.named {
|
||||||
quote!(Self { #(#field_names),* })
|
quote!(Self { #(#field_names),* })
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -21,114 +21,96 @@ mod openapi_type;
|
||||||
use openapi_type::expand_openapi_type;
|
use openapi_type::expand_openapi_type;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn print_tokens(tokens : TokenStream2) -> TokenStream
|
fn print_tokens(tokens: TokenStream2) -> TokenStream {
|
||||||
{
|
|
||||||
//eprintln!("{}", tokens);
|
//eprintln!("{}", tokens);
|
||||||
tokens.into()
|
tokens.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn expand_derive<F>(input : TokenStream, expand : F) -> TokenStream
|
fn expand_derive<F>(input: TokenStream, expand: F) -> TokenStream
|
||||||
where
|
where
|
||||||
F : FnOnce(DeriveInput) -> Result<TokenStream2>
|
F: FnOnce(DeriveInput) -> Result<TokenStream2>
|
||||||
{
|
{
|
||||||
print_tokens(expand(parse_macro_input!(input))
|
print_tokens(expand(parse_macro_input!(input)).unwrap_or_else(|err| err.to_compile_error()))
|
||||||
.unwrap_or_else(|err| err.to_compile_error()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn expand_macro<F, A, I>(attrs : TokenStream, item : TokenStream, expand : F) -> TokenStream
|
fn expand_macro<F, A, I>(attrs: TokenStream, item: TokenStream, expand: F) -> TokenStream
|
||||||
where
|
where
|
||||||
F : FnOnce(A, I) -> Result<TokenStream2>,
|
F: FnOnce(A, I) -> Result<TokenStream2>,
|
||||||
A : ParseMacroInput,
|
A: ParseMacroInput,
|
||||||
I : ParseMacroInput
|
I: ParseMacroInput
|
||||||
{
|
{
|
||||||
print_tokens(expand(parse_macro_input!(attrs), parse_macro_input!(item))
|
print_tokens(expand(parse_macro_input!(attrs), parse_macro_input!(item)).unwrap_or_else(|err| err.to_compile_error()))
|
||||||
.unwrap_or_else(|err| err.to_compile_error()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn krate() -> TokenStream2
|
fn krate() -> TokenStream2 {
|
||||||
{
|
|
||||||
quote!(::gotham_restful)
|
quote!(::gotham_restful)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_derive(FromBody)]
|
#[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)
|
expand_derive(input, expand_from_body)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
#[proc_macro_derive(OpenapiType, attributes(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)
|
expand_derive(input, expand_openapi_type)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_derive(RequestBody, attributes(supported_types))]
|
#[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)
|
expand_derive(input, expand_request_body)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_derive(Resource, attributes(resource))]
|
#[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)
|
expand_derive(input, expand_resource)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_derive(ResourceError, attributes(display, from, status))]
|
#[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)
|
expand_derive(input, expand_resource_error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[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))
|
expand_macro(attr, item, |attr, item| expand_method(Method::ReadAll, attr, item))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[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))
|
expand_macro(attr, item, |attr, item| expand_method(Method::Read, attr, item))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[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))
|
expand_macro(attr, item, |attr, item| expand_method(Method::Search, attr, item))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[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))
|
expand_macro(attr, item, |attr, item| expand_method(Method::Create, attr, item))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[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))
|
expand_macro(attr, item, |attr, item| expand_method(Method::ChangeAll, attr, item))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[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))
|
expand_macro(attr, item, |attr, item| expand_method(Method::Change, attr, item))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[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))
|
expand_macro(attr, item, |attr, item| expand_method(Method::RemoveAll, attr, item))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[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))
|
expand_macro(attr, item, |attr, item| expand_method(Method::Remove, attr, item))
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,26 +2,13 @@ use crate::util::CollectToResult;
|
||||||
use heck::{CamelCase, SnakeCase};
|
use heck::{CamelCase, SnakeCase};
|
||||||
use proc_macro2::{Ident, Span, TokenStream};
|
use proc_macro2::{Ident, Span, TokenStream};
|
||||||
use quote::{format_ident, quote};
|
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 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,
|
ReadAll,
|
||||||
Read,
|
Read,
|
||||||
Search,
|
Search,
|
||||||
|
@ -32,12 +19,10 @@ pub enum Method
|
||||||
Remove
|
Remove
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Method
|
impl FromStr for Method {
|
||||||
{
|
|
||||||
type Err = Error;
|
type Err = Error;
|
||||||
|
|
||||||
fn from_str(str : &str) -> Result<Self>
|
fn from_str(str: &str) -> Result<Self> {
|
||||||
{
|
|
||||||
match str {
|
match str {
|
||||||
"ReadAll" | "read_all" => Ok(Self::ReadAll),
|
"ReadAll" | "read_all" => Ok(Self::ReadAll),
|
||||||
"Read" | "read" => Ok(Self::Read),
|
"Read" | "read" => Ok(Self::Read),
|
||||||
|
@ -52,10 +37,8 @@ impl FromStr for Method
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Method
|
impl Method {
|
||||||
{
|
pub fn type_names(&self) -> Vec<&'static str> {
|
||||||
pub fn type_names(&self) -> Vec<&'static str>
|
|
||||||
{
|
|
||||||
use Method::*;
|
use Method::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
|
@ -67,8 +50,7 @@ impl Method
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn trait_ident(&self) -> Ident
|
pub fn trait_ident(&self) -> Ident {
|
||||||
{
|
|
||||||
use Method::*;
|
use Method::*;
|
||||||
|
|
||||||
let name = match self {
|
let name = match self {
|
||||||
|
@ -84,8 +66,7 @@ impl Method
|
||||||
format_ident!("Resource{}", name)
|
format_ident!("Resource{}", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fn_ident(&self) -> Ident
|
pub fn fn_ident(&self) -> Ident {
|
||||||
{
|
|
||||||
use Method::*;
|
use Method::*;
|
||||||
|
|
||||||
let name = match self {
|
let name = match self {
|
||||||
|
@ -101,25 +82,25 @@ impl Method
|
||||||
format_ident!("{}", name)
|
format_ident!("{}", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mod_ident(&self, resource : &str) -> Ident
|
pub fn mod_ident(&self, resource: &str) -> Ident {
|
||||||
{
|
format_ident!(
|
||||||
format_ident!("_gotham_restful_resource_{}_method_{}", resource.to_snake_case(), self.fn_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())
|
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())
|
format_ident!("{}_{}_setup_impl", resource.to_snake_case(), self.fn_ident())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
enum MethodArgumentType
|
enum MethodArgumentType {
|
||||||
{
|
|
||||||
StateRef,
|
StateRef,
|
||||||
StateMutRef,
|
StateMutRef,
|
||||||
MethodArg(Type),
|
MethodArg(Type),
|
||||||
|
@ -128,81 +109,77 @@ enum MethodArgumentType
|
||||||
AuthStatusRef(Type)
|
AuthStatusRef(Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MethodArgumentType
|
impl MethodArgumentType {
|
||||||
{
|
fn is_state_ref(&self) -> bool {
|
||||||
fn is_state_ref(&self) -> bool
|
|
||||||
{
|
|
||||||
matches!(self, Self::StateRef | Self::StateMutRef)
|
matches!(self, Self::StateRef | Self::StateMutRef)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_method_arg(&self) -> bool
|
fn is_method_arg(&self) -> bool {
|
||||||
{
|
|
||||||
matches!(self, Self::MethodArg(_))
|
matches!(self, Self::MethodArg(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_database_conn(&self) -> bool
|
fn is_database_conn(&self) -> bool {
|
||||||
{
|
|
||||||
matches!(self, Self::DatabaseConnection(_))
|
matches!(self, Self::DatabaseConnection(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_auth_status(&self) -> bool
|
fn is_auth_status(&self) -> bool {
|
||||||
{
|
|
||||||
matches!(self, Self::AuthStatus(_) | Self::AuthStatusRef(_))
|
matches!(self, Self::AuthStatus(_) | Self::AuthStatusRef(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ty(&self) -> Option<&Type>
|
fn ty(&self) -> Option<&Type> {
|
||||||
{
|
|
||||||
match self {
|
match self {
|
||||||
Self::MethodArg(ty) | Self::DatabaseConnection(ty) | Self::AuthStatus(ty) | Self::AuthStatusRef(ty) => Some(ty),
|
Self::MethodArg(ty) | Self::DatabaseConnection(ty) | Self::AuthStatus(ty) | Self::AuthStatusRef(ty) => Some(ty),
|
||||||
_ => None
|
_ => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn quote_ty(&self) -> Option<TokenStream>
|
fn quote_ty(&self) -> Option<TokenStream> {
|
||||||
{
|
|
||||||
self.ty().map(|ty| quote!(#ty))
|
self.ty().map(|ty| quote!(#ty))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MethodArgument
|
struct MethodArgument {
|
||||||
{
|
ident: Ident,
|
||||||
ident : Ident,
|
ident_span: Span,
|
||||||
ident_span : Span,
|
ty: MethodArgumentType
|
||||||
ty : MethodArgumentType
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Spanned for MethodArgument
|
impl Spanned for MethodArgument {
|
||||||
{
|
fn span(&self) -> Span {
|
||||||
fn span(&self) -> Span
|
|
||||||
{
|
|
||||||
self.ident_span
|
self.ident_span
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn interpret_arg_ty(attrs : &[Attribute], name : &str, ty : Type) -> Result<MethodArgumentType>
|
fn interpret_arg_ty(attrs: &[Attribute], name: &str, ty: Type) -> Result<MethodArgumentType> {
|
||||||
{
|
let attr = attrs
|
||||||
let attr = attrs.iter()
|
.iter()
|
||||||
.find(|arg| arg.path.segments.iter().any(|path| &path.ident.to_string() == "rest_arg"))
|
.find(|arg| arg.path.segments.iter().any(|path| &path.ident.to_string() == "rest_arg"))
|
||||||
.map(|arg| arg.tokens.to_string());
|
.map(|arg| arg.tokens.to_string());
|
||||||
|
|
||||||
// TODO issue a warning for _state usage once diagnostics become stable
|
// 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 {
|
return match ty {
|
||||||
Type::Reference(ty) => Ok(if ty.mutability.is_none() { MethodArgumentType::StateRef } else { MethodArgumentType::StateMutRef }),
|
Type::Reference(ty) => Ok(if ty.mutability.is_none() {
|
||||||
_ => Err(Error::new(ty.span(), "The state parameter has to be a (mutable) reference to gotham_restful::State"))
|
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 {
|
return Ok(match ty {
|
||||||
Type::Reference(ty) => MethodArgumentType::AuthStatusRef(*ty.elem),
|
Type::Reference(ty) => MethodArgumentType::AuthStatusRef(*ty.elem),
|
||||||
ty => MethodArgumentType::AuthStatus(ty)
|
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 {
|
return Ok(MethodArgumentType::DatabaseConnection(match ty {
|
||||||
Type::Reference(ty) => *ty.elem,
|
Type::Reference(ty) => *ty.elem,
|
||||||
|
@ -213,26 +190,25 @@ fn interpret_arg_ty(attrs : &[Attribute], name : &str, ty : Type) -> Result<Meth
|
||||||
Ok(MethodArgumentType::MethodArg(ty))
|
Ok(MethodArgumentType::MethodArg(ty))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn interpret_arg(index : usize, arg : &PatType) -> Result<MethodArgument>
|
fn interpret_arg(index: usize, arg: &PatType) -> Result<MethodArgument> {
|
||||||
{
|
|
||||||
let pat = &arg.pat;
|
let pat = &arg.pat;
|
||||||
let ident = format_ident!("arg{}", index);
|
let ident = format_ident!("arg{}", index);
|
||||||
let orig_name = quote!(#pat);
|
let orig_name = quote!(#pat);
|
||||||
let ty = interpret_arg_ty(&arg.attrs, &orig_name.to_string(), *arg.ty.clone())?;
|
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")]
|
#[cfg(feature = "openapi")]
|
||||||
fn expand_operation_id(attrs : &[NestedMeta]) -> TokenStream
|
fn expand_operation_id(attrs: &[NestedMeta]) -> TokenStream {
|
||||||
{
|
let mut operation_id: Option<&Lit> = None;
|
||||||
let mut operation_id : Option<&Lit> = None;
|
for meta in attrs {
|
||||||
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()) {
|
||||||
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)
|
operation_id = Some(&kv.lit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -250,21 +226,19 @@ fn expand_operation_id(attrs : &[NestedMeta]) -> TokenStream
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "openapi"))]
|
#[cfg(not(feature = "openapi"))]
|
||||||
fn expand_operation_id(_ : &[NestedMeta]) -> TokenStream
|
fn expand_operation_id(_: &[NestedMeta]) -> TokenStream {
|
||||||
{
|
|
||||||
quote!()
|
quote!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expand_wants_auth(attrs : &[NestedMeta], default : bool) -> TokenStream
|
fn expand_wants_auth(attrs: &[NestedMeta], default: bool) -> TokenStream {
|
||||||
{
|
let default_lit = Lit::Bool(LitBool {
|
||||||
let default_lit = Lit::Bool(LitBool { value: default, span: Span::call_site() });
|
value: default,
|
||||||
|
span: Span::call_site()
|
||||||
|
});
|
||||||
let mut wants_auth = &default_lit;
|
let mut wants_auth = &default_lit;
|
||||||
for meta in attrs
|
for meta in attrs {
|
||||||
{
|
if let NestedMeta::Meta(Meta::NameValue(kv)) = meta {
|
||||||
if let NestedMeta::Meta(Meta::NameValue(kv)) = meta
|
if kv.path.segments.last().map(|p| p.ident.to_string()) == Some("wants_auth".to_owned()) {
|
||||||
{
|
|
||||||
if kv.path.segments.last().map(|p| p.ident.to_string()) == Some("wants_auth".to_owned())
|
|
||||||
{
|
|
||||||
wants_auth = &kv.lit
|
wants_auth = &kv.lit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -279,28 +253,36 @@ fn expand_wants_auth(attrs : &[NestedMeta], default : bool) -> TokenStream
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::comparison_chain)]
|
#[allow(clippy::comparison_chain)]
|
||||||
pub fn expand_method(method : Method, mut attrs : AttributeArgs, fun : ItemFn) -> Result<TokenStream>
|
pub fn expand_method(method: Method, mut attrs: AttributeArgs, fun: ItemFn) -> Result<TokenStream> {
|
||||||
{
|
|
||||||
let krate = super::krate();
|
let krate = super::krate();
|
||||||
|
|
||||||
// parse attributes
|
// parse attributes
|
||||||
if attrs.len() < 1
|
if attrs.len() < 1 {
|
||||||
{
|
return Err(Error::new(
|
||||||
return Err(Error::new(Span::call_site(), "Missing Resource struct. Example: #[read_all(MyResource)]"));
|
Span::call_site(),
|
||||||
|
"Missing Resource struct. Example: #[read_all(MyResource)]"
|
||||||
|
));
|
||||||
}
|
}
|
||||||
let resource_path = match attrs.remove(0) {
|
let resource_path = match attrs.remove(0) {
|
||||||
NestedMeta::Meta(Meta::Path(path)) => path,
|
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())
|
let resource_name = resource_path
|
||||||
.ok_or_else(|| Error::new(resource_path.span(), "Resource name must not be empty"))?;
|
.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_ident = &fun.sig.ident;
|
||||||
let fun_vis = &fun.vis;
|
let fun_vis = &fun.vis;
|
||||||
let fun_is_async = fun.sig.asyncness.is_some();
|
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"));
|
return Err(Error::new(unsafety.span(), "Resource methods must not be unsafe"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -323,7 +305,10 @@ pub fn expand_method(method : Method, mut attrs : AttributeArgs, fun : ItemFn) -
|
||||||
let res_ident = format_ident!("res");
|
let res_ident = format_ident!("res");
|
||||||
|
|
||||||
// extract arguments into pattern, ident and type
|
// extract arguments into pattern, ident and type
|
||||||
let args = fun.sig.inputs.iter()
|
let args = fun
|
||||||
|
.sig
|
||||||
|
.inputs
|
||||||
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, arg)| match arg {
|
.map(|(i, arg)| match arg {
|
||||||
FnArg::Typed(arg) => interpret_arg(i, arg),
|
FnArg::Typed(arg) => interpret_arg(i, arg),
|
||||||
|
@ -334,18 +319,14 @@ pub fn expand_method(method : Method, mut attrs : AttributeArgs, fun : ItemFn) -
|
||||||
// extract the generic parameters to use
|
// extract the generic parameters to use
|
||||||
let ty_names = method.type_names();
|
let ty_names = method.type_names();
|
||||||
let ty_len = ty_names.len();
|
let ty_len = ty_names.len();
|
||||||
let generics_args : Vec<&MethodArgument> = args.iter()
|
let generics_args: Vec<&MethodArgument> = args.iter().filter(|arg| (*arg).ty.is_method_arg()).collect();
|
||||||
.filter(|arg| (*arg).ty.is_method_arg())
|
if generics_args.len() > ty_len {
|
||||||
.collect();
|
|
||||||
if generics_args.len() > ty_len
|
|
||||||
{
|
|
||||||
return Err(Error::new(generics_args[ty_len].span(), "Too many arguments"));
|
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"));
|
return Err(Error::new(fun_ident.span(), "Too few arguments"));
|
||||||
}
|
}
|
||||||
let generics : Vec<TokenStream> = generics_args.iter()
|
let generics: Vec<TokenStream> = generics_args
|
||||||
|
.iter()
|
||||||
.map(|arg| arg.ty.quote_ty().unwrap())
|
.map(|arg| arg.ty.quote_ty().unwrap())
|
||||||
.zip(ty_names)
|
.zip(ty_names)
|
||||||
.map(|(arg, name)| {
|
.map(|(arg, name)| {
|
||||||
|
@ -355,45 +336,51 @@ pub fn expand_method(method : Method, mut attrs : AttributeArgs, fun : ItemFn) -
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// extract the definition of our method
|
// extract the definition of our method
|
||||||
let mut args_def : Vec<TokenStream> = args.iter()
|
let mut args_def: Vec<TokenStream> = args
|
||||||
|
.iter()
|
||||||
.filter(|arg| (*arg).ty.is_method_arg())
|
.filter(|arg| (*arg).ty.is_method_arg())
|
||||||
.map(|arg| {
|
.map(|arg| {
|
||||||
let ident = &arg.ident;
|
let ident = &arg.ident;
|
||||||
let ty = arg.ty.quote_ty();
|
let ty = arg.ty.quote_ty();
|
||||||
quote!(#ident : #ty)
|
quote!(#ident : #ty)
|
||||||
}).collect();
|
})
|
||||||
|
.collect();
|
||||||
args_def.insert(0, quote!(mut #state_ident : #krate::State));
|
args_def.insert(0, quote!(mut #state_ident : #krate::State));
|
||||||
|
|
||||||
// extract the arguments to pass over to the supplied method
|
// extract the arguments to pass over to the supplied method
|
||||||
let args_pass : Vec<TokenStream> = args.iter().map(|arg| match (&arg.ty, &arg.ident) {
|
let args_pass: Vec<TokenStream> = args
|
||||||
(MethodArgumentType::StateRef, _) => quote!(&#state_ident),
|
.iter()
|
||||||
(MethodArgumentType::StateMutRef, _) => quote!(&mut #state_ident),
|
.map(|arg| match (&arg.ty, &arg.ident) {
|
||||||
(MethodArgumentType::MethodArg(_), ident) => quote!(#ident),
|
(MethodArgumentType::StateRef, _) => quote!(&#state_ident),
|
||||||
(MethodArgumentType::DatabaseConnection(_), _) => quote!(&#conn_ident),
|
(MethodArgumentType::StateMutRef, _) => quote!(&mut #state_ident),
|
||||||
(MethodArgumentType::AuthStatus(_), _) => quote!(#auth_ident),
|
(MethodArgumentType::MethodArg(_), ident) => quote!(#ident),
|
||||||
(MethodArgumentType::AuthStatusRef(_), _) => quote!(&#auth_ident)
|
(MethodArgumentType::DatabaseConnection(_), _) => quote!(&#conn_ident),
|
||||||
}).collect();
|
(MethodArgumentType::AuthStatus(_), _) => quote!(#auth_ident),
|
||||||
|
(MethodArgumentType::AuthStatusRef(_), _) => quote!(&#auth_ident)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
// prepare the method block
|
// prepare the method block
|
||||||
let mut block = quote!(#fun_ident(#(#args_pass),*));
|
let mut block = quote!(#fun_ident(#(#args_pass),*));
|
||||||
let mut state_block = quote!();
|
let mut state_block = quote!();
|
||||||
if fun_is_async
|
if fun_is_async {
|
||||||
{
|
if let Some(arg) = args.iter().find(|arg| (*arg).ty.is_state_ref()) {
|
||||||
if let Some(arg) = args.iter().find(|arg| (*arg).ty.is_state_ref())
|
return Err(Error::new(
|
||||||
{
|
arg.span(),
|
||||||
return Err(Error::new(arg.span(), "async fn must not take &State as an argument as State is not Sync, consider boxing"));
|
"async fn must not take &State as an argument as State is not Sync, consider boxing"
|
||||||
|
));
|
||||||
}
|
}
|
||||||
block = quote!(#block.await);
|
block = quote!(#block.await);
|
||||||
}
|
}
|
||||||
if is_no_content
|
if is_no_content {
|
||||||
{
|
|
||||||
block = quote!(#block; Default::default())
|
block = quote!(#block; Default::default())
|
||||||
}
|
}
|
||||||
if let Some(arg) = args.iter().find(|arg| (*arg).ty.is_database_conn())
|
if let Some(arg) = args.iter().find(|arg| (*arg).ty.is_database_conn()) {
|
||||||
{
|
if fun_is_async {
|
||||||
if fun_is_async
|
return Err(Error::new(
|
||||||
{
|
arg.span(),
|
||||||
return Err(Error::new(arg.span(), "async fn is not supported when database support is required, consider boxing"));
|
"async fn is not supported when database support is required, consider boxing"
|
||||||
|
));
|
||||||
}
|
}
|
||||||
let conn_ty = arg.ty.quote_ty();
|
let conn_ty = arg.ty.quote_ty();
|
||||||
state_block = quote! {
|
state_block = quote! {
|
||||||
|
@ -411,8 +398,7 @@ 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();
|
let auth_ty = arg.ty.quote_ty();
|
||||||
state_block = quote! {
|
state_block = quote! {
|
||||||
#state_block
|
#state_block
|
||||||
|
@ -422,8 +408,7 @@ pub fn expand_method(method : Method, mut attrs : AttributeArgs, fun : ItemFn) -
|
||||||
|
|
||||||
// prepare the where clause
|
// prepare the where clause
|
||||||
let mut where_clause = quote!(#resource_path : #krate::Resource,);
|
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();
|
let auth_ty = arg.ty.quote_ty();
|
||||||
where_clause = quote!(#where_clause #auth_ty : Clone,);
|
where_clause = quote!(#where_clause #auth_ty : Clone,);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,46 +1,31 @@
|
||||||
use crate::util::{CollectToResult, remove_parens};
|
use crate::util::{remove_parens, CollectToResult};
|
||||||
use proc_macro2::{Ident, TokenStream};
|
use proc_macro2::{Ident, TokenStream};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{
|
use syn::{
|
||||||
parse_macro_input,
|
parse_macro_input, spanned::Spanned, Attribute, AttributeArgs, Data, DataEnum, DataStruct, DeriveInput, Error, Field,
|
||||||
spanned::Spanned,
|
Fields, GenericParam, Generics, Lit, LitStr, Meta, NestedMeta, Result, Variant
|
||||||
Attribute,
|
|
||||||
AttributeArgs,
|
|
||||||
Data,
|
|
||||||
DataEnum,
|
|
||||||
DataStruct,
|
|
||||||
DeriveInput,
|
|
||||||
Error,
|
|
||||||
Field,
|
|
||||||
Fields,
|
|
||||||
Generics,
|
|
||||||
GenericParam,
|
|
||||||
Lit,
|
|
||||||
LitStr,
|
|
||||||
Meta,
|
|
||||||
NestedMeta,
|
|
||||||
Result,
|
|
||||||
Variant
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn expand_openapi_type(input : DeriveInput) -> Result<TokenStream>
|
pub fn expand_openapi_type(input: DeriveInput) -> Result<TokenStream> {
|
||||||
{
|
|
||||||
match (input.ident, input.generics, input.attrs, input.data) {
|
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::Enum(inum)) => expand_enum(ident, generics, attrs, inum),
|
||||||
(ident, generics, attrs, Data::Struct(strukt)) => expand_struct(ident, generics, attrs, strukt),
|
(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
|
fn expand_where(generics: &Generics) -> TokenStream {
|
||||||
{
|
if generics.params.is_empty() {
|
||||||
if generics.params.is_empty()
|
|
||||||
{
|
|
||||||
return quote!();
|
return quote!();
|
||||||
}
|
}
|
||||||
|
|
||||||
let krate = super::krate();
|
let krate = super::krate();
|
||||||
let idents = generics.params.iter()
|
let idents = generics
|
||||||
|
.params
|
||||||
|
.iter()
|
||||||
.map(|param| match param {
|
.map(|param| match param {
|
||||||
GenericParam::Type(ty) => Some(ty.ident.clone()),
|
GenericParam::Type(ty) => Some(ty.ident.clone()),
|
||||||
_ => None
|
_ => None
|
||||||
|
@ -54,46 +39,39 @@ fn expand_where(generics : &Generics) -> TokenStream
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
struct Attrs
|
struct Attrs {
|
||||||
{
|
nullable: bool,
|
||||||
nullable : bool,
|
rename: Option<String>
|
||||||
rename : Option<String>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_string(lit : &Lit) -> Result<String>
|
fn to_string(lit: &Lit) -> Result<String> {
|
||||||
{
|
|
||||||
match lit {
|
match lit {
|
||||||
Lit::Str(str) => Ok(str.value()),
|
Lit::Str(str) => Ok(str.value()),
|
||||||
_ => Err(Error::new(lit.span(), "Expected string literal"))
|
_ => Err(Error::new(lit.span(), "Expected string literal"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_bool(lit : &Lit) -> Result<bool>
|
fn to_bool(lit: &Lit) -> Result<bool> {
|
||||||
{
|
|
||||||
match lit {
|
match lit {
|
||||||
Lit::Bool(bool) => Ok(bool.value),
|
Lit::Bool(bool) => Ok(bool.value),
|
||||||
_ => Err(Error::new(lit.span(), "Expected bool"))
|
_ => Err(Error::new(lit.span(), "Expected bool"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_attributes(input : &[Attribute]) -> Result<Attrs>
|
fn parse_attributes(input: &[Attribute]) -> Result<Attrs> {
|
||||||
{
|
|
||||||
let mut parsed = Attrs::default();
|
let mut parsed = Attrs::default();
|
||||||
for attr in input
|
for attr in input {
|
||||||
{
|
if attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("openapi".to_owned()) {
|
||||||
if attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("openapi".to_owned())
|
|
||||||
{
|
|
||||||
let tokens = remove_parens(attr.tokens.clone());
|
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
|
// TODO this is not public api but syn currently doesn't offer another convenient way to parse AttributeArgs
|
||||||
let nested = parse_macro_input::parse::<AttributeArgs>(tokens.into())?;
|
let nested = parse_macro_input::parse::<AttributeArgs>(tokens.into())?;
|
||||||
for meta in nested
|
for meta in nested {
|
||||||
{
|
|
||||||
match &meta {
|
match &meta {
|
||||||
NestedMeta::Meta(Meta::NameValue(kv)) => match kv.path.segments.last().map(|s| s.ident.to_string()) {
|
NestedMeta::Meta(Meta::NameValue(kv)) => match kv.path.segments.last().map(|s| s.ident.to_string()) {
|
||||||
Some(key) => match key.as_ref() {
|
Some(key) => match key.as_ref() {
|
||||||
"nullable" => parsed.nullable = to_bool(&kv.lit)?,
|
"nullable" => parsed.nullable = to_bool(&kv.lit)?,
|
||||||
"rename" => parsed.rename = Some(to_string(&kv.lit)?),
|
"rename" => parsed.rename = Some(to_string(&kv.lit)?),
|
||||||
_ => return Err(Error::new(kv.path.span(), "Unknown key")),
|
_ => return Err(Error::new(kv.path.span(), "Unknown key"))
|
||||||
},
|
},
|
||||||
_ => return Err(Error::new(meta.span(), "Unexpected token"))
|
_ => return Err(Error::new(meta.span(), "Unexpected token"))
|
||||||
},
|
},
|
||||||
|
@ -105,11 +83,12 @@ fn parse_attributes(input : &[Attribute]) -> Result<Attrs>
|
||||||
Ok(parsed)
|
Ok(parsed)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expand_variant(variant : &Variant) -> Result<TokenStream>
|
fn expand_variant(variant: &Variant) -> Result<TokenStream> {
|
||||||
{
|
if variant.fields != Fields::Unit {
|
||||||
if variant.fields != Fields::Unit
|
return Err(Error::new(
|
||||||
{
|
variant.span(),
|
||||||
return Err(Error::new(variant.span(), "#[derive(OpenapiType)] does not support enum variants with fields"));
|
"#[derive(OpenapiType)] does not support enum variants with fields"
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let ident = &variant.ident;
|
let ident = &variant.ident;
|
||||||
|
@ -125,8 +104,7 @@ fn expand_variant(variant : &Variant) -> Result<TokenStream>
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expand_enum(ident : Ident, generics : Generics, attrs : Vec<Attribute>, input : DataEnum) -> Result<TokenStream>
|
fn expand_enum(ident: Ident, generics: Generics, attrs: Vec<Attribute>, input: DataEnum) -> Result<TokenStream> {
|
||||||
{
|
|
||||||
let krate = super::krate();
|
let krate = super::krate();
|
||||||
let where_clause = expand_where(&generics);
|
let where_clause = expand_where(&generics);
|
||||||
|
|
||||||
|
@ -137,9 +115,7 @@ fn expand_enum(ident : Ident, generics : Generics, attrs : Vec<Attribute>, input
|
||||||
None => ident.to_string()
|
None => ident.to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
let variants = input.variants.iter()
|
let variants = input.variants.iter().map(expand_variant).collect_to_result()?;
|
||||||
.map(expand_variant)
|
|
||||||
.collect_to_result()?;
|
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
impl #generics #krate::OpenapiType for #ident #generics
|
impl #generics #krate::OpenapiType for #ident #generics
|
||||||
|
@ -170,11 +146,15 @@ fn expand_enum(ident : Ident, generics : Generics, attrs : Vec<Attribute>, input
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expand_field(field : &Field) -> Result<TokenStream>
|
fn expand_field(field: &Field) -> Result<TokenStream> {
|
||||||
{
|
|
||||||
let ident = match &field.ident {
|
let ident = match &field.ident {
|
||||||
Some(ident) => 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 ident_str = LitStr::new(&ident.to_string(), ident.span());
|
||||||
let ty = &field.ty;
|
let ty = &field.ty;
|
||||||
|
@ -226,8 +206,7 @@ fn expand_field(field : &Field) -> Result<TokenStream>
|
||||||
}})
|
}})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expand_struct(ident : Ident, generics : Generics, attrs : Vec<Attribute>, input : DataStruct) -> Result<TokenStream>
|
fn expand_struct(ident: Ident, generics: Generics, attrs: Vec<Attribute>, input: DataStruct) -> Result<TokenStream> {
|
||||||
{
|
|
||||||
let krate = super::krate();
|
let krate = super::krate();
|
||||||
let where_clause = expand_where(&generics);
|
let where_clause = expand_where(&generics);
|
||||||
|
|
||||||
|
@ -238,17 +217,18 @@ fn expand_struct(ident : Ident, generics : Generics, attrs : Vec<Attribute>, inp
|
||||||
None => ident.to_string()
|
None => ident.to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
let fields : Vec<TokenStream> = match input.fields {
|
let fields: Vec<TokenStream> = match input.fields {
|
||||||
Fields::Named(named_fields) => {
|
Fields::Named(named_fields) => named_fields.named.iter().map(expand_field).collect_to_result()?,
|
||||||
named_fields.named.iter()
|
Fields::Unnamed(fields) => {
|
||||||
.map(expand_field)
|
return Err(Error::new(
|
||||||
.collect_to_result()?
|
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()
|
Fields::Unit => Vec::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(quote!{
|
Ok(quote! {
|
||||||
impl #generics #krate::OpenapiType for #ident #generics
|
impl #generics #krate::OpenapiType for #ident #generics
|
||||||
#where_clause
|
#where_clause
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,34 +6,25 @@ use syn::{
|
||||||
parse::{Parse, ParseStream},
|
parse::{Parse, ParseStream},
|
||||||
punctuated::Punctuated,
|
punctuated::Punctuated,
|
||||||
spanned::Spanned,
|
spanned::Spanned,
|
||||||
DeriveInput,
|
DeriveInput, Error, Generics, Path, Result, Token
|
||||||
Error,
|
|
||||||
Generics,
|
|
||||||
Path,
|
|
||||||
Result,
|
|
||||||
Token
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MimeList(Punctuated<Path, Token![,]>);
|
struct MimeList(Punctuated<Path, Token![,]>);
|
||||||
|
|
||||||
impl Parse for MimeList
|
impl Parse for MimeList {
|
||||||
{
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
fn parse(input: ParseStream) -> Result<Self>
|
|
||||||
{
|
|
||||||
let list = Punctuated::parse_separated_nonempty(&input)?;
|
let list = Punctuated::parse_separated_nonempty(&input)?;
|
||||||
Ok(Self(list))
|
Ok(Self(list))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "openapi"))]
|
#[cfg(not(feature = "openapi"))]
|
||||||
fn impl_openapi_type(_ident : &Ident, _generics : &Generics) -> TokenStream
|
fn impl_openapi_type(_ident: &Ident, _generics: &Generics) -> TokenStream {
|
||||||
{
|
|
||||||
quote!()
|
quote!()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "openapi")]
|
#[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();
|
let krate = super::krate();
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
|
@ -52,20 +43,26 @@ fn impl_openapi_type(ident : &Ident, generics : &Generics) -> TokenStream
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expand_request_body(input : DeriveInput) -> Result<TokenStream>
|
pub fn expand_request_body(input: DeriveInput) -> Result<TokenStream> {
|
||||||
{
|
|
||||||
let krate = super::krate();
|
let krate = super::krate();
|
||||||
let ident = input.ident;
|
let ident = input.ident;
|
||||||
let generics = input.generics;
|
let generics = input.generics;
|
||||||
|
|
||||||
let types = input.attrs.into_iter()
|
let types = input
|
||||||
.filter(|attr| attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("supported_types".to_string()))
|
.attrs
|
||||||
|
.into_iter()
|
||||||
|
.filter(|attr| {
|
||||||
|
attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("supported_types".to_string())
|
||||||
|
})
|
||||||
.flat_map(|attr| {
|
.flat_map(|attr| {
|
||||||
let span = attr.span();
|
let span = attr.span();
|
||||||
attr.parse_args::<MimeList>()
|
attr.parse_args::<MimeList>()
|
||||||
.map(|list| Box::new(list.0.into_iter().map(Ok)) as Box<dyn Iterator<Item = Result<Path>>>)
|
.map(|list| Box::new(list.0.into_iter().map(Ok)) as Box<dyn Iterator<Item = Result<Path>>>)
|
||||||
.unwrap_or_else(|mut err| {
|
.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)))
|
Box::new(iter::once(Err(err)))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,23 +1,18 @@
|
||||||
use crate::{method::Method, util::CollectToResult};
|
use crate::{method::Method, util::CollectToResult};
|
||||||
use proc_macro2::{Ident, TokenStream};
|
use proc_macro2::{Ident, TokenStream};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
use std::{iter, str::FromStr};
|
||||||
use syn::{
|
use syn::{
|
||||||
parenthesized,
|
parenthesized,
|
||||||
parse::{Parse, ParseStream},
|
parse::{Parse, ParseStream},
|
||||||
punctuated::Punctuated,
|
punctuated::Punctuated,
|
||||||
DeriveInput,
|
DeriveInput, Error, Result, Token
|
||||||
Error,
|
|
||||||
Result,
|
|
||||||
Token
|
|
||||||
};
|
};
|
||||||
use std::{iter, str::FromStr};
|
|
||||||
|
|
||||||
struct MethodList(Punctuated<Ident, Token![,]>);
|
struct MethodList(Punctuated<Ident, Token![,]>);
|
||||||
|
|
||||||
impl Parse for MethodList
|
impl Parse for MethodList {
|
||||||
{
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
fn parse(input: ParseStream) -> Result<Self>
|
|
||||||
{
|
|
||||||
let content;
|
let content;
|
||||||
let _paren = parenthesized!(content in input);
|
let _paren = parenthesized!(content in input);
|
||||||
let list = Punctuated::parse_separated_nonempty(&content)?;
|
let list = Punctuated::parse_separated_nonempty(&content)?;
|
||||||
|
@ -25,25 +20,31 @@ impl Parse for MethodList
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expand_resource(input : DeriveInput) -> Result<TokenStream>
|
pub fn expand_resource(input: DeriveInput) -> Result<TokenStream> {
|
||||||
{
|
|
||||||
let krate = super::krate();
|
let krate = super::krate();
|
||||||
let ident = input.ident;
|
let ident = input.ident;
|
||||||
let name = ident.to_string();
|
let name = ident.to_string();
|
||||||
|
|
||||||
let methods = input.attrs.into_iter().filter(|attr|
|
let methods =
|
||||||
attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("resource".to_string()) // TODO wtf
|
input
|
||||||
).map(|attr| {
|
.attrs
|
||||||
syn::parse2(attr.tokens).map(|m : MethodList| m.0.into_iter())
|
.into_iter()
|
||||||
}).flat_map(|list| match list {
|
.filter(
|
||||||
Ok(iter) => Box::new(iter.map(|method| {
|
|attr| {
|
||||||
let method = Method::from_str(&method.to_string()).map_err(|err| Error::new(method.span(), err))?;
|
attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("resource".to_string())
|
||||||
let mod_ident = method.mod_ident(&name);
|
} // TODO wtf
|
||||||
let ident = method.setup_ident(&name);
|
)
|
||||||
Ok(quote!(#mod_ident::#ident(&mut route);))
|
.map(|attr| syn::parse2(attr.tokens).map(|m: MethodList| m.0.into_iter()))
|
||||||
})) as Box<dyn Iterator<Item = Result<TokenStream>>>,
|
.flat_map(|list| match list {
|
||||||
Err(err) => Box::new(iter::once(Err(err)))
|
Ok(iter) => Box::new(iter.map(|method| {
|
||||||
}).collect_to_result()?;
|
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<dyn Iterator<Item = Result<TokenStream>>>,
|
||||||
|
Err(err) => Box::new(iter::once(Err(err)))
|
||||||
|
})
|
||||||
|
.collect_to_result()?;
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
impl #krate::Resource for #ident
|
impl #krate::Resource for #ident
|
||||||
|
|
|
@ -1,68 +1,54 @@
|
||||||
use crate::util::{CollectToResult, remove_parens};
|
use crate::util::{remove_parens, CollectToResult};
|
||||||
use proc_macro2::{Ident, TokenStream};
|
use proc_macro2::{Ident, TokenStream};
|
||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote};
|
||||||
use std::iter;
|
use std::iter;
|
||||||
use syn::{
|
use syn::{
|
||||||
spanned::Spanned,
|
spanned::Spanned, Attribute, Data, DeriveInput, Error, Fields, GenericParam, LitStr, Path, PathSegment, Result, Type,
|
||||||
Attribute,
|
|
||||||
Data,
|
|
||||||
DeriveInput,
|
|
||||||
Error,
|
|
||||||
Fields,
|
|
||||||
GenericParam,
|
|
||||||
LitStr,
|
|
||||||
Path,
|
|
||||||
PathSegment,
|
|
||||||
Result,
|
|
||||||
Type,
|
|
||||||
Variant
|
Variant
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ErrorVariantField {
|
||||||
struct ErrorVariantField
|
attrs: Vec<Attribute>,
|
||||||
{
|
ident: Ident,
|
||||||
attrs : Vec<Attribute>,
|
ty: Type
|
||||||
ident : Ident,
|
|
||||||
ty : Type
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ErrorVariant
|
struct ErrorVariant {
|
||||||
{
|
ident: Ident,
|
||||||
ident : Ident,
|
status: Option<Path>,
|
||||||
status : Option<Path>,
|
is_named: bool,
|
||||||
is_named : bool,
|
fields: Vec<ErrorVariantField>,
|
||||||
fields : Vec<ErrorVariantField>,
|
from_ty: Option<(usize, Type)>,
|
||||||
from_ty : Option<(usize, Type)>,
|
display: Option<LitStr>
|
||||||
display : Option<LitStr>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_variant(variant : Variant) -> Result<ErrorVariant>
|
fn process_variant(variant: Variant) -> Result<ErrorVariant> {
|
||||||
{
|
let status =
|
||||||
let status = match variant.attrs.iter()
|
match variant.attrs.iter().find(|attr| {
|
||||||
.find(|attr| attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("status".to_string()))
|
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()))?),
|
Some(attr) => Some(syn::parse2(remove_parens(attr.tokens.clone()))?),
|
||||||
None => None
|
None => None
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut is_named = false;
|
let mut is_named = false;
|
||||||
let mut fields = Vec::new();
|
let mut fields = Vec::new();
|
||||||
match variant.fields {
|
match variant.fields {
|
||||||
Fields::Named(named) => {
|
Fields::Named(named) => {
|
||||||
is_named = true;
|
is_named = true;
|
||||||
for field in named.named
|
for field in named.named {
|
||||||
{
|
|
||||||
let span = field.span();
|
let span = field.span();
|
||||||
fields.push(ErrorVariantField {
|
fields.push(ErrorVariantField {
|
||||||
attrs: field.attrs,
|
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
|
ty: field.ty
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Fields::Unnamed(unnamed) => {
|
Fields::Unnamed(unnamed) => {
|
||||||
for (i, field) in unnamed.unnamed.into_iter().enumerate()
|
for (i, field) in unnamed.unnamed.into_iter().enumerate() {
|
||||||
{
|
|
||||||
fields.push(ErrorVariantField {
|
fields.push(ErrorVariantField {
|
||||||
attrs: field.attrs,
|
attrs: field.attrs,
|
||||||
ident: format_ident!("arg{}", i),
|
ident: format_ident!("arg{}", i),
|
||||||
|
@ -73,14 +59,20 @@ fn process_variant(variant : Variant) -> Result<ErrorVariant>
|
||||||
Fields::Unit => {}
|
Fields::Unit => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
let from_ty = fields.iter()
|
let from_ty = fields
|
||||||
|
.iter()
|
||||||
.enumerate()
|
.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()));
|
.map(|(i, field)| (i, field.ty.clone()));
|
||||||
|
|
||||||
let display = match variant.attrs.iter()
|
let display = match variant.attrs.iter().find(|attr| {
|
||||||
.find(|attr| attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("display".to_string()))
|
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()))?),
|
Some(attr) => Some(syn::parse2(remove_parens(attr.tokens.clone()))?),
|
||||||
None => None
|
None => None
|
||||||
};
|
};
|
||||||
|
@ -95,18 +87,15 @@ fn process_variant(variant : Variant) -> Result<ErrorVariant>
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path_segment(name : &str) -> PathSegment
|
fn path_segment(name: &str) -> PathSegment {
|
||||||
{
|
|
||||||
PathSegment {
|
PathSegment {
|
||||||
ident: format_ident!("{}", name),
|
ident: format_ident!("{}", name),
|
||||||
arguments: Default::default()
|
arguments: Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ErrorVariant
|
impl ErrorVariant {
|
||||||
{
|
fn fields_pat(&self) -> TokenStream {
|
||||||
fn fields_pat(&self) -> TokenStream
|
|
||||||
{
|
|
||||||
let mut fields = self.fields.iter().map(|field| &field.ident).peekable();
|
let mut fields = self.fields.iter().map(|field| &field.ident).peekable();
|
||||||
if fields.peek().is_none() {
|
if fields.peek().is_none() {
|
||||||
quote!()
|
quote!()
|
||||||
|
@ -117,49 +106,58 @@ impl ErrorVariant
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_display_match_arm(&self, formatter_ident : &Ident, enum_ident : &Ident) -> Result<TokenStream>
|
fn to_display_match_arm(&self, formatter_ident: &Ident, enum_ident: &Ident) -> Result<TokenStream> {
|
||||||
{
|
|
||||||
let ident = &self.ident;
|
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
|
// lets find all required format parameters
|
||||||
let display_str = display.value();
|
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 len = display_str.len();
|
||||||
let mut start = len;
|
let mut start = len;
|
||||||
let mut iter = display_str.chars().enumerate().peekable();
|
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
|
// we found a new opening brace
|
||||||
if start == len && c == '{'
|
if start == len && c == '{' {
|
||||||
{
|
|
||||||
start = i + 1;
|
start = i + 1;
|
||||||
}
|
}
|
||||||
// we found a duplicate opening brace
|
// we found a duplicate opening brace
|
||||||
else if start == i && c == '{'
|
else if start == i && c == '{' {
|
||||||
{
|
|
||||||
start = len;
|
start = len;
|
||||||
}
|
}
|
||||||
// we found a closing brace
|
// we found a closing brace
|
||||||
else if start < i && c == '}'
|
else if start < i && c == '}' {
|
||||||
{
|
|
||||||
match iter.peek() {
|
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])
|
_ => params.push(&display_str[start..i])
|
||||||
};
|
};
|
||||||
start = len;
|
start = len;
|
||||||
}
|
}
|
||||||
// we found a closing brace without content
|
// we found a closing brace without content
|
||||||
else if start == i && c == '}'
|
else if start == i && c == '}' {
|
||||||
{
|
return Err(Error::new(
|
||||||
return Err(Error::new(display.span(), "Error parsing format string: parameter name must not be empty"))
|
display.span(),
|
||||||
|
"Error parsing format string: parameter name must not be empty"
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if start != len
|
if start != len {
|
||||||
{
|
return Err(Error::new(
|
||||||
return Err(Error::new(display.span(), "Error parsing format string: Unmatched opening brace"));
|
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();
|
let fields_pat = self.fields_pat();
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
|
@ -167,21 +165,28 @@ impl ErrorVariant
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_match_arm(self, krate : &TokenStream, enum_ident : &Ident) -> Result<TokenStream>
|
fn into_match_arm(self, krate: &TokenStream, enum_ident: &Ident) -> Result<TokenStream> {
|
||||||
{
|
|
||||||
let ident = &self.ident;
|
let ident = &self.ident;
|
||||||
let fields_pat = self.fields_pat();
|
let fields_pat = self.fields_pat();
|
||||||
let status = self.status.map(|status| {
|
let status = self.status.map(|status| {
|
||||||
// the status might be relative to StatusCode, so let's fix that
|
// 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"));
|
let status_ident = status.segments.first().cloned().unwrap_or_else(|| path_segment("OK"));
|
||||||
Path {
|
Path {
|
||||||
leading_colon: Some(Default::default()),
|
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
|
// the response will come directly from the from_ty if present
|
||||||
|
@ -204,8 +209,7 @@ impl ErrorVariant
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn were(&self) -> Option<TokenStream>
|
fn were(&self) -> Option<TokenStream> {
|
||||||
{
|
|
||||||
match self.from_ty.as_ref() {
|
match self.from_ty.as_ref() {
|
||||||
Some((_, ty)) => Some(quote!( #ty : ::std::error::Error )),
|
Some((_, ty)) => Some(quote!( #ty : ::std::error::Error )),
|
||||||
None => None
|
None => None
|
||||||
|
@ -213,8 +217,7 @@ impl ErrorVariant
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expand_resource_error(input : DeriveInput) -> Result<TokenStream>
|
pub fn expand_resource_error(input: DeriveInput) -> Result<TokenStream> {
|
||||||
{
|
|
||||||
let krate = super::krate();
|
let krate = super::krate();
|
||||||
let ident = input.ident;
|
let ident = input.ident;
|
||||||
let generics = input.generics;
|
let generics = input.generics;
|
||||||
|
@ -223,12 +226,13 @@ pub fn expand_resource_error(input : DeriveInput) -> Result<TokenStream>
|
||||||
Data::Enum(inum) => Ok(inum),
|
Data::Enum(inum) => Ok(inum),
|
||||||
Data::Struct(strukt) => Err(strukt.struct_token.span()),
|
Data::Struct(strukt) => Err(strukt.struct_token.span()),
|
||||||
Data::Union(uni) => Err(uni.union_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_err(|span| Error::new(span, "#[derive(ResourceError)] only works for enums"))?;
|
||||||
.map(process_variant)
|
let variants = inum.variants.into_iter().map(process_variant).collect_to_result()?;
|
||||||
.collect_to_result()?;
|
|
||||||
|
|
||||||
let display_impl = if variants.iter().any(|v| v.display.is_none()) { None } else {
|
let display_impl = if variants.iter().any(|v| v.display.is_none()) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
let were = generics.params.iter().filter_map(|param| match param {
|
let were = generics.params.iter().filter_map(|param| match param {
|
||||||
GenericParam::Type(ty) => {
|
GenericParam::Type(ty) => {
|
||||||
let ident = &ty.ident;
|
let ident = &ty.ident;
|
||||||
|
@ -237,7 +241,8 @@ pub fn expand_resource_error(input : DeriveInput) -> Result<TokenStream>
|
||||||
_ => None
|
_ => None
|
||||||
});
|
});
|
||||||
let formatter_ident = format_ident!("resource_error_display_formatter");
|
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))
|
.map(|v| v.to_display_match_arm(&formatter_ident, &ident))
|
||||||
.collect_to_result()?;
|
.collect_to_result()?;
|
||||||
Some(quote! {
|
Some(quote! {
|
||||||
|
@ -254,10 +259,9 @@ pub fn expand_resource_error(input : DeriveInput) -> Result<TokenStream>
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut from_impls : Vec<TokenStream> = Vec::new();
|
let mut from_impls: Vec<TokenStream> = Vec::new();
|
||||||
|
|
||||||
for var in &variants
|
for var in &variants {
|
||||||
{
|
|
||||||
let var_ident = &var.ident;
|
let var_ident = &var.ident;
|
||||||
let (from_index, from_ty) = match var.from_ty.as_ref() {
|
let (from_index, from_ty) = match var.from_ty.as_ref() {
|
||||||
Some(f) => f,
|
Some(f) => f,
|
||||||
|
@ -266,14 +270,20 @@ pub fn expand_resource_error(input : DeriveInput) -> Result<TokenStream>
|
||||||
let from_ident = &var.fields[*from_index].ident;
|
let from_ident = &var.fields[*from_index].ident;
|
||||||
|
|
||||||
let fields_pat = var.fields_pat();
|
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)
|
.filter(|(i, _)| i != from_index)
|
||||||
.map(|(_, field)| {
|
.map(|(_, field)| {
|
||||||
let ty = &field.ty;
|
let ty = &field.ty;
|
||||||
quote!( #ty : Default )
|
quote!( #ty : Default )
|
||||||
})
|
})
|
||||||
.chain(iter::once(quote!( #from_ty : ::std::error::Error )));
|
.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)
|
.filter(|(i, _)| i != from_index)
|
||||||
.map(|(_, field)| {
|
.map(|(_, field)| {
|
||||||
let id = &field.ident;
|
let id = &field.ident;
|
||||||
|
@ -295,9 +305,10 @@ pub fn expand_resource_error(input : DeriveInput) -> Result<TokenStream>
|
||||||
}
|
}
|
||||||
|
|
||||||
let were = variants.iter().filter_map(|variant| variant.were()).collect::<Vec<_>>();
|
let were = variants.iter().filter_map(|variant| variant.were()).collect::<Vec<_>>();
|
||||||
let variants = variants.into_iter()
|
let variants = variants
|
||||||
|
.into_iter()
|
||||||
.map(|variant| variant.into_match_arm(&krate, &ident))
|
.map(|variant| variant.into_match_arm(&krate, &ident))
|
||||||
.collect_to_result()?;
|
.collect_to_result()?;
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
#display_impl
|
#display_impl
|
||||||
|
|
|
@ -2,8 +2,7 @@ use proc_macro2::{Delimiter, TokenStream, TokenTree};
|
||||||
use std::iter;
|
use std::iter;
|
||||||
use syn::Error;
|
use syn::Error;
|
||||||
|
|
||||||
pub trait CollectToResult
|
pub trait CollectToResult {
|
||||||
{
|
|
||||||
type Item;
|
type Item;
|
||||||
|
|
||||||
fn collect_to_result(self) -> Result<Vec<Self::Item>, Error>;
|
fn collect_to_result(self) -> Result<Vec<Self::Item>, Error>;
|
||||||
|
@ -11,31 +10,30 @@ pub trait CollectToResult
|
||||||
|
|
||||||
impl<Item, I> CollectToResult for I
|
impl<Item, I> CollectToResult for I
|
||||||
where
|
where
|
||||||
I : Iterator<Item = Result<Item, Error>>
|
I: Iterator<Item = Result<Item, Error>>
|
||||||
{
|
{
|
||||||
type Item = Item;
|
type Item = Item;
|
||||||
|
|
||||||
fn collect_to_result(self) -> Result<Vec<Item>, Error>
|
fn collect_to_result(self) -> Result<Vec<Item>, Error> {
|
||||||
{
|
self.fold(<Result<Vec<Item>, Error>>::Ok(Vec::new()), |res, code| match (code, res) {
|
||||||
self.fold(<Result<Vec<Item>, Error>>::Ok(Vec::new()), |res, code| {
|
(Ok(code), Ok(mut codes)) => {
|
||||||
match (code, res) {
|
codes.push(code);
|
||||||
(Ok(code), Ok(mut codes)) => { codes.push(code); Ok(codes) },
|
Ok(codes)
|
||||||
(Ok(_), Err(errors)) => Err(errors),
|
},
|
||||||
(Err(err), Ok(_)) => Err(err),
|
(Ok(_), Err(errors)) => Err(errors),
|
||||||
(Err(err), Err(mut errors)) => { errors.combine(err); 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| {
|
let iter = input.into_iter().flat_map(|tt| {
|
||||||
if let TokenTree::Group(group) = &tt
|
if let TokenTree::Group(group) = &tt {
|
||||||
{
|
if group.delimiter() == Delimiter::Parenthesis {
|
||||||
if group.delimiter() == Delimiter::Parenthesis
|
|
||||||
{
|
|
||||||
return Box::new(group.stream().into_iter()) as Box<dyn Iterator<Item = TokenTree>>;
|
return Box::new(group.stream().into_iter()) as Box<dyn Iterator<Item = TokenTree>>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#[macro_use] extern crate gotham_derive;
|
#[macro_use]
|
||||||
#[macro_use] extern crate log;
|
extern crate gotham_derive;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
use fake::{faker::internet::en::Username, Fake};
|
use fake::{faker::internet::en::Username, Fake};
|
||||||
use gotham::{
|
use gotham::{
|
||||||
|
@ -20,25 +22,19 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
#[resource(read_all, read, search, create, change_all, change, remove, remove_all)]
|
#[resource(read_all, read, search, create, change_all, change, remove, remove_all)]
|
||||||
struct Users
|
struct Users {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
#[resource(ReadAll)]
|
#[resource(ReadAll)]
|
||||||
struct Auth
|
struct Auth {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, OpenapiType, Serialize, StateData, StaticResponseExtender)]
|
#[derive(Deserialize, OpenapiType, Serialize, StateData, StaticResponseExtender)]
|
||||||
struct User
|
struct User {
|
||||||
{
|
username: String
|
||||||
username : String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[read_all(Users)]
|
#[read_all(Users)]
|
||||||
fn read_all() -> Success<Vec<Option<User>>>
|
fn read_all() -> Success<Vec<Option<User>>> {
|
||||||
{
|
|
||||||
vec![Username().fake(), Username().fake()]
|
vec![Username().fake(), Username().fake()]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|username| Some(User { username }))
|
.map(|username| Some(User { username }))
|
||||||
|
@ -47,80 +43,72 @@ fn read_all() -> Success<Vec<Option<User>>>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[read(Users)]
|
#[read(Users)]
|
||||||
fn read(id : u64) -> Success<User>
|
fn read(id: u64) -> Success<User> {
|
||||||
{
|
let username: String = Username().fake();
|
||||||
let username : String = Username().fake();
|
User {
|
||||||
User { username: format!("{}{}", username, id) }.into()
|
username: format!("{}{}", username, id)
|
||||||
|
}
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[search(Users)]
|
#[search(Users)]
|
||||||
fn search(query : User) -> Success<User>
|
fn search(query: User) -> Success<User> {
|
||||||
{
|
|
||||||
query.into()
|
query.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[create(Users)]
|
#[create(Users)]
|
||||||
fn create(body : User)
|
fn create(body: User) {
|
||||||
{
|
|
||||||
info!("Created User: {}", body.username);
|
info!("Created User: {}", body.username);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[change_all(Users)]
|
#[change_all(Users)]
|
||||||
fn update_all(body : Vec<User>)
|
fn update_all(body: Vec<User>) {
|
||||||
{
|
info!(
|
||||||
info!("Changing all Users to {:?}", body.into_iter().map(|u| u.username).collect::<Vec<String>>());
|
"Changing all Users to {:?}",
|
||||||
|
body.into_iter().map(|u| u.username).collect::<Vec<String>>()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[change(Users)]
|
#[change(Users)]
|
||||||
fn update(id : u64, body : User)
|
fn update(id: u64, body: User) {
|
||||||
{
|
|
||||||
info!("Change User {} to {}", id, body.username);
|
info!("Change User {} to {}", id, body.username);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[remove_all(Users)]
|
#[remove_all(Users)]
|
||||||
fn remove_all()
|
fn remove_all() {
|
||||||
{
|
|
||||||
info!("Delete all Users");
|
info!("Delete all Users");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[remove(Users)]
|
#[remove(Users)]
|
||||||
fn remove(id : u64)
|
fn remove(id: u64) {
|
||||||
{
|
|
||||||
info!("Delete User {}", id);
|
info!("Delete User {}", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[read_all(Auth)]
|
#[read_all(Auth)]
|
||||||
fn auth_read_all(auth : AuthStatus<()>) -> AuthSuccess<String>
|
fn auth_read_all(auth: AuthStatus<()>) -> AuthSuccess<String> {
|
||||||
{
|
|
||||||
match auth {
|
match auth {
|
||||||
AuthStatus::Authenticated(data) => Ok(format!("{:?}", data)),
|
AuthStatus::Authenticated(data) => Ok(format!("{:?}", data)),
|
||||||
_ => Err(Forbidden)
|
_ => Err(Forbidden)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ADDR : &str = "127.0.0.1:18080";
|
const ADDR: &str = "127.0.0.1:18080";
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
struct Handler;
|
struct Handler;
|
||||||
impl<T> AuthHandler<T> for Handler
|
impl<T> AuthHandler<T> for Handler {
|
||||||
{
|
fn jwt_secret<F: FnOnce() -> Option<T>>(&self, _state: &mut State, _decode_data: F) -> Option<Vec<u8>> {
|
||||||
fn jwt_secret<F : FnOnce() -> Option<T>>(&self, _state : &mut State, _decode_data : F) -> Option<Vec<u8>>
|
|
||||||
{
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main()
|
fn main() {
|
||||||
{
|
|
||||||
let encoder = PatternEncoder::new("{d(%Y-%m-%d %H:%M:%S%.3f %Z)} [{l}] {M} - {m}\n");
|
let encoder = PatternEncoder::new("{d(%Y-%m-%d %H:%M:%S%.3f %Z)} [{l}] {M} - {m}\n");
|
||||||
let config = Config::builder()
|
let config = Config::builder()
|
||||||
.appender(
|
.appender(Appender::builder().build(
|
||||||
Appender::builder()
|
"stdout",
|
||||||
.build("stdout", Box::new(
|
Box::new(ConsoleAppender::builder().encoder(Box::new(encoder)).build())
|
||||||
ConsoleAppender::builder()
|
))
|
||||||
.encoder(Box::new(encoder))
|
|
||||||
.build()
|
|
||||||
)))
|
|
||||||
.build(Root::builder().appender("stdout").build(LevelFilter::Info))
|
.build(Root::builder().appender("stdout").build(LevelFilter::Info))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
log4rs::init_config(config).unwrap();
|
log4rs::init_config(config).unwrap();
|
||||||
|
@ -134,26 +122,22 @@ fn main()
|
||||||
|
|
||||||
let auth = <AuthMiddleware<(), Handler>>::from_source(AuthSource::AuthorizationHeader);
|
let auth = <AuthMiddleware<(), Handler>>::from_source(AuthSource::AuthorizationHeader);
|
||||||
let logging = RequestLogger::new(log::Level::Info);
|
let logging = RequestLogger::new(log::Level::Info);
|
||||||
let (chain, pipelines) = single_pipeline(
|
let (chain, pipelines) = single_pipeline(new_pipeline().add(auth).add(logging).add(cors).build());
|
||||||
new_pipeline()
|
|
||||||
.add(auth)
|
|
||||||
.add(logging)
|
|
||||||
.add(cors)
|
|
||||||
.build()
|
|
||||||
);
|
|
||||||
|
|
||||||
gotham::start(ADDR, build_router(chain, pipelines, |route| {
|
gotham::start(
|
||||||
let info = OpenapiInfo {
|
ADDR,
|
||||||
title: "Users Example".to_owned(),
|
build_router(chain, pipelines, |route| {
|
||||||
version: "0.0.1".to_owned(),
|
let info = OpenapiInfo {
|
||||||
urls: vec![format!("http://{}", ADDR)]
|
title: "Users Example".to_owned(),
|
||||||
};
|
version: "0.0.1".to_owned(),
|
||||||
route.with_openapi(info, |mut route| {
|
urls: vec![format!("http://{}", ADDR)]
|
||||||
route.resource::<Users>("users");
|
};
|
||||||
route.resource::<Auth>("auth");
|
route.with_openapi(info, |mut route| {
|
||||||
route.get_openapi("openapi");
|
route.resource::<Users>("users");
|
||||||
});
|
route.resource::<Auth>("auth");
|
||||||
}));
|
route.get_openapi("openapi");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
println!("Gotham started on {} for testing", ADDR);
|
println!("Gotham started on {} for testing", ADDR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
19
rustfmt.toml
Normal file
19
rustfmt.toml
Normal file
|
@ -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
|
210
src/auth.rs
210
src/auth.rs
|
@ -1,29 +1,24 @@
|
||||||
use crate::{AuthError, Forbidden, HeaderName};
|
use crate::{AuthError, Forbidden, HeaderName};
|
||||||
use cookie::CookieJar;
|
use cookie::CookieJar;
|
||||||
use futures_util::{future, future::{FutureExt, TryFutureExt}};
|
use futures_util::{
|
||||||
|
future,
|
||||||
|
future::{FutureExt, TryFutureExt}
|
||||||
|
};
|
||||||
use gotham::{
|
use gotham::{
|
||||||
handler::HandlerFuture,
|
handler::HandlerFuture,
|
||||||
hyper::header::{AUTHORIZATION, HeaderMap},
|
hyper::header::{HeaderMap, AUTHORIZATION},
|
||||||
middleware::{Middleware, NewMiddleware},
|
middleware::{Middleware, NewMiddleware},
|
||||||
state::{FromState, State}
|
state::{FromState, State}
|
||||||
};
|
};
|
||||||
use jsonwebtoken::{
|
use jsonwebtoken::{errors::ErrorKind, DecodingKey};
|
||||||
errors::ErrorKind,
|
|
||||||
DecodingKey
|
|
||||||
};
|
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use std::{
|
use std::{marker::PhantomData, panic::RefUnwindSafe, pin::Pin};
|
||||||
marker::PhantomData,
|
|
||||||
panic::RefUnwindSafe,
|
|
||||||
pin::Pin
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use jsonwebtoken::Validation as AuthValidation;
|
pub use jsonwebtoken::Validation as AuthValidation;
|
||||||
|
|
||||||
/// The authentication status returned by the auth middleware for each request.
|
/// The authentication status returned by the auth middleware for each request.
|
||||||
#[derive(Debug, StateData)]
|
#[derive(Debug, StateData)]
|
||||||
pub enum AuthStatus<T : Send + 'static>
|
pub enum AuthStatus<T: Send + 'static> {
|
||||||
{
|
|
||||||
/// The auth status is unknown.
|
/// The auth status is unknown.
|
||||||
Unknown,
|
Unknown,
|
||||||
/// The request has been performed without any kind of authentication.
|
/// The request has been performed without any kind of authentication.
|
||||||
|
@ -38,10 +33,9 @@ pub enum AuthStatus<T : Send + 'static>
|
||||||
|
|
||||||
impl<T> Clone for AuthStatus<T>
|
impl<T> Clone for AuthStatus<T>
|
||||||
where
|
where
|
||||||
T : Clone + Send + 'static
|
T: Clone + Send + 'static
|
||||||
{
|
{
|
||||||
fn clone(&self) -> Self
|
fn clone(&self) -> Self {
|
||||||
{
|
|
||||||
match self {
|
match self {
|
||||||
Self::Unknown => Self::Unknown,
|
Self::Unknown => Self::Unknown,
|
||||||
Self::Unauthenticated => Self::Unauthenticated,
|
Self::Unauthenticated => Self::Unauthenticated,
|
||||||
|
@ -52,16 +46,10 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Copy for AuthStatus<T>
|
impl<T> Copy for AuthStatus<T> where T: Copy + Send + 'static {}
|
||||||
where
|
|
||||||
T : Copy + Send + 'static
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T : Send + 'static> AuthStatus<T>
|
impl<T: Send + 'static> AuthStatus<T> {
|
||||||
{
|
pub fn ok(self) -> Result<T, AuthError> {
|
||||||
pub fn ok(self) -> Result<T, AuthError>
|
|
||||||
{
|
|
||||||
match self {
|
match self {
|
||||||
Self::Authenticated(data) => Ok(data),
|
Self::Authenticated(data) => Ok(data),
|
||||||
_ => Err(Forbidden)
|
_ => Err(Forbidden)
|
||||||
|
@ -71,8 +59,7 @@ impl<T : Send + 'static> AuthStatus<T>
|
||||||
|
|
||||||
/// The source of the authentication token in the request.
|
/// The source of the authentication token in the request.
|
||||||
#[derive(Clone, Debug, StateData)]
|
#[derive(Clone, Debug, StateData)]
|
||||||
pub enum AuthSource
|
pub enum AuthSource {
|
||||||
{
|
|
||||||
/// Take the token from a cookie with the given name.
|
/// Take the token from a cookie with the given name.
|
||||||
Cookie(String),
|
Cookie(String),
|
||||||
/// Take the token from a header with the given name.
|
/// Take the token from a header with the given name.
|
||||||
|
@ -100,36 +87,29 @@ impl<T> AuthHandler<T> for CustomAuthHandler {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
pub trait AuthHandler<Data>
|
pub trait AuthHandler<Data> {
|
||||||
{
|
|
||||||
/// Return the SHA256-HMAC secret used to verify the JWT token.
|
/// Return the SHA256-HMAC secret used to verify the JWT token.
|
||||||
fn jwt_secret<F : FnOnce() -> Option<Data>>(&self, state : &mut State, decode_data : F) -> Option<Vec<u8>>;
|
fn jwt_secret<F: FnOnce() -> Option<Data>>(&self, state: &mut State, decode_data: F) -> Option<Vec<u8>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An `AuthHandler` returning always the same secret. See `AuthMiddleware` for a usage example.
|
/// An `AuthHandler` returning always the same secret. See `AuthMiddleware` for a usage example.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct StaticAuthHandler
|
pub struct StaticAuthHandler {
|
||||||
{
|
secret: Vec<u8>
|
||||||
secret : Vec<u8>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StaticAuthHandler
|
impl StaticAuthHandler {
|
||||||
{
|
pub fn from_vec(secret: Vec<u8>) -> Self {
|
||||||
pub fn from_vec(secret : Vec<u8>) -> Self
|
|
||||||
{
|
|
||||||
Self { secret }
|
Self { secret }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_array(secret : &[u8]) -> Self
|
pub fn from_array(secret: &[u8]) -> Self {
|
||||||
{
|
|
||||||
Self::from_vec(secret.to_vec())
|
Self::from_vec(secret.to_vec())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> AuthHandler<T> for StaticAuthHandler
|
impl<T> AuthHandler<T> for StaticAuthHandler {
|
||||||
{
|
fn jwt_secret<F: FnOnce() -> Option<T>>(&self, _state: &mut State, _decode_data: F) -> Option<Vec<u8>> {
|
||||||
fn jwt_secret<F : FnOnce() -> Option<T>>(&self, _state : &mut State, _decode_data : F) -> Option<Vec<u8>>
|
|
||||||
{
|
|
||||||
Some(self.secret.clone())
|
Some(self.secret.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,19 +153,18 @@ fn main() {
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AuthMiddleware<Data, Handler>
|
pub struct AuthMiddleware<Data, Handler> {
|
||||||
{
|
source: AuthSource,
|
||||||
source : AuthSource,
|
validation: AuthValidation,
|
||||||
validation : AuthValidation,
|
handler: Handler,
|
||||||
handler : Handler,
|
_data: PhantomData<Data>
|
||||||
_data : PhantomData<Data>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Data, Handler> Clone for AuthMiddleware<Data, Handler>
|
impl<Data, Handler> Clone for AuthMiddleware<Data, Handler>
|
||||||
where Handler : Clone
|
where
|
||||||
|
Handler: Clone
|
||||||
{
|
{
|
||||||
fn clone(&self) -> Self
|
fn clone(&self) -> Self {
|
||||||
{
|
|
||||||
Self {
|
Self {
|
||||||
source: self.source.clone(),
|
source: self.source.clone(),
|
||||||
validation: self.validation.clone(),
|
validation: self.validation.clone(),
|
||||||
|
@ -197,11 +176,10 @@ where Handler : Clone
|
||||||
|
|
||||||
impl<Data, Handler> AuthMiddleware<Data, Handler>
|
impl<Data, Handler> AuthMiddleware<Data, Handler>
|
||||||
where
|
where
|
||||||
Data : DeserializeOwned + Send,
|
Data: DeserializeOwned + Send,
|
||||||
Handler : AuthHandler<Data> + Default
|
Handler: AuthHandler<Data> + Default
|
||||||
{
|
{
|
||||||
pub fn from_source(source : AuthSource) -> Self
|
pub fn from_source(source: AuthSource) -> Self {
|
||||||
{
|
|
||||||
Self {
|
Self {
|
||||||
source,
|
source,
|
||||||
validation: Default::default(),
|
validation: Default::default(),
|
||||||
|
@ -213,11 +191,10 @@ where
|
||||||
|
|
||||||
impl<Data, Handler> AuthMiddleware<Data, Handler>
|
impl<Data, Handler> AuthMiddleware<Data, Handler>
|
||||||
where
|
where
|
||||||
Data : DeserializeOwned + Send,
|
Data: DeserializeOwned + Send,
|
||||||
Handler : AuthHandler<Data>
|
Handler: AuthHandler<Data>
|
||||||
{
|
{
|
||||||
pub fn new(source : AuthSource, validation : AuthValidation, handler : Handler) -> Self
|
pub fn new(source: AuthSource, validation: AuthValidation, handler: Handler) -> Self {
|
||||||
{
|
|
||||||
Self {
|
Self {
|
||||||
source,
|
source,
|
||||||
validation,
|
validation,
|
||||||
|
@ -226,28 +203,21 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn auth_status(&self, state : &mut State) -> AuthStatus<Data>
|
fn auth_status(&self, state: &mut State) -> AuthStatus<Data> {
|
||||||
{
|
|
||||||
// extract the provided token, if any
|
// extract the provided token, if any
|
||||||
let token = match &self.source {
|
let token = match &self.source {
|
||||||
AuthSource::Cookie(name) => {
|
AuthSource::Cookie(name) => CookieJar::try_borrow_from(&state)
|
||||||
CookieJar::try_borrow_from(&state)
|
.and_then(|jar| jar.get(&name))
|
||||||
.and_then(|jar| jar.get(&name))
|
.map(|cookie| cookie.value().to_owned()),
|
||||||
.map(|cookie| cookie.value().to_owned())
|
AuthSource::Header(name) => HeaderMap::try_borrow_from(&state)
|
||||||
},
|
.and_then(|map| map.get(name))
|
||||||
AuthSource::Header(name) => {
|
.and_then(|header| header.to_str().ok())
|
||||||
HeaderMap::try_borrow_from(&state)
|
.map(|value| value.to_owned()),
|
||||||
.and_then(|map| map.get(name))
|
AuthSource::AuthorizationHeader => HeaderMap::try_borrow_from(&state)
|
||||||
.and_then(|header| header.to_str().ok())
|
.and_then(|map| map.get(AUTHORIZATION))
|
||||||
.map(|value| value.to_owned())
|
.and_then(|header| header.to_str().ok())
|
||||||
},
|
.and_then(|value| value.split_whitespace().nth(1))
|
||||||
AuthSource::AuthorizationHeader => {
|
.map(|value| value.to_owned())
|
||||||
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
|
// unauthed if no token
|
||||||
|
@ -270,7 +240,7 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
// validate the token
|
// 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,
|
Ok(data) => data.claims,
|
||||||
Err(e) => match dbg!(e.into_kind()) {
|
Err(e) => match dbg!(e.into_kind()) {
|
||||||
ErrorKind::ExpiredSignature => return AuthStatus::Expired,
|
ErrorKind::ExpiredSignature => return AuthStatus::Expired,
|
||||||
|
@ -285,12 +255,12 @@ where
|
||||||
|
|
||||||
impl<Data, Handler> Middleware for AuthMiddleware<Data, Handler>
|
impl<Data, Handler> Middleware for AuthMiddleware<Data, Handler>
|
||||||
where
|
where
|
||||||
Data : DeserializeOwned + Send + 'static,
|
Data: DeserializeOwned + Send + 'static,
|
||||||
Handler : AuthHandler<Data>
|
Handler: AuthHandler<Data>
|
||||||
{
|
{
|
||||||
fn call<Chain>(self, mut state : State, chain : Chain) -> Pin<Box<HandlerFuture>>
|
fn call<Chain>(self, mut state: State, chain: Chain) -> Pin<Box<HandlerFuture>>
|
||||||
where
|
where
|
||||||
Chain : FnOnce(State) -> Pin<Box<HandlerFuture>>
|
Chain: FnOnce(State) -> Pin<Box<HandlerFuture>>
|
||||||
{
|
{
|
||||||
// put the source in our state, required for e.g. openapi
|
// put the source in our state, required for e.g. openapi
|
||||||
state.put(self.source.clone());
|
state.put(self.source.clone());
|
||||||
|
@ -306,26 +276,24 @@ where
|
||||||
|
|
||||||
impl<Data, Handler> NewMiddleware for AuthMiddleware<Data, Handler>
|
impl<Data, Handler> NewMiddleware for AuthMiddleware<Data, Handler>
|
||||||
where
|
where
|
||||||
Self : Clone + Middleware + Sync + RefUnwindSafe
|
Self: Clone + Middleware + Sync + RefUnwindSafe
|
||||||
{
|
{
|
||||||
type Instance = Self;
|
type Instance = Self;
|
||||||
|
|
||||||
fn new_middleware(&self) -> Result<Self::Instance, std::io::Error>
|
fn new_middleware(&self) -> Result<Self::Instance, std::io::Error> {
|
||||||
{
|
let c: Self = self.clone();
|
||||||
let c : Self = self.clone();
|
|
||||||
Ok(c)
|
Ok(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test
|
mod test {
|
||||||
{
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use cookie::Cookie;
|
use cookie::Cookie;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
// 256-bit random string
|
// 256-bit random string
|
||||||
const JWT_SECRET : &'static [u8; 32] = b"Lyzsfnta0cdxyF0T9y6VGxp3jpgoMUuW";
|
const JWT_SECRET: &'static [u8; 32] = b"Lyzsfnta0cdxyF0T9y6VGxp3jpgoMUuW";
|
||||||
|
|
||||||
// some known tokens
|
// some known tokens
|
||||||
const VALID_TOKEN : &'static str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtc3JkMCIsInN1YiI6ImdvdGhhbS1yZXN0ZnVsIiwiaWF0IjoxNTc3ODM2ODAwLCJleHAiOjQxMDI0NDQ4MDB9.8h8Ax-nnykqEQ62t7CxmM3ja6NzUQ4L0MLOOzddjLKk";
|
const VALID_TOKEN : &'static str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtc3JkMCIsInN1YiI6ImdvdGhhbS1yZXN0ZnVsIiwiaWF0IjoxNTc3ODM2ODAwLCJleHAiOjQxMDI0NDQ4MDB9.8h8Ax-nnykqEQ62t7CxmM3ja6NzUQ4L0MLOOzddjLKk";
|
||||||
|
@ -333,18 +301,15 @@ mod test
|
||||||
const INVALID_TOKEN : &'static str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtc3JkMCIsInN1YiI6ImdvdGhhbS1yZXN0ZnVsIiwiaWF0IjoxNTc3ODM2ODAwLCJleHAiOjQxMDI0NDQ4MDB9";
|
const INVALID_TOKEN : &'static str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtc3JkMCIsInN1YiI6ImdvdGhhbS1yZXN0ZnVsIiwiaWF0IjoxNTc3ODM2ODAwLCJleHAiOjQxMDI0NDQ4MDB9";
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, PartialEq)]
|
#[derive(Debug, Deserialize, PartialEq)]
|
||||||
struct TestData
|
struct TestData {
|
||||||
{
|
iss: String,
|
||||||
iss : String,
|
sub: String,
|
||||||
sub : String,
|
iat: u64,
|
||||||
iat : u64,
|
exp: u64
|
||||||
exp : u64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TestData
|
impl Default for TestData {
|
||||||
{
|
fn default() -> Self {
|
||||||
fn default() -> Self
|
|
||||||
{
|
|
||||||
Self {
|
Self {
|
||||||
iss: "msrd0".to_owned(),
|
iss: "msrd0".to_owned(),
|
||||||
sub: "gotham-restful".to_owned(),
|
sub: "gotham-restful".to_owned(),
|
||||||
|
@ -356,17 +321,14 @@ mod test
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct NoneAuthHandler;
|
struct NoneAuthHandler;
|
||||||
impl<T> AuthHandler<T> for NoneAuthHandler
|
impl<T> AuthHandler<T> for NoneAuthHandler {
|
||||||
{
|
fn jwt_secret<F: FnOnce() -> Option<T>>(&self, _state: &mut State, _decode_data: F) -> Option<Vec<u8>> {
|
||||||
fn jwt_secret<F : FnOnce() -> Option<T>>(&self, _state : &mut State, _decode_data : F) -> Option<Vec<u8>>
|
|
||||||
{
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_auth_middleware_none_secret()
|
fn test_auth_middleware_none_secret() {
|
||||||
{
|
|
||||||
let middleware = <AuthMiddleware<TestData, NoneAuthHandler>>::from_source(AuthSource::AuthorizationHeader);
|
let middleware = <AuthMiddleware<TestData, NoneAuthHandler>>::from_source(AuthSource::AuthorizationHeader);
|
||||||
State::with_new(|mut state| {
|
State::with_new(|mut state| {
|
||||||
let mut headers = HeaderMap::new();
|
let mut headers = HeaderMap::new();
|
||||||
|
@ -379,18 +341,17 @@ mod test
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct TestAssertingHandler;
|
struct TestAssertingHandler;
|
||||||
impl<T> AuthHandler<T> for TestAssertingHandler
|
impl<T> AuthHandler<T> for TestAssertingHandler
|
||||||
where T : Debug + Default + PartialEq
|
where
|
||||||
|
T: Debug + Default + PartialEq
|
||||||
{
|
{
|
||||||
fn jwt_secret<F : FnOnce() -> Option<T>>(&self, _state : &mut State, decode_data : F) -> Option<Vec<u8>>
|
fn jwt_secret<F: FnOnce() -> Option<T>>(&self, _state: &mut State, decode_data: F) -> Option<Vec<u8>> {
|
||||||
{
|
|
||||||
assert_eq!(decode_data(), Some(T::default()));
|
assert_eq!(decode_data(), Some(T::default()));
|
||||||
Some(JWT_SECRET.to_vec())
|
Some(JWT_SECRET.to_vec())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_auth_middleware_decode_data()
|
fn test_auth_middleware_decode_data() {
|
||||||
{
|
|
||||||
let middleware = <AuthMiddleware<TestData, TestAssertingHandler>>::from_source(AuthSource::AuthorizationHeader);
|
let middleware = <AuthMiddleware<TestData, TestAssertingHandler>>::from_source(AuthSource::AuthorizationHeader);
|
||||||
State::with_new(|mut state| {
|
State::with_new(|mut state| {
|
||||||
let mut headers = HeaderMap::new();
|
let mut headers = HeaderMap::new();
|
||||||
|
@ -400,15 +361,15 @@ mod test
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_middleware<T>(source : AuthSource) -> AuthMiddleware<T, StaticAuthHandler>
|
fn new_middleware<T>(source: AuthSource) -> AuthMiddleware<T, StaticAuthHandler>
|
||||||
where T : DeserializeOwned + Send
|
where
|
||||||
|
T: DeserializeOwned + Send
|
||||||
{
|
{
|
||||||
AuthMiddleware::new(source, Default::default(), StaticAuthHandler::from_array(JWT_SECRET))
|
AuthMiddleware::new(source, Default::default(), StaticAuthHandler::from_array(JWT_SECRET))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_auth_middleware_no_token()
|
fn test_auth_middleware_no_token() {
|
||||||
{
|
|
||||||
let middleware = new_middleware::<TestData>(AuthSource::AuthorizationHeader);
|
let middleware = new_middleware::<TestData>(AuthSource::AuthorizationHeader);
|
||||||
State::with_new(|mut state| {
|
State::with_new(|mut state| {
|
||||||
let status = middleware.auth_status(&mut state);
|
let status = middleware.auth_status(&mut state);
|
||||||
|
@ -420,8 +381,7 @@ mod test
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_auth_middleware_expired_token()
|
fn test_auth_middleware_expired_token() {
|
||||||
{
|
|
||||||
let middleware = new_middleware::<TestData>(AuthSource::AuthorizationHeader);
|
let middleware = new_middleware::<TestData>(AuthSource::AuthorizationHeader);
|
||||||
State::with_new(|mut state| {
|
State::with_new(|mut state| {
|
||||||
let mut headers = HeaderMap::new();
|
let mut headers = HeaderMap::new();
|
||||||
|
@ -436,8 +396,7 @@ mod test
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_auth_middleware_invalid_token()
|
fn test_auth_middleware_invalid_token() {
|
||||||
{
|
|
||||||
let middleware = new_middleware::<TestData>(AuthSource::AuthorizationHeader);
|
let middleware = new_middleware::<TestData>(AuthSource::AuthorizationHeader);
|
||||||
State::with_new(|mut state| {
|
State::with_new(|mut state| {
|
||||||
let mut headers = HeaderMap::new();
|
let mut headers = HeaderMap::new();
|
||||||
|
@ -452,8 +411,7 @@ mod test
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_auth_middleware_auth_header_token()
|
fn test_auth_middleware_auth_header_token() {
|
||||||
{
|
|
||||||
let middleware = new_middleware::<TestData>(AuthSource::AuthorizationHeader);
|
let middleware = new_middleware::<TestData>(AuthSource::AuthorizationHeader);
|
||||||
State::with_new(|mut state| {
|
State::with_new(|mut state| {
|
||||||
let mut headers = HeaderMap::new();
|
let mut headers = HeaderMap::new();
|
||||||
|
@ -468,8 +426,7 @@ mod test
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_auth_middleware_header_token()
|
fn test_auth_middleware_header_token() {
|
||||||
{
|
|
||||||
let header_name = "x-znoiprwmvfexju";
|
let header_name = "x-znoiprwmvfexju";
|
||||||
let middleware = new_middleware::<TestData>(AuthSource::Header(HeaderName::from_static(header_name)));
|
let middleware = new_middleware::<TestData>(AuthSource::Header(HeaderName::from_static(header_name)));
|
||||||
State::with_new(|mut state| {
|
State::with_new(|mut state| {
|
||||||
|
@ -485,8 +442,7 @@ mod test
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_auth_middleware_cookie_token()
|
fn test_auth_middleware_cookie_token() {
|
||||||
{
|
|
||||||
let cookie_name = "znoiprwmvfexju";
|
let cookie_name = "znoiprwmvfexju";
|
||||||
let middleware = new_middleware::<TestData>(AuthSource::Cookie(cookie_name.to_owned()));
|
let middleware = new_middleware::<TestData>(AuthSource::Cookie(cookie_name.to_owned()));
|
||||||
State::with_new(|mut state| {
|
State::with_new(|mut state| {
|
||||||
|
|
94
src/cors.rs
94
src/cors.rs
|
@ -4,22 +4,19 @@ use gotham::{
|
||||||
helpers::http::response::create_empty_response,
|
helpers::http::response::create_empty_response,
|
||||||
hyper::{
|
hyper::{
|
||||||
header::{
|
header::{
|
||||||
ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_METHODS,
|
HeaderMap, HeaderName, HeaderValue, ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_HEADERS,
|
||||||
ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_MAX_AGE, ACCESS_CONTROL_REQUEST_METHOD, ORIGIN, VARY,
|
ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_MAX_AGE,
|
||||||
HeaderMap, HeaderName, HeaderValue
|
ACCESS_CONTROL_REQUEST_METHOD, ORIGIN, VARY
|
||||||
},
|
},
|
||||||
Body, Method, Response, StatusCode
|
Body, Method, Response, StatusCode
|
||||||
},
|
},
|
||||||
middleware::Middleware,
|
middleware::Middleware,
|
||||||
pipeline::chain::PipelineHandleChain,
|
pipeline::chain::PipelineHandleChain,
|
||||||
router::builder::*,
|
router::builder::*,
|
||||||
state::{FromState, State},
|
state::{FromState, State}
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use std::{
|
use std::{panic::RefUnwindSafe, pin::Pin};
|
||||||
panic::RefUnwindSafe,
|
|
||||||
pin::Pin
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Specify the allowed origins of the request. It is up to the browser to check the validity of the
|
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.
|
allowed to make the request.
|
||||||
*/
|
*/
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Origin
|
pub enum Origin {
|
||||||
{
|
|
||||||
/// Do not send any `Access-Control-Allow-Origin` headers.
|
/// Do not send any `Access-Control-Allow-Origin` headers.
|
||||||
None,
|
None,
|
||||||
/// Send `Access-Control-Allow-Origin: *`. Note that browser will not send credentials.
|
/// Send `Access-Control-Allow-Origin: *`. Note that browser will not send credentials.
|
||||||
|
@ -39,19 +35,15 @@ pub enum Origin
|
||||||
Copy
|
Copy
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Origin
|
impl Default for Origin {
|
||||||
{
|
fn default() -> Self {
|
||||||
fn default() -> Self
|
|
||||||
{
|
|
||||||
Self::None
|
Self::None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Origin
|
impl Origin {
|
||||||
{
|
|
||||||
/// Get the header value for the `Access-Control-Allow-Origin` header.
|
/// Get the header value for the `Access-Control-Allow-Origin` header.
|
||||||
fn header_value(&self, state : &State) -> Option<HeaderValue>
|
fn header_value(&self, state: &State) -> Option<HeaderValue> {
|
||||||
{
|
|
||||||
match self {
|
match self {
|
||||||
Self::None => None,
|
Self::None => None,
|
||||||
Self::Star => Some("*".parse().unwrap()),
|
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
|
[`State`]: ../gotham/state/struct.State.html
|
||||||
*/
|
*/
|
||||||
#[derive(Clone, Debug, Default, NewMiddleware, StateData)]
|
#[derive(Clone, Debug, Default, NewMiddleware, StateData)]
|
||||||
pub struct CorsConfig
|
pub struct CorsConfig {
|
||||||
{
|
|
||||||
/// The allowed origins.
|
/// The allowed origins.
|
||||||
pub origin : Origin,
|
pub origin: Origin,
|
||||||
/// The allowed headers.
|
/// The allowed headers.
|
||||||
pub headers : Vec<HeaderName>,
|
pub headers: Vec<HeaderName>,
|
||||||
/// The amount of seconds that the preflight request can be cached.
|
/// 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.
|
/// Whether or not the request may be made with supplying credentials.
|
||||||
pub credentials : bool
|
pub credentials: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Middleware for CorsConfig
|
impl Middleware for CorsConfig {
|
||||||
{
|
fn call<Chain>(self, mut state: State, chain: Chain) -> Pin<Box<HandlerFuture>>
|
||||||
fn call<Chain>(self, mut state : State, chain : Chain) -> Pin<Box<HandlerFuture>>
|
|
||||||
where
|
where
|
||||||
Chain : FnOnce(State) -> Pin<Box<HandlerFuture>>
|
Chain: FnOnce(State) -> Pin<Box<HandlerFuture>>
|
||||||
{
|
{
|
||||||
state.put(self);
|
state.put(self);
|
||||||
chain(state)
|
chain(state)
|
||||||
|
@ -161,27 +151,23 @@ For further information on CORS, read https://developer.mozilla.org/en-US/docs/W
|
||||||
|
|
||||||
[`CorsConfig`]: ./struct.CorsConfig.html
|
[`CorsConfig`]: ./struct.CorsConfig.html
|
||||||
*/
|
*/
|
||||||
pub fn handle_cors(state : &State, res : &mut Response<Body>)
|
pub fn handle_cors(state: &State, res: &mut Response<Body>) {
|
||||||
{
|
|
||||||
let config = CorsConfig::try_borrow_from(state);
|
let config = CorsConfig::try_borrow_from(state);
|
||||||
let headers = res.headers_mut();
|
let headers = res.headers_mut();
|
||||||
|
|
||||||
// non-preflight requests require the Access-Control-Allow-Origin header
|
// 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);
|
headers.insert(ACCESS_CONTROL_ALLOW_ORIGIN, header);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the origin is copied over, we should tell the browser by specifying the Vary 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()));
|
let vary = headers.get(VARY).map(|vary| format!("{},Origin", vary.to_str().unwrap()));
|
||||||
headers.insert(VARY, vary.as_deref().unwrap_or("Origin").parse().unwrap());
|
headers.insert(VARY, vary.as_deref().unwrap_or("Origin").parse().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we allow credentials, tell the browser
|
// 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());
|
headers.insert(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true".parse().unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -206,16 +192,15 @@ pub fn handle_cors(state : &State, res : &mut Response<Body>)
|
||||||
/// ```
|
/// ```
|
||||||
pub trait CorsRoute<C, P>
|
pub trait CorsRoute<C, P>
|
||||||
where
|
where
|
||||||
C : PipelineHandleChain<P> + Copy + Send + Sync + 'static,
|
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
|
||||||
P : RefUnwindSafe + Send + Sync + 'static
|
P: RefUnwindSafe + Send + Sync + 'static
|
||||||
{
|
{
|
||||||
/// Handle a preflight request on `path` for `method`. To configure the behaviour, use
|
/// Handle a preflight request on `path` for `method`. To configure the behaviour, use
|
||||||
/// [`CorsConfig`](struct.CorsConfig.html).
|
/// [`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<Body>)
|
fn cors_preflight_handler(state: State) -> (State, Response<Body>) {
|
||||||
{
|
|
||||||
let config = CorsConfig::try_borrow_from(&state);
|
let config = CorsConfig::try_borrow_from(&state);
|
||||||
|
|
||||||
// prepare the response
|
// prepare the response
|
||||||
|
@ -223,22 +208,22 @@ fn cors_preflight_handler(state : State) -> (State, Response<Body>)
|
||||||
let headers = res.headers_mut();
|
let headers = res.headers_mut();
|
||||||
|
|
||||||
// copy the request method over to the response
|
// 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);
|
headers.insert(ACCESS_CONTROL_ALLOW_METHODS, method);
|
||||||
|
|
||||||
// if we allow any headers, put them in
|
// if we allow any headers, put them in
|
||||||
if let Some(hdrs) = config.map(|cfg| &cfg.headers)
|
if let Some(hdrs) = config.map(|cfg| &cfg.headers) {
|
||||||
{
|
if hdrs.len() > 0 {
|
||||||
if hdrs.len() > 0
|
|
||||||
{
|
|
||||||
// TODO do we want to return all headers or just those asked by the browser?
|
// 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());
|
headers.insert(ACCESS_CONTROL_ALLOW_HEADERS, hdrs.iter().join(",").parse().unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the max age for the preflight cache
|
// 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());
|
headers.insert(ACCESS_CONTROL_MAX_AGE, age.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,15 +236,12 @@ fn cors_preflight_handler(state : State) -> (State, Response<Body>)
|
||||||
|
|
||||||
impl<D, C, P> CorsRoute<C, P> for D
|
impl<D, C, P> CorsRoute<C, P> for D
|
||||||
where
|
where
|
||||||
D : DrawRoutes<C, P>,
|
D: DrawRoutes<C, P>,
|
||||||
C : PipelineHandleChain<P> + Copy + Send + Sync + 'static,
|
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
|
||||||
P : RefUnwindSafe + 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);
|
let matcher = AccessControlRequestMethodMatcher::new(method);
|
||||||
self.options(path)
|
self.options(path).extend_route_matcher(matcher).to(cors_preflight_handler);
|
||||||
.extend_route_matcher(matcher)
|
|
||||||
.to(cors_preflight_handler);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
54
src/lib.rs
54
src/lib.rs
|
@ -370,9 +370,12 @@ Licensed under your option of:
|
||||||
// weird proc macro issue
|
// weird proc macro issue
|
||||||
extern crate self as gotham_restful;
|
extern crate self as gotham_restful;
|
||||||
|
|
||||||
#[macro_use] extern crate gotham_derive;
|
#[macro_use]
|
||||||
#[macro_use] extern crate log;
|
extern crate gotham_derive;
|
||||||
#[macro_use] extern crate serde;
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde;
|
||||||
|
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use gotham;
|
pub use gotham;
|
||||||
|
@ -388,8 +391,7 @@ pub use gotham_restful_derive::*;
|
||||||
|
|
||||||
/// Not public API
|
/// Not public API
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub mod export
|
pub mod export {
|
||||||
{
|
|
||||||
pub use futures_util::future::FutureExt;
|
pub use futures_util::future::FutureExt;
|
||||||
|
|
||||||
pub use serde_json;
|
pub use serde_json;
|
||||||
|
@ -406,24 +408,12 @@ pub mod export
|
||||||
#[cfg(feature = "auth")]
|
#[cfg(feature = "auth")]
|
||||||
mod auth;
|
mod auth;
|
||||||
#[cfg(feature = "auth")]
|
#[cfg(feature = "auth")]
|
||||||
pub use auth::{
|
pub use auth::{AuthHandler, AuthMiddleware, AuthSource, AuthStatus, AuthValidation, StaticAuthHandler};
|
||||||
AuthHandler,
|
|
||||||
AuthMiddleware,
|
|
||||||
AuthSource,
|
|
||||||
AuthStatus,
|
|
||||||
AuthValidation,
|
|
||||||
StaticAuthHandler
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(feature = "cors")]
|
#[cfg(feature = "cors")]
|
||||||
mod cors;
|
mod cors;
|
||||||
#[cfg(feature = "cors")]
|
#[cfg(feature = "cors")]
|
||||||
pub use cors::{
|
pub use cors::{handle_cors, CorsConfig, CorsRoute, Origin};
|
||||||
handle_cors,
|
|
||||||
CorsConfig,
|
|
||||||
CorsRoute,
|
|
||||||
Origin
|
|
||||||
};
|
|
||||||
|
|
||||||
pub mod matcher;
|
pub mod matcher;
|
||||||
|
|
||||||
|
@ -438,16 +428,8 @@ pub use openapi::{
|
||||||
|
|
||||||
mod resource;
|
mod resource;
|
||||||
pub use resource::{
|
pub use resource::{
|
||||||
Resource,
|
Resource, ResourceChange, ResourceChangeAll, ResourceCreate, ResourceMethod, ResourceRead, ResourceReadAll,
|
||||||
ResourceMethod,
|
ResourceRemove, ResourceRemoveAll, ResourceSearch
|
||||||
ResourceReadAll,
|
|
||||||
ResourceRead,
|
|
||||||
ResourceSearch,
|
|
||||||
ResourceCreate,
|
|
||||||
ResourceChangeAll,
|
|
||||||
ResourceChange,
|
|
||||||
ResourceRemoveAll,
|
|
||||||
ResourceRemove
|
|
||||||
};
|
};
|
||||||
|
|
||||||
mod response;
|
mod response;
|
||||||
|
@ -455,22 +437,14 @@ pub use response::Response;
|
||||||
|
|
||||||
mod result;
|
mod result;
|
||||||
pub use result::{
|
pub use result::{
|
||||||
AuthError,
|
AuthError, AuthError::Forbidden, AuthErrorOrOther, AuthResult, AuthSuccess, IntoResponseError, NoContent, Raw,
|
||||||
AuthError::Forbidden,
|
ResourceResult, Success
|
||||||
AuthErrorOrOther,
|
|
||||||
AuthResult,
|
|
||||||
AuthSuccess,
|
|
||||||
IntoResponseError,
|
|
||||||
NoContent,
|
|
||||||
Raw,
|
|
||||||
ResourceResult,
|
|
||||||
Success
|
|
||||||
};
|
};
|
||||||
|
|
||||||
mod routing;
|
mod routing;
|
||||||
pub use routing::{DrawResources, DrawResourceRoutes};
|
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
pub use routing::WithOpenapi;
|
pub use routing::WithOpenapi;
|
||||||
|
pub use routing::{DrawResourceRoutes, DrawResources};
|
||||||
|
|
||||||
mod types;
|
mod types;
|
||||||
pub use types::*;
|
pub use types::*;
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
use gotham::{
|
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},
|
router::{non_match::RouteNonMatch, route::matcher::RouteMatcher},
|
||||||
state::{FromState, State}
|
state::{FromState, State}
|
||||||
};
|
};
|
||||||
|
@ -18,41 +21,35 @@ use gotham::{
|
||||||
///
|
///
|
||||||
/// # build_simple_router(|route| {
|
/// # build_simple_router(|route| {
|
||||||
/// // use the matcher for your request
|
/// // use the matcher for your request
|
||||||
/// route.options("/foo")
|
/// route.options("/foo").extend_route_matcher(matcher).to(|state| {
|
||||||
/// .extend_route_matcher(matcher)
|
/// // we know that this is a CORS preflight for a PUT request
|
||||||
/// .to(|state| {
|
/// let mut res = create_empty_response(&state, StatusCode::NO_CONTENT);
|
||||||
/// // we know that this is a CORS preflight for a PUT request
|
/// res.headers_mut().insert(ACCESS_CONTROL_ALLOW_METHODS, "PUT".parse().unwrap());
|
||||||
/// let mut res = create_empty_response(&state, StatusCode::NO_CONTENT);
|
/// (state, res)
|
||||||
/// res.headers_mut().insert(ACCESS_CONTROL_ALLOW_METHODS, "PUT".parse().unwrap());
|
|
||||||
/// (state, res)
|
|
||||||
/// });
|
/// });
|
||||||
/// # });
|
/// # });
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct AccessControlRequestMethodMatcher
|
pub struct AccessControlRequestMethodMatcher {
|
||||||
{
|
method: Method
|
||||||
method : Method
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AccessControlRequestMethodMatcher
|
impl AccessControlRequestMethodMatcher {
|
||||||
{
|
|
||||||
/// Construct a new matcher that matches if the `Access-Control-Request-Method` header matches `method`.
|
/// 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,
|
/// 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
|
/// 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.
|
/// it is uppercased or this matcher will never succeed.
|
||||||
pub fn new(method : Method) -> Self
|
pub fn new(method: Method) -> Self {
|
||||||
{
|
|
||||||
Self { method }
|
Self { method }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RouteMatcher for AccessControlRequestMethodMatcher
|
impl RouteMatcher for AccessControlRequestMethodMatcher {
|
||||||
{
|
fn is_match(&self, state: &State) -> Result<(), RouteNonMatch> {
|
||||||
fn is_match(&self, state : &State) -> Result<(), RouteNonMatch>
|
|
||||||
{
|
|
||||||
// according to the fetch specification, methods should be normalized by byte-uppercase
|
// according to the fetch specification, methods should be normalized by byte-uppercase
|
||||||
// https://fetch.spec.whatwg.org/#concept-method
|
// 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(|value| value.to_str().ok())
|
||||||
.and_then(|str| str.to_ascii_uppercase().parse::<Method>().ok())
|
.and_then(|str| str.to_ascii_uppercase().parse::<Method>().ok())
|
||||||
{
|
{
|
||||||
|
@ -62,19 +59,17 @@ impl RouteMatcher for AccessControlRequestMethodMatcher
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test
|
mod test {
|
||||||
{
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn with_state<F>(accept : Option<&str>, block : F)
|
fn with_state<F>(accept: Option<&str>, block: F)
|
||||||
where F : FnOnce(&mut State) -> ()
|
where
|
||||||
|
F: FnOnce(&mut State) -> ()
|
||||||
{
|
{
|
||||||
State::with_new(|state| {
|
State::with_new(|state| {
|
||||||
let mut headers = HeaderMap::new();
|
let mut headers = HeaderMap::new();
|
||||||
if let Some(acc) = accept
|
if let Some(acc) = accept {
|
||||||
{
|
|
||||||
headers.insert(ACCESS_CONTROL_REQUEST_METHOD, acc.parse().unwrap());
|
headers.insert(ACCESS_CONTROL_REQUEST_METHOD, acc.parse().unwrap());
|
||||||
}
|
}
|
||||||
state.put(headers);
|
state.put(headers);
|
||||||
|
@ -83,23 +78,20 @@ mod test
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn no_acrm_header()
|
fn no_acrm_header() {
|
||||||
{
|
|
||||||
let matcher = AccessControlRequestMethodMatcher::new(Method::PUT);
|
let matcher = AccessControlRequestMethodMatcher::new(Method::PUT);
|
||||||
with_state(None, |state| assert!(matcher.is_match(&state).is_err()));
|
with_state(None, |state| assert!(matcher.is_match(&state).is_err()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn correct_acrm_header()
|
fn correct_acrm_header() {
|
||||||
{
|
|
||||||
let matcher = AccessControlRequestMethodMatcher::new(Method::PUT);
|
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()));
|
||||||
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]
|
#[test]
|
||||||
fn incorrect_acrm_header()
|
fn incorrect_acrm_header() {
|
||||||
{
|
|
||||||
let matcher = AccessControlRequestMethodMatcher::new(Method::PUT);
|
let matcher = AccessControlRequestMethodMatcher::new(Method::PUT);
|
||||||
with_state(Some("DELETE"), |state| assert!(matcher.is_match(&state).is_err()));
|
with_state(Some("DELETE"), |state| assert!(matcher.is_match(&state).is_err()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,4 +2,3 @@
|
||||||
mod access_control_request_method;
|
mod access_control_request_method;
|
||||||
#[cfg(feature = "cors")]
|
#[cfg(feature = "cors")]
|
||||||
pub use access_control_request_method::AccessControlRequestMethodMatcher;
|
pub use access_control_request_method::AccessControlRequestMethodMatcher;
|
||||||
|
|
||||||
|
|
|
@ -1,29 +1,26 @@
|
||||||
use crate::{OpenapiType, OpenapiSchema};
|
use crate::{OpenapiSchema, OpenapiType};
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use openapiv3::{
|
use openapiv3::{
|
||||||
Components, OpenAPI, PathItem, ReferenceOr, ReferenceOr::Item, ReferenceOr::Reference, Schema,
|
Components, OpenAPI, PathItem, ReferenceOr,
|
||||||
Server
|
ReferenceOr::{Item, Reference},
|
||||||
|
Schema, Server
|
||||||
};
|
};
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct OpenapiInfo
|
pub struct OpenapiInfo {
|
||||||
{
|
pub title: String,
|
||||||
pub title : String,
|
pub version: String,
|
||||||
pub version : String,
|
pub urls: Vec<String>
|
||||||
pub urls : Vec<String>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct OpenapiBuilder
|
pub struct OpenapiBuilder {
|
||||||
{
|
pub openapi: Arc<RwLock<OpenAPI>>
|
||||||
pub openapi : Arc<RwLock<OpenAPI>>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OpenapiBuilder
|
impl OpenapiBuilder {
|
||||||
{
|
pub fn new(info: OpenapiInfo) -> Self {
|
||||||
pub fn new(info : OpenapiInfo) -> Self
|
|
||||||
{
|
|
||||||
Self {
|
Self {
|
||||||
openapi: Arc::new(RwLock::new(OpenAPI {
|
openapi: Arc::new(RwLock::new(OpenAPI {
|
||||||
openapi: "3.0.2".to_string(),
|
openapi: "3.0.2".to_string(),
|
||||||
|
@ -32,8 +29,13 @@ impl OpenapiBuilder
|
||||||
version: info.version,
|
version: info.version,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
servers: info.urls.into_iter()
|
servers: info
|
||||||
.map(|url| Server { url, ..Default::default() })
|
.urls
|
||||||
|
.into_iter()
|
||||||
|
.map(|url| Server {
|
||||||
|
url,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}))
|
}))
|
||||||
|
@ -42,8 +44,7 @@ impl OpenapiBuilder
|
||||||
|
|
||||||
/// Remove path from the OpenAPI spec, or return an empty one if not included. This is handy if you need to
|
/// 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
|
/// 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();
|
let mut openapi = self.openapi.write().unwrap();
|
||||||
match openapi.paths.swap_remove(path) {
|
match openapi.paths.swap_remove(path) {
|
||||||
Some(Item(item)) => item,
|
Some(Item(item)) => item,
|
||||||
|
@ -51,14 +52,12 @@ impl OpenapiBuilder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_path<Path : ToString>(&mut self, path : Path, item : PathItem)
|
pub fn add_path<Path: ToString>(&mut self, path: Path, item: PathItem) {
|
||||||
{
|
|
||||||
let mut openapi = self.openapi.write().unwrap();
|
let mut openapi = self.openapi.write().unwrap();
|
||||||
openapi.paths.insert(path.to_string(), Item(item));
|
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);
|
self.add_schema_dependencies(&mut schema.dependencies);
|
||||||
|
|
||||||
let mut openapi = self.openapi.write().unwrap();
|
let mut openapi = self.openapi.write().unwrap();
|
||||||
|
@ -74,25 +73,23 @@ impl OpenapiBuilder
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_schema_dependencies(&mut self, dependencies : &mut IndexMap<String, OpenapiSchema>)
|
fn add_schema_dependencies(&mut self, dependencies: &mut IndexMap<String, OpenapiSchema>) {
|
||||||
{
|
let keys: Vec<String> = dependencies.keys().map(|k| k.to_string()).collect();
|
||||||
let keys : Vec<String> = dependencies.keys().map(|k| k.to_string()).collect();
|
for dep in keys {
|
||||||
for dep in keys
|
|
||||||
{
|
|
||||||
let dep_schema = dependencies.swap_remove(&dep);
|
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);
|
self.add_schema_impl(dep, dep_schema);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_schema<T : OpenapiType>(&mut self) -> ReferenceOr<Schema>
|
pub fn add_schema<T: OpenapiType>(&mut self) -> ReferenceOr<Schema> {
|
||||||
{
|
|
||||||
let mut schema = T::schema();
|
let mut schema = T::schema();
|
||||||
match schema.name.clone() {
|
match schema.name.clone() {
|
||||||
Some(name) => {
|
Some(name) => {
|
||||||
let reference = Reference { reference: format!("#/components/schemas/{}", name) };
|
let reference = Reference {
|
||||||
|
reference: format!("#/components/schemas/{}", name)
|
||||||
|
};
|
||||||
self.add_schema_impl(name, schema);
|
self.add_schema_impl(name, schema);
|
||||||
reference
|
reference
|
||||||
},
|
},
|
||||||
|
@ -104,27 +101,22 @@ impl OpenapiBuilder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
mod test
|
mod test {
|
||||||
{
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[derive(OpenapiType)]
|
#[derive(OpenapiType)]
|
||||||
struct Message
|
struct Message {
|
||||||
{
|
msg: String
|
||||||
msg : String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(OpenapiType)]
|
#[derive(OpenapiType)]
|
||||||
struct Messages
|
struct Messages {
|
||||||
{
|
msgs: Vec<Message>
|
||||||
msgs : Vec<Message>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn info() -> OpenapiInfo
|
fn info() -> OpenapiInfo {
|
||||||
{
|
|
||||||
OpenapiInfo {
|
OpenapiInfo {
|
||||||
title: "TEST CASE".to_owned(),
|
title: "TEST CASE".to_owned(),
|
||||||
version: "1.2.3".to_owned(),
|
version: "1.2.3".to_owned(),
|
||||||
|
@ -132,14 +124,12 @@ mod test
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn openapi(builder : OpenapiBuilder) -> OpenAPI
|
fn openapi(builder: OpenapiBuilder) -> OpenAPI {
|
||||||
{
|
|
||||||
Arc::try_unwrap(builder.openapi).unwrap().into_inner().unwrap()
|
Arc::try_unwrap(builder.openapi).unwrap().into_inner().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn new_builder()
|
fn new_builder() {
|
||||||
{
|
|
||||||
let info = info();
|
let info = info();
|
||||||
let builder = OpenapiBuilder::new(info.clone());
|
let builder = OpenapiBuilder::new(info.clone());
|
||||||
let openapi = openapi(builder);
|
let openapi = openapi(builder);
|
||||||
|
@ -150,13 +140,18 @@ mod test
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn add_schema()
|
fn add_schema() {
|
||||||
{
|
|
||||||
let mut builder = OpenapiBuilder::new(info());
|
let mut builder = OpenapiBuilder::new(info());
|
||||||
builder.add_schema::<Option<Messages>>();
|
builder.add_schema::<Option<Messages>>();
|
||||||
let openapi = openapi(builder);
|
let openapi = openapi(builder);
|
||||||
|
|
||||||
assert_eq!(openapi.components.clone().unwrap_or_default().schemas["Message"] , ReferenceOr::Item(Message ::schema().into_schema()));
|
assert_eq!(
|
||||||
assert_eq!(openapi.components.clone().unwrap_or_default().schemas["Messages"], ReferenceOr::Item(Messages::schema().into_schema()));
|
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())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,32 +15,26 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct OpenapiHandler
|
pub struct OpenapiHandler {
|
||||||
{
|
openapi: Arc<RwLock<OpenAPI>>
|
||||||
openapi : Arc<RwLock<OpenAPI>>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OpenapiHandler
|
impl OpenapiHandler {
|
||||||
{
|
pub fn new(openapi: Arc<RwLock<OpenAPI>>) -> Self {
|
||||||
pub fn new(openapi : Arc<RwLock<OpenAPI>>) -> Self
|
|
||||||
{
|
|
||||||
Self { openapi }
|
Self { openapi }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NewHandler for OpenapiHandler
|
impl NewHandler for OpenapiHandler {
|
||||||
{
|
|
||||||
type Instance = Self;
|
type Instance = Self;
|
||||||
|
|
||||||
fn new_handler(&self) -> Result<Self>
|
fn new_handler(&self) -> Result<Self> {
|
||||||
{
|
|
||||||
Ok(self.clone())
|
Ok(self.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "auth")]
|
#[cfg(feature = "auth")]
|
||||||
fn get_security(state : &mut State) -> IndexMap<String, ReferenceOr<SecurityScheme>>
|
fn get_security(state: &mut State) -> IndexMap<String, ReferenceOr<SecurityScheme>> {
|
||||||
{
|
|
||||||
use crate::AuthSource;
|
use crate::AuthSource;
|
||||||
use gotham::state::FromState;
|
use gotham::state::FromState;
|
||||||
|
|
||||||
|
@ -64,28 +58,25 @@ fn get_security(state : &mut State) -> IndexMap<String, ReferenceOr<SecuritySche
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut security_schemes : IndexMap<String, ReferenceOr<SecurityScheme>> = Default::default();
|
let mut security_schemes: IndexMap<String, ReferenceOr<SecurityScheme>> = Default::default();
|
||||||
security_schemes.insert(SECURITY_NAME.to_owned(), ReferenceOr::Item(security_scheme));
|
security_schemes.insert(SECURITY_NAME.to_owned(), ReferenceOr::Item(security_scheme));
|
||||||
|
|
||||||
security_schemes
|
security_schemes
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "auth"))]
|
#[cfg(not(feature = "auth"))]
|
||||||
fn get_security(state : &mut State) -> (Vec<SecurityRequirement>, IndexMap<String, ReferenceOr<SecurityScheme>>)
|
fn get_security(state: &mut State) -> (Vec<SecurityRequirement>, IndexMap<String, ReferenceOr<SecurityScheme>>) {
|
||||||
{
|
|
||||||
Default::default()
|
Default::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler for OpenapiHandler
|
impl Handler for OpenapiHandler {
|
||||||
{
|
fn handle(self, mut state: State) -> Pin<Box<HandlerFuture>> {
|
||||||
fn handle(self, mut state : State) -> Pin<Box<HandlerFuture>>
|
|
||||||
{
|
|
||||||
let openapi = match self.openapi.read() {
|
let openapi = match self.openapi.read() {
|
||||||
Ok(openapi) => openapi,
|
Ok(openapi) => openapi,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Unable to acquire read lock for the OpenAPI specification: {}", e);
|
error!("Unable to acquire read lock for the OpenAPI specification: {}", e);
|
||||||
let res = create_response(&state, crate::StatusCode::INTERNAL_SERVER_ERROR, TEXT_PLAIN, "");
|
let res = create_response(&state, crate::StatusCode::INTERNAL_SERVER_ERROR, TEXT_PLAIN, "");
|
||||||
return future::ok((state, res)).boxed()
|
return future::ok((state, res)).boxed();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
|
const SECURITY_NAME: &str = "authToken";
|
||||||
const SECURITY_NAME : &str = "authToken";
|
|
||||||
|
|
||||||
pub mod builder;
|
pub mod builder;
|
||||||
pub mod handler;
|
pub mod handler;
|
||||||
|
|
|
@ -1,32 +1,21 @@
|
||||||
use crate::{
|
|
||||||
resource::*,
|
|
||||||
result::*,
|
|
||||||
OpenapiSchema,
|
|
||||||
RequestBody
|
|
||||||
};
|
|
||||||
use super::SECURITY_NAME;
|
use super::SECURITY_NAME;
|
||||||
|
use crate::{resource::*, result::*, OpenapiSchema, RequestBody};
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use mime::Mime;
|
use mime::Mime;
|
||||||
use openapiv3::{
|
use openapiv3::{
|
||||||
MediaType, Operation, Parameter, ParameterData, ParameterSchemaOrContent, ReferenceOr,
|
MediaType, Operation, Parameter, ParameterData, ParameterSchemaOrContent, ReferenceOr, ReferenceOr::Item,
|
||||||
ReferenceOr::Item, RequestBody as OARequestBody, Response, Responses, Schema, SchemaKind,
|
RequestBody as OARequestBody, Response, Responses, Schema, SchemaKind, StatusCode, Type
|
||||||
StatusCode, Type
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct OperationParams<'a>
|
struct OperationParams<'a> {
|
||||||
{
|
path_params: Vec<(&'a str, ReferenceOr<Schema>)>,
|
||||||
path_params : Vec<(&'a str, ReferenceOr<Schema>)>,
|
query_params: Option<OpenapiSchema>
|
||||||
query_params : Option<OpenapiSchema>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> OperationParams<'a>
|
impl<'a> OperationParams<'a> {
|
||||||
{
|
fn add_path_params(&self, params: &mut Vec<ReferenceOr<Parameter>>) {
|
||||||
fn add_path_params(&self, params : &mut Vec<ReferenceOr<Parameter>>)
|
for param in &self.path_params {
|
||||||
{
|
|
||||||
for param in &self.path_params
|
|
||||||
{
|
|
||||||
params.push(Item(Parameter::Path {
|
params.push(Item(Parameter::Path {
|
||||||
parameter_data: ParameterData {
|
parameter_data: ParameterData {
|
||||||
name: (*param).0.to_string(),
|
name: (*param).0.to_string(),
|
||||||
|
@ -37,13 +26,12 @@ impl<'a> OperationParams<'a>
|
||||||
example: None,
|
example: None,
|
||||||
examples: IndexMap::new()
|
examples: IndexMap::new()
|
||||||
},
|
},
|
||||||
style: Default::default(),
|
style: Default::default()
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_query_params(self, params : &mut Vec<ReferenceOr<Parameter>>)
|
fn add_query_params(self, params: &mut Vec<ReferenceOr<Parameter>>) {
|
||||||
{
|
|
||||||
let query_params = match self.query_params {
|
let query_params = match self.query_params {
|
||||||
Some(qp) => qp.schema,
|
Some(qp) => qp.schema,
|
||||||
None => return
|
None => return
|
||||||
|
@ -52,8 +40,7 @@ impl<'a> OperationParams<'a>
|
||||||
SchemaKind::Type(Type::Object(ty)) => ty,
|
SchemaKind::Type(Type::Object(ty)) => ty,
|
||||||
_ => panic!("Query Parameters needs to be a plain struct")
|
_ => 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);
|
let required = query_params.required.contains(&name);
|
||||||
params.push(Item(Parameter::Query {
|
params.push(Item(Parameter::Query {
|
||||||
parameter_data: ParameterData {
|
parameter_data: ParameterData {
|
||||||
|
@ -72,31 +59,27 @@ impl<'a> OperationParams<'a>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_params(self) -> Vec<ReferenceOr<Parameter>>
|
fn into_params(self) -> Vec<ReferenceOr<Parameter>> {
|
||||||
{
|
let mut params: Vec<ReferenceOr<Parameter>> = Vec::new();
|
||||||
let mut params : Vec<ReferenceOr<Parameter>> = Vec::new();
|
|
||||||
self.add_path_params(&mut params);
|
self.add_path_params(&mut params);
|
||||||
self.add_query_params(&mut params);
|
self.add_query_params(&mut params);
|
||||||
params
|
params
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct OperationDescription<'a>
|
pub struct OperationDescription<'a> {
|
||||||
{
|
operation_id: Option<String>,
|
||||||
operation_id : Option<String>,
|
default_status: crate::StatusCode,
|
||||||
default_status : crate::StatusCode,
|
accepted_types: Option<Vec<Mime>>,
|
||||||
accepted_types : Option<Vec<Mime>>,
|
schema: ReferenceOr<Schema>,
|
||||||
schema : ReferenceOr<Schema>,
|
params: OperationParams<'a>,
|
||||||
params : OperationParams<'a>,
|
body_schema: Option<ReferenceOr<Schema>>,
|
||||||
body_schema : Option<ReferenceOr<Schema>>,
|
supported_types: Option<Vec<Mime>>,
|
||||||
supported_types : Option<Vec<Mime>>,
|
requires_auth: bool
|
||||||
requires_auth : bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> OperationDescription<'a>
|
impl<'a> OperationDescription<'a> {
|
||||||
{
|
pub fn new<Handler: ResourceMethod>(schema: ReferenceOr<Schema>) -> Self {
|
||||||
pub fn new<Handler : ResourceMethod>(schema : ReferenceOr<Schema>) -> Self
|
|
||||||
{
|
|
||||||
Self {
|
Self {
|
||||||
operation_id: Handler::operation_id(),
|
operation_id: Handler::operation_id(),
|
||||||
default_status: Handler::Res::default_status(),
|
default_status: Handler::Res::default_status(),
|
||||||
|
@ -109,31 +92,25 @@ impl<'a> OperationDescription<'a>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_path_param(mut self, name : &'a str, schema : ReferenceOr<Schema>) -> Self
|
pub fn add_path_param(mut self, name: &'a str, schema: ReferenceOr<Schema>) -> Self {
|
||||||
{
|
|
||||||
self.params.path_params.push((name, schema));
|
self.params.path_params.push((name, schema));
|
||||||
self
|
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.params.query_params = Some(params);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_body<Body : RequestBody>(mut self, schema : ReferenceOr<Schema>) -> Self
|
pub fn with_body<Body: RequestBody>(mut self, schema: ReferenceOr<Schema>) -> Self {
|
||||||
{
|
|
||||||
self.body_schema = Some(schema);
|
self.body_schema = Some(schema);
|
||||||
self.supported_types = Body::supported_types();
|
self.supported_types = Body::supported_types();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn schema_to_content(types: Vec<Mime>, schema: ReferenceOr<Schema>) -> IndexMap<String, MediaType> {
|
||||||
fn schema_to_content(types : Vec<Mime>, schema : ReferenceOr<Schema>) -> IndexMap<String, MediaType>
|
let mut content: IndexMap<String, MediaType> = IndexMap::new();
|
||||||
{
|
for ty in types {
|
||||||
let mut content : IndexMap<String, MediaType> = IndexMap::new();
|
|
||||||
for ty in types
|
|
||||||
{
|
|
||||||
content.insert(ty.to_string(), MediaType {
|
content.insert(ty.to_string(), MediaType {
|
||||||
schema: Some(schema.clone()),
|
schema: Some(schema.clone()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -142,30 +119,41 @@ impl<'a> OperationDescription<'a>
|
||||||
content
|
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
|
// 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) = (
|
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 content = Self::schema_to_content(accepted_types.or_all_types(), schema);
|
||||||
|
|
||||||
let mut responses : IndexMap<StatusCode, ReferenceOr<Response>> = IndexMap::new();
|
let mut responses: IndexMap<StatusCode, ReferenceOr<Response>> = IndexMap::new();
|
||||||
responses.insert(StatusCode::Code(default_status.as_u16()), Item(Response {
|
responses.insert(
|
||||||
description: default_status.canonical_reason().map(|d| d.to_string()).unwrap_or_default(),
|
StatusCode::Code(default_status.as_u16()),
|
||||||
content,
|
Item(Response {
|
||||||
..Default::default()
|
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 {
|
let request_body = body_schema.map(|schema| {
|
||||||
description: None,
|
Item(OARequestBody {
|
||||||
content: Self::schema_to_content(supported_types.or_all_types(), schema),
|
description: None,
|
||||||
required: true
|
content: Self::schema_to_content(supported_types.or_all_types(), schema),
|
||||||
}));
|
required: true
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
let mut security = Vec::new();
|
let mut security = Vec::new();
|
||||||
if requires_auth
|
if requires_auth {
|
||||||
{
|
|
||||||
let mut sec = IndexMap::new();
|
let mut sec = IndexMap::new();
|
||||||
sec.insert(SECURITY_NAME.to_owned(), Vec::new());
|
sec.insert(SECURITY_NAME.to_owned(), Vec::new());
|
||||||
security.push(sec);
|
security.push(sec);
|
||||||
|
@ -187,16 +175,13 @@ impl<'a> OperationDescription<'a>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test
|
mod test {
|
||||||
{
|
|
||||||
use crate::{OpenapiType, ResourceResult};
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::{OpenapiType, ResourceResult};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn no_content_schema_to_content()
|
fn no_content_schema_to_content() {
|
||||||
{
|
|
||||||
let types = NoContent::accepted_types();
|
let types = NoContent::accepted_types();
|
||||||
let schema = <NoContent as OpenapiType>::schema();
|
let schema = <NoContent as OpenapiType>::schema();
|
||||||
let content = OperationDescription::schema_to_content(types.or_all_types(), Item(schema.into_schema()));
|
let content = OperationDescription::schema_to_content(types.or_all_types(), Item(schema.into_schema()));
|
||||||
|
@ -204,8 +189,7 @@ mod test
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn raw_schema_to_content()
|
fn raw_schema_to_content() {
|
||||||
{
|
|
||||||
let types = Raw::<&str>::accepted_types();
|
let types = Raw::<&str>::accepted_types();
|
||||||
let schema = <Raw<&str> as OpenapiType>::schema();
|
let schema = <Raw<&str> as OpenapiType>::schema();
|
||||||
let content = OperationDescription::schema_to_content(types.or_all_types(), Item(schema.into_schema()));
|
let content = OperationDescription::schema_to_content(types.or_all_types(), Item(schema.into_schema()));
|
||||||
|
|
|
@ -1,40 +1,30 @@
|
||||||
use crate::{
|
|
||||||
resource::*,
|
|
||||||
routing::*,
|
|
||||||
OpenapiType,
|
|
||||||
};
|
|
||||||
use super::{builder::OpenapiBuilder, handler::OpenapiHandler, operation::OperationDescription};
|
use super::{builder::OpenapiBuilder, handler::OpenapiHandler, operation::OperationDescription};
|
||||||
use gotham::{
|
use crate::{resource::*, routing::*, OpenapiType};
|
||||||
pipeline::chain::PipelineHandleChain,
|
use gotham::{pipeline::chain::PipelineHandleChain, router::builder::*};
|
||||||
router::builder::*
|
|
||||||
};
|
|
||||||
use std::panic::RefUnwindSafe;
|
use std::panic::RefUnwindSafe;
|
||||||
|
|
||||||
/// This trait adds the `get_openapi` method to an OpenAPI-aware router.
|
/// This trait adds the `get_openapi` method to an OpenAPI-aware router.
|
||||||
pub trait GetOpenapi
|
pub trait GetOpenapi {
|
||||||
{
|
fn get_openapi(&mut self, path: &str);
|
||||||
fn get_openapi(&mut self, path : &str);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct OpenapiRouter<'a, D>
|
pub struct OpenapiRouter<'a, D> {
|
||||||
{
|
pub(crate) router: &'a mut D,
|
||||||
pub(crate) router : &'a mut D,
|
pub(crate) scope: Option<&'a str>,
|
||||||
pub(crate) scope : Option<&'a str>,
|
pub(crate) openapi_builder: &'a mut OpenapiBuilder
|
||||||
pub(crate) openapi_builder : &'a mut OpenapiBuilder
|
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! implOpenapiRouter {
|
macro_rules! implOpenapiRouter {
|
||||||
($implType:ident) => {
|
($implType:ident) => {
|
||||||
|
|
||||||
impl<'a, 'b, C, P> OpenapiRouter<'a, $implType<'b, C, P>>
|
impl<'a, 'b, C, P> OpenapiRouter<'a, $implType<'b, C, P>>
|
||||||
where
|
where
|
||||||
C : PipelineHandleChain<P> + Copy + Send + Sync + 'static,
|
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
|
||||||
P : RefUnwindSafe + Send + Sync + 'static
|
P: RefUnwindSafe + Send + Sync + 'static
|
||||||
{
|
{
|
||||||
pub fn scope<F>(&mut self, path : &str, callback : F)
|
pub fn scope<F>(&mut self, path: &str, callback: F)
|
||||||
where
|
where
|
||||||
F : FnOnce(&mut OpenapiRouter<'_, ScopeBuilder<'_, C, P>>)
|
F: FnOnce(&mut OpenapiRouter<'_, ScopeBuilder<'_, C, P>>)
|
||||||
{
|
{
|
||||||
let mut openapi_builder = self.openapi_builder.clone();
|
let mut openapi_builder = self.openapi_builder.clone();
|
||||||
let new_scope = self.scope.map(|scope| format!("{}/{}", scope, path).replace("//", "/"));
|
let new_scope = self.scope.map(|scope| format!("{}/{}", scope, path).replace("//", "/"));
|
||||||
|
@ -51,33 +41,32 @@ macro_rules! implOpenapiRouter {
|
||||||
|
|
||||||
impl<'a, 'b, C, P> GetOpenapi for OpenapiRouter<'a, $implType<'b, C, P>>
|
impl<'a, 'b, C, P> GetOpenapi for OpenapiRouter<'a, $implType<'b, C, P>>
|
||||||
where
|
where
|
||||||
C : PipelineHandleChain<P> + Copy + Send + Sync + 'static,
|
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
|
||||||
P : RefUnwindSafe + Send + Sync + 'static
|
P: RefUnwindSafe + Send + Sync + 'static
|
||||||
{
|
{
|
||||||
fn get_openapi(&mut self, path : &str)
|
fn get_openapi(&mut self, path: &str) {
|
||||||
{
|
self.router
|
||||||
self.router.get(path).to_new_handler(OpenapiHandler::new(self.openapi_builder.openapi.clone()));
|
.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>>
|
impl<'a, 'b, C, P> DrawResources for OpenapiRouter<'a, $implType<'b, C, P>>
|
||||||
where
|
where
|
||||||
C : PipelineHandleChain<P> + Copy + Send + Sync + 'static,
|
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
|
||||||
P : RefUnwindSafe + Send + Sync + 'static
|
P: RefUnwindSafe + Send + Sync + 'static
|
||||||
{
|
{
|
||||||
fn resource<R : Resource>(&mut self, path : &str)
|
fn resource<R: Resource>(&mut self, path: &str) {
|
||||||
{
|
|
||||||
R::setup((self, path));
|
R::setup((self, path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b, C, P> DrawResourceRoutes for (&mut OpenapiRouter<'a, $implType<'b, C, P>>, &str)
|
impl<'a, 'b, C, P> DrawResourceRoutes for (&mut OpenapiRouter<'a, $implType<'b, C, P>>, &str)
|
||||||
where
|
where
|
||||||
C : PipelineHandleChain<P> + Copy + Send + Sync + 'static,
|
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
|
||||||
P : RefUnwindSafe + Send + Sync + 'static
|
P: RefUnwindSafe + Send + Sync + 'static
|
||||||
{
|
{
|
||||||
fn read_all<Handler : ResourceReadAll>(&mut self)
|
fn read_all<Handler: ResourceReadAll>(&mut self) {
|
||||||
{
|
|
||||||
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
|
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
|
||||||
|
|
||||||
let path = format!("{}/{}", self.0.scope.unwrap_or_default(), self.1);
|
let path = format!("{}/{}", self.0.scope.unwrap_or_default(), self.1);
|
||||||
|
@ -88,67 +77,81 @@ macro_rules! implOpenapiRouter {
|
||||||
(&mut *(self.0).router, self.1).read_all::<Handler>()
|
(&mut *(self.0).router, self.1).read_all::<Handler>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read<Handler : ResourceRead>(&mut self)
|
fn read<Handler: ResourceRead>(&mut self) {
|
||||||
{
|
|
||||||
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
|
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
|
||||||
let id_schema = (self.0).openapi_builder.add_schema::<Handler::ID>();
|
let id_schema = (self.0).openapi_builder.add_schema::<Handler::ID>();
|
||||||
|
|
||||||
let path = format!("{}/{}/{{id}}", self.0.scope.unwrap_or_default(), self.1);
|
let path = format!("{}/{}/{{id}}", self.0.scope.unwrap_or_default(), self.1);
|
||||||
let mut item = (self.0).openapi_builder.remove_path(&path);
|
let mut item = (self.0).openapi_builder.remove_path(&path);
|
||||||
item.get = Some(OperationDescription::new::<Handler>(schema).add_path_param("id", id_schema).into_operation());
|
item.get = Some(
|
||||||
|
OperationDescription::new::<Handler>(schema)
|
||||||
|
.add_path_param("id", id_schema)
|
||||||
|
.into_operation()
|
||||||
|
);
|
||||||
(self.0).openapi_builder.add_path(path, item);
|
(self.0).openapi_builder.add_path(path, item);
|
||||||
|
|
||||||
(&mut *(self.0).router, self.1).read::<Handler>()
|
(&mut *(self.0).router, self.1).read::<Handler>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search<Handler : ResourceSearch>(&mut self)
|
fn search<Handler: ResourceSearch>(&mut self) {
|
||||||
{
|
|
||||||
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
|
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
|
||||||
|
|
||||||
let path = format!("{}/{}/search", self.0.scope.unwrap_or_default(), self.1);
|
let path = format!("{}/{}/search", self.0.scope.unwrap_or_default(), self.1);
|
||||||
let mut item = (self.0).openapi_builder.remove_path(&path);
|
let mut item = (self.0).openapi_builder.remove_path(&path);
|
||||||
item.get = Some(OperationDescription::new::<Handler>(schema).with_query_params(Handler::Query::schema()).into_operation());
|
item.get = Some(
|
||||||
|
OperationDescription::new::<Handler>(schema)
|
||||||
|
.with_query_params(Handler::Query::schema())
|
||||||
|
.into_operation()
|
||||||
|
);
|
||||||
(self.0).openapi_builder.add_path(path, item);
|
(self.0).openapi_builder.add_path(path, item);
|
||||||
|
|
||||||
(&mut *(self.0).router, self.1).search::<Handler>()
|
(&mut *(self.0).router, self.1).search::<Handler>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create<Handler : ResourceCreate>(&mut self)
|
fn create<Handler: ResourceCreate>(&mut self)
|
||||||
where
|
where
|
||||||
Handler::Res : 'static,
|
Handler::Res: 'static,
|
||||||
Handler::Body : 'static
|
Handler::Body: 'static
|
||||||
{
|
{
|
||||||
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
|
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
|
||||||
let body_schema = (self.0).openapi_builder.add_schema::<Handler::Body>();
|
let body_schema = (self.0).openapi_builder.add_schema::<Handler::Body>();
|
||||||
|
|
||||||
let path = format!("{}/{}", self.0.scope.unwrap_or_default(), self.1);
|
let path = format!("{}/{}", self.0.scope.unwrap_or_default(), self.1);
|
||||||
let mut item = (self.0).openapi_builder.remove_path(&path);
|
let mut item = (self.0).openapi_builder.remove_path(&path);
|
||||||
item.post = Some(OperationDescription::new::<Handler>(schema).with_body::<Handler::Body>(body_schema).into_operation());
|
item.post = Some(
|
||||||
|
OperationDescription::new::<Handler>(schema)
|
||||||
|
.with_body::<Handler::Body>(body_schema)
|
||||||
|
.into_operation()
|
||||||
|
);
|
||||||
(self.0).openapi_builder.add_path(path, item);
|
(self.0).openapi_builder.add_path(path, item);
|
||||||
|
|
||||||
(&mut *(self.0).router, self.1).create::<Handler>()
|
(&mut *(self.0).router, self.1).create::<Handler>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn change_all<Handler : ResourceChangeAll>(&mut self)
|
fn change_all<Handler: ResourceChangeAll>(&mut self)
|
||||||
where
|
where
|
||||||
Handler::Res : 'static,
|
Handler::Res: 'static,
|
||||||
Handler::Body : 'static
|
Handler::Body: 'static
|
||||||
{
|
{
|
||||||
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
|
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
|
||||||
let body_schema = (self.0).openapi_builder.add_schema::<Handler::Body>();
|
let body_schema = (self.0).openapi_builder.add_schema::<Handler::Body>();
|
||||||
|
|
||||||
let path = format!("{}/{}", self.0.scope.unwrap_or_default(), self.1);
|
let path = format!("{}/{}", self.0.scope.unwrap_or_default(), self.1);
|
||||||
let mut item = (self.0).openapi_builder.remove_path(&path);
|
let mut item = (self.0).openapi_builder.remove_path(&path);
|
||||||
item.put = Some(OperationDescription::new::<Handler>(schema).with_body::<Handler::Body>(body_schema).into_operation());
|
item.put = Some(
|
||||||
|
OperationDescription::new::<Handler>(schema)
|
||||||
|
.with_body::<Handler::Body>(body_schema)
|
||||||
|
.into_operation()
|
||||||
|
);
|
||||||
(self.0).openapi_builder.add_path(path, item);
|
(self.0).openapi_builder.add_path(path, item);
|
||||||
|
|
||||||
(&mut *(self.0).router, self.1).change_all::<Handler>()
|
(&mut *(self.0).router, self.1).change_all::<Handler>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn change<Handler : ResourceChange>(&mut self)
|
fn change<Handler: ResourceChange>(&mut self)
|
||||||
where
|
where
|
||||||
Handler::Res : 'static,
|
Handler::Res: 'static,
|
||||||
Handler::Body : 'static
|
Handler::Body: 'static
|
||||||
{
|
{
|
||||||
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
|
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
|
||||||
let id_schema = (self.0).openapi_builder.add_schema::<Handler::ID>();
|
let id_schema = (self.0).openapi_builder.add_schema::<Handler::ID>();
|
||||||
|
@ -156,14 +159,18 @@ macro_rules! implOpenapiRouter {
|
||||||
|
|
||||||
let path = format!("{}/{}/{{id}}", self.0.scope.unwrap_or_default(), self.1);
|
let path = format!("{}/{}/{{id}}", self.0.scope.unwrap_or_default(), self.1);
|
||||||
let mut item = (self.0).openapi_builder.remove_path(&path);
|
let mut item = (self.0).openapi_builder.remove_path(&path);
|
||||||
item.put = Some(OperationDescription::new::<Handler>(schema).add_path_param("id", id_schema).with_body::<Handler::Body>(body_schema).into_operation());
|
item.put = Some(
|
||||||
|
OperationDescription::new::<Handler>(schema)
|
||||||
|
.add_path_param("id", id_schema)
|
||||||
|
.with_body::<Handler::Body>(body_schema)
|
||||||
|
.into_operation()
|
||||||
|
);
|
||||||
(self.0).openapi_builder.add_path(path, item);
|
(self.0).openapi_builder.add_path(path, item);
|
||||||
|
|
||||||
(&mut *(self.0).router, self.1).change::<Handler>()
|
(&mut *(self.0).router, self.1).change::<Handler>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_all<Handler : ResourceRemoveAll>(&mut self)
|
fn remove_all<Handler: ResourceRemoveAll>(&mut self) {
|
||||||
{
|
|
||||||
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
|
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
|
||||||
|
|
||||||
let path = format!("{}/{}", self.0.scope.unwrap_or_default(), self.1);
|
let path = format!("{}/{}", self.0.scope.unwrap_or_default(), self.1);
|
||||||
|
@ -174,21 +181,23 @@ macro_rules! implOpenapiRouter {
|
||||||
(&mut *(self.0).router, self.1).remove_all::<Handler>()
|
(&mut *(self.0).router, self.1).remove_all::<Handler>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove<Handler : ResourceRemove>(&mut self)
|
fn remove<Handler: ResourceRemove>(&mut self) {
|
||||||
{
|
|
||||||
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
|
let schema = (self.0).openapi_builder.add_schema::<Handler::Res>();
|
||||||
let id_schema = (self.0).openapi_builder.add_schema::<Handler::ID>();
|
let id_schema = (self.0).openapi_builder.add_schema::<Handler::ID>();
|
||||||
|
|
||||||
let path = format!("{}/{}/{{id}}", self.0.scope.unwrap_or_default(), self.1);
|
let path = format!("{}/{}/{{id}}", self.0.scope.unwrap_or_default(), self.1);
|
||||||
let mut item = (self.0).openapi_builder.remove_path(&path);
|
let mut item = (self.0).openapi_builder.remove_path(&path);
|
||||||
item.delete = Some(OperationDescription::new::<Handler>(schema).add_path_param("id", id_schema).into_operation());
|
item.delete = Some(
|
||||||
|
OperationDescription::new::<Handler>(schema)
|
||||||
|
.add_path_param("id", id_schema)
|
||||||
|
.into_operation()
|
||||||
|
);
|
||||||
(self.0).openapi_builder.add_path(path, item);
|
(self.0).openapi_builder.add_path(path, item);
|
||||||
|
|
||||||
(&mut *(self.0).router, self.1).remove::<Handler>()
|
(&mut *(self.0).router, self.1).remove::<Handler>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
implOpenapiRouter!(RouterBuilder);
|
implOpenapiRouter!(RouterBuilder);
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
#[cfg(feature = "chrono")]
|
#[cfg(feature = "chrono")]
|
||||||
use chrono::{
|
use chrono::{Date, DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, Utc};
|
||||||
Date, DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, Utc
|
|
||||||
};
|
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use openapiv3::{
|
use openapiv3::{
|
||||||
AdditionalProperties, ArrayType, IntegerType, NumberFormat, NumberType, ObjectType, ReferenceOr::Item,
|
AdditionalProperties, ArrayType, IntegerType, NumberFormat, NumberType, ObjectType,
|
||||||
ReferenceOr::Reference, Schema, SchemaData, SchemaKind, StringType, Type, VariantOrUnknownOrEmpty
|
ReferenceOr::{Item, Reference},
|
||||||
|
Schema, SchemaData, SchemaKind, StringType, Type, VariantOrUnknownOrEmpty
|
||||||
};
|
};
|
||||||
#[cfg(feature = "uuid")]
|
|
||||||
use uuid::Uuid;
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeSet, HashMap, HashSet},
|
collections::{BTreeSet, HashMap, HashSet},
|
||||||
hash::BuildHasher
|
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
|
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
|
[`OpenapiType`]: trait.OpenapiType.html
|
||||||
*/
|
*/
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct OpenapiSchema
|
pub struct OpenapiSchema {
|
||||||
{
|
|
||||||
/// The name of this schema. If it is None, the schema will be inlined.
|
/// The name of this schema. If it is None, the schema will be inlined.
|
||||||
pub name : Option<String>,
|
pub name: Option<String>,
|
||||||
/// Whether this particular schema is nullable. Note that there is no guarantee that this will
|
/// 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
|
/// make it into the final specification, it might just be interpreted as a hint to make it
|
||||||
/// an optional parameter.
|
/// an optional parameter.
|
||||||
pub nullable : bool,
|
pub nullable: bool,
|
||||||
/// The actual OpenAPI schema.
|
/// 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
|
/// Other schemas that this schema depends on. They will be included in the final OpenAPI Spec
|
||||||
/// along with this schema.
|
/// along with this schema.
|
||||||
pub dependencies : IndexMap<String, OpenapiSchema>
|
pub dependencies: IndexMap<String, OpenapiSchema>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OpenapiSchema
|
impl OpenapiSchema {
|
||||||
{
|
|
||||||
/// Create a new schema that has no name.
|
/// Create a new schema that has no name.
|
||||||
pub fn new(schema : SchemaKind) -> Self
|
pub fn new(schema: SchemaKind) -> Self {
|
||||||
{
|
|
||||||
Self {
|
Self {
|
||||||
name: None,
|
name: None,
|
||||||
nullable: false,
|
nullable: false,
|
||||||
|
@ -51,8 +47,7 @@ impl OpenapiSchema
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert this schema to an `openapiv3::Schema` that can be serialized to the OpenAPI Spec.
|
/// 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 {
|
||||||
schema_data: SchemaData {
|
schema_data: SchemaData {
|
||||||
nullable: self.nullable,
|
nullable: self.nullable,
|
||||||
|
@ -80,15 +75,12 @@ struct MyResponse {
|
||||||
|
|
||||||
[`OpenapiSchema`]: struct.OpenapiSchema.html
|
[`OpenapiSchema`]: struct.OpenapiSchema.html
|
||||||
*/
|
*/
|
||||||
pub trait OpenapiType
|
pub trait OpenapiType {
|
||||||
{
|
|
||||||
fn schema() -> OpenapiSchema;
|
fn schema() -> OpenapiSchema;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OpenapiType for ()
|
impl OpenapiType for () {
|
||||||
{
|
fn schema() -> OpenapiSchema {
|
||||||
fn schema() -> OpenapiSchema
|
|
||||||
{
|
|
||||||
OpenapiSchema::new(SchemaKind::Type(Type::Object(ObjectType {
|
OpenapiSchema::new(SchemaKind::Type(Type::Object(ObjectType {
|
||||||
additional_properties: Some(AdditionalProperties::Any(false)),
|
additional_properties: Some(AdditionalProperties::Any(false)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -96,11 +88,9 @@ impl OpenapiType for ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OpenapiType for bool
|
impl OpenapiType for bool {
|
||||||
{
|
fn schema() -> OpenapiSchema {
|
||||||
fn schema() -> OpenapiSchema
|
OpenapiSchema::new(SchemaKind::Type(Type::Boolean {}))
|
||||||
{
|
|
||||||
OpenapiSchema::new(SchemaKind::Type(Type::Boolean{}))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,20 +221,26 @@ str_types!(String, &str);
|
||||||
#[cfg(feature = "chrono")]
|
#[cfg(feature = "chrono")]
|
||||||
str_types!(format = Date, Date<FixedOffset>, Date<Local>, Date<Utc>, NaiveDate);
|
str_types!(format = Date, Date<FixedOffset>, Date<Local>, Date<Utc>, NaiveDate);
|
||||||
#[cfg(feature = "chrono")]
|
#[cfg(feature = "chrono")]
|
||||||
str_types!(format = DateTime, DateTime<FixedOffset>, DateTime<Local>, DateTime<Utc>, NaiveDateTime);
|
str_types!(
|
||||||
|
format = DateTime,
|
||||||
|
DateTime<FixedOffset>,
|
||||||
|
DateTime<Local>,
|
||||||
|
DateTime<Utc>,
|
||||||
|
NaiveDateTime
|
||||||
|
);
|
||||||
|
|
||||||
#[cfg(feature = "uuid")]
|
#[cfg(feature = "uuid")]
|
||||||
str_types!(format_str = "uuid", Uuid);
|
str_types!(format_str = "uuid", Uuid);
|
||||||
|
|
||||||
impl<T : OpenapiType> OpenapiType for Option<T>
|
impl<T: OpenapiType> OpenapiType for Option<T> {
|
||||||
{
|
fn schema() -> OpenapiSchema {
|
||||||
fn schema() -> OpenapiSchema
|
|
||||||
{
|
|
||||||
let schema = T::schema();
|
let schema = T::schema();
|
||||||
let mut dependencies = schema.dependencies.clone();
|
let mut dependencies = schema.dependencies.clone();
|
||||||
let schema = match schema.name.clone() {
|
let schema = match schema.name.clone() {
|
||||||
Some(name) => {
|
Some(name) => {
|
||||||
let reference = Reference { reference: format!("#/components/schemas/{}", name) };
|
let reference = Reference {
|
||||||
|
reference: format!("#/components/schemas/{}", name)
|
||||||
|
};
|
||||||
dependencies.insert(name, schema);
|
dependencies.insert(name, schema);
|
||||||
SchemaKind::AllOf { all_of: vec![reference] }
|
SchemaKind::AllOf { all_of: vec![reference] }
|
||||||
},
|
},
|
||||||
|
@ -260,16 +256,16 @@ impl<T : OpenapiType> OpenapiType for Option<T>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T : OpenapiType> OpenapiType for Vec<T>
|
impl<T: OpenapiType> OpenapiType for Vec<T> {
|
||||||
{
|
fn schema() -> OpenapiSchema {
|
||||||
fn schema() -> OpenapiSchema
|
|
||||||
{
|
|
||||||
let schema = T::schema();
|
let schema = T::schema();
|
||||||
let mut dependencies = schema.dependencies.clone();
|
let mut dependencies = schema.dependencies.clone();
|
||||||
|
|
||||||
let items = match schema.name.clone() {
|
let items = match schema.name.clone() {
|
||||||
Some(name) => {
|
Some(name) => {
|
||||||
let reference = Reference { reference: format!("#/components/schemas/{}", name) };
|
let reference = Reference {
|
||||||
|
reference: format!("#/components/schemas/{}", name)
|
||||||
|
};
|
||||||
dependencies.insert(name, schema);
|
dependencies.insert(name, schema);
|
||||||
reference
|
reference
|
||||||
},
|
},
|
||||||
|
@ -290,32 +286,28 @@ impl<T : OpenapiType> OpenapiType for Vec<T>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T : OpenapiType> OpenapiType for BTreeSet<T>
|
impl<T: OpenapiType> OpenapiType for BTreeSet<T> {
|
||||||
{
|
fn schema() -> OpenapiSchema {
|
||||||
fn schema() -> OpenapiSchema
|
|
||||||
{
|
|
||||||
<Vec<T> as OpenapiType>::schema()
|
<Vec<T> as OpenapiType>::schema()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T : OpenapiType, S : BuildHasher> OpenapiType for HashSet<T, S>
|
impl<T: OpenapiType, S: BuildHasher> OpenapiType for HashSet<T, S> {
|
||||||
{
|
fn schema() -> OpenapiSchema {
|
||||||
fn schema() -> OpenapiSchema
|
|
||||||
{
|
|
||||||
<Vec<T> as OpenapiType>::schema()
|
<Vec<T> as OpenapiType>::schema()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K, T : OpenapiType, S : BuildHasher> OpenapiType for HashMap<K, T, S>
|
impl<K, T: OpenapiType, S: BuildHasher> OpenapiType for HashMap<K, T, S> {
|
||||||
{
|
fn schema() -> OpenapiSchema {
|
||||||
fn schema() -> OpenapiSchema
|
|
||||||
{
|
|
||||||
let schema = T::schema();
|
let schema = T::schema();
|
||||||
let mut dependencies = schema.dependencies.clone();
|
let mut dependencies = schema.dependencies.clone();
|
||||||
|
|
||||||
let items = Box::new(match schema.name.clone() {
|
let items = Box::new(match schema.name.clone() {
|
||||||
Some(name) => {
|
Some(name) => {
|
||||||
let reference = Reference { reference: format!("#/components/schemas/{}", name) };
|
let reference = Reference {
|
||||||
|
reference: format!("#/components/schemas/{}", name)
|
||||||
|
};
|
||||||
dependencies.insert(name, schema);
|
dependencies.insert(name, schema);
|
||||||
reference
|
reference
|
||||||
},
|
},
|
||||||
|
@ -334,10 +326,8 @@ impl<K, T : OpenapiType, S : BuildHasher> OpenapiType for HashMap<K, T, S>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OpenapiType for serde_json::Value
|
impl OpenapiType for serde_json::Value {
|
||||||
{
|
fn schema() -> OpenapiSchema {
|
||||||
fn schema() -> OpenapiSchema
|
|
||||||
{
|
|
||||||
OpenapiSchema {
|
OpenapiSchema {
|
||||||
nullable: true,
|
nullable: true,
|
||||||
name: None,
|
name: None,
|
||||||
|
@ -347,10 +337,8 @@ impl OpenapiType for serde_json::Value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test
|
mod test {
|
||||||
{
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,14 @@
|
||||||
use crate::{DrawResourceRoutes, RequestBody, ResourceID, ResourceResult, ResourceType};
|
use crate::{DrawResourceRoutes, RequestBody, ResourceID, ResourceResult, ResourceType};
|
||||||
use gotham::{
|
use gotham::{extractor::QueryStringExtractor, hyper::Body, state::State};
|
||||||
extractor::QueryStringExtractor,
|
use std::{future::Future, pin::Pin};
|
||||||
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
|
/// 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.
|
/// 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)]`.
|
/// 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.
|
/// Register all methods handled by this resource with the underlying router.
|
||||||
fn setup<D : DrawResourceRoutes>(route : D);
|
fn setup<D: DrawResourceRoutes>(route: D);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A common trait for every resource method. It defines the return type as well as some general
|
/// 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
|
/// It is not recommended to implement this yourself. Rather, just write your handler method and
|
||||||
/// annotate it with `#[<method>(YourResource)]`, where `<method>` is one of the supported
|
/// annotate it with `#[<method>(YourResource)]`, where `<method>` is one of the supported
|
||||||
/// resource methods.
|
/// resource methods.
|
||||||
pub trait ResourceMethod
|
pub trait ResourceMethod {
|
||||||
{
|
type Res: ResourceResult + Send + 'static;
|
||||||
type Res : ResourceResult + Send + 'static;
|
|
||||||
|
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
fn operation_id() -> Option<String>
|
fn operation_id() -> Option<String> {
|
||||||
{
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wants_auth() -> bool
|
fn wants_auth() -> bool {
|
||||||
{
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The read_all [`ResourceMethod`](trait.ResourceMethod.html).
|
/// The read_all [`ResourceMethod`](trait.ResourceMethod.html).
|
||||||
pub trait ResourceReadAll : ResourceMethod
|
pub trait ResourceReadAll: ResourceMethod {
|
||||||
{
|
|
||||||
/// Handle a GET request on the Resource root.
|
/// Handle a GET request on the Resource root.
|
||||||
fn read_all(state : State) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
|
fn read_all(state: State) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The read [`ResourceMethod`](trait.ResourceMethod.html).
|
/// The read [`ResourceMethod`](trait.ResourceMethod.html).
|
||||||
pub trait ResourceRead : ResourceMethod
|
pub trait ResourceRead: ResourceMethod {
|
||||||
{
|
|
||||||
/// The ID type to be parsed from the request path.
|
/// 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.
|
/// Handle a GET request on the Resource with an id.
|
||||||
fn read(state : State, id : Self::ID) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
|
fn read(state: State, id: Self::ID) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The search [`ResourceMethod`](trait.ResourceMethod.html).
|
/// The search [`ResourceMethod`](trait.ResourceMethod.html).
|
||||||
pub trait ResourceSearch : ResourceMethod
|
pub trait ResourceSearch: ResourceMethod {
|
||||||
{
|
|
||||||
/// The Query type to be parsed from the request parameters.
|
/// The Query type to be parsed from the request parameters.
|
||||||
type Query : ResourceType + QueryStringExtractor<Body> + Sync;
|
type Query: ResourceType + QueryStringExtractor<Body> + Sync;
|
||||||
|
|
||||||
/// Handle a GET request on the Resource with additional search parameters.
|
/// Handle a GET request on the Resource with additional search parameters.
|
||||||
fn search(state : State, query : Self::Query) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
|
fn search(state: State, query: Self::Query) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The create [`ResourceMethod`](trait.ResourceMethod.html).
|
/// The create [`ResourceMethod`](trait.ResourceMethod.html).
|
||||||
pub trait ResourceCreate : ResourceMethod
|
pub trait ResourceCreate: ResourceMethod {
|
||||||
{
|
|
||||||
/// The Body type to be parsed from the request body.
|
/// The Body type to be parsed from the request body.
|
||||||
type Body : RequestBody;
|
type Body: RequestBody;
|
||||||
|
|
||||||
/// Handle a POST request on the Resource root.
|
/// Handle a POST request on the Resource root.
|
||||||
fn create(state : State, body : Self::Body) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
|
fn create(state: State, body: Self::Body) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The change_all [`ResourceMethod`](trait.ResourceMethod.html).
|
/// 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.
|
/// The Body type to be parsed from the request body.
|
||||||
type Body : RequestBody;
|
type Body: RequestBody;
|
||||||
|
|
||||||
/// Handle a PUT request on the Resource root.
|
/// Handle a PUT request on the Resource root.
|
||||||
fn change_all(state : State, body : Self::Body) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
|
fn change_all(state: State, body: Self::Body) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The change [`ResourceMethod`](trait.ResourceMethod.html).
|
/// The change [`ResourceMethod`](trait.ResourceMethod.html).
|
||||||
pub trait ResourceChange : ResourceMethod
|
pub trait ResourceChange: ResourceMethod {
|
||||||
{
|
|
||||||
/// The Body type to be parsed from the request body.
|
/// 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.
|
/// 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.
|
/// Handle a PUT request on the Resource with an id.
|
||||||
fn change(state : State, id : Self::ID, body : Self::Body) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
|
fn change(state: State, id: Self::ID, body: Self::Body) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The remove_all [`ResourceMethod`](trait.ResourceMethod.html).
|
/// The remove_all [`ResourceMethod`](trait.ResourceMethod.html).
|
||||||
pub trait ResourceRemoveAll : ResourceMethod
|
pub trait ResourceRemoveAll: ResourceMethod {
|
||||||
{
|
|
||||||
/// Handle a DELETE request on the Resource root.
|
/// Handle a DELETE request on the Resource root.
|
||||||
fn remove_all(state : State) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
|
fn remove_all(state: State) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The remove [`ResourceMethod`](trait.ResourceMethod.html).
|
/// The remove [`ResourceMethod`](trait.ResourceMethod.html).
|
||||||
pub trait ResourceRemove : ResourceMethod
|
pub trait ResourceRemove: ResourceMethod {
|
||||||
{
|
|
||||||
/// The ID type to be parsed from the request path.
|
/// 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.
|
/// Handle a DELETE request on the Resource with an id.
|
||||||
fn remove(state : State, id : Self::ID) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
|
fn remove(state: State, id: Self::ID) -> Pin<Box<dyn Future<Output = (State, Self::Res)> + Send>>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,18 +3,15 @@ use mime::{Mime, APPLICATION_JSON};
|
||||||
|
|
||||||
/// A response, used to create the final gotham response from.
|
/// A response, used to create the final gotham response from.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Response
|
pub struct Response {
|
||||||
{
|
pub status: StatusCode,
|
||||||
pub status : StatusCode,
|
pub body: Body,
|
||||||
pub body : Body,
|
pub mime: Option<Mime>
|
||||||
pub mime : Option<Mime>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Response
|
impl Response {
|
||||||
{
|
|
||||||
/// Create a new `Response` from raw data.
|
/// Create a new `Response` from raw data.
|
||||||
pub fn new<B : Into<Body>>(status : StatusCode, body : B, mime : Option<Mime>) -> Self
|
pub fn new<B: Into<Body>>(status: StatusCode, body: B, mime: Option<Mime>) -> Self {
|
||||||
{
|
|
||||||
Self {
|
Self {
|
||||||
status,
|
status,
|
||||||
body: body.into(),
|
body: body.into(),
|
||||||
|
@ -23,8 +20,7 @@ impl Response
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a `Response` with mime type json from already serialized data.
|
/// Create a `Response` with mime type json from already serialized data.
|
||||||
pub fn json<B : Into<Body>>(status : StatusCode, body : B) -> Self
|
pub fn json<B: Into<Body>>(status: StatusCode, body: B) -> Self {
|
||||||
{
|
|
||||||
Self {
|
Self {
|
||||||
status,
|
status,
|
||||||
body: body.into(),
|
body: body.into(),
|
||||||
|
@ -33,8 +29,7 @@ impl Response
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a _204 No Content_ `Response`.
|
/// Create a _204 No Content_ `Response`.
|
||||||
pub fn no_content() -> Self
|
pub fn no_content() -> Self {
|
||||||
{
|
|
||||||
Self {
|
Self {
|
||||||
status: StatusCode::NO_CONTENT,
|
status: StatusCode::NO_CONTENT,
|
||||||
body: Body::empty(),
|
body: Body::empty(),
|
||||||
|
@ -43,8 +38,7 @@ impl Response
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create an empty _403 Forbidden_ `Response`.
|
/// Create an empty _403 Forbidden_ `Response`.
|
||||||
pub fn forbidden() -> Self
|
pub fn forbidden() -> Self {
|
||||||
{
|
|
||||||
Self {
|
Self {
|
||||||
status: StatusCode::FORBIDDEN,
|
status: StatusCode::FORBIDDEN,
|
||||||
body: Body::empty(),
|
body: Body::empty(),
|
||||||
|
@ -53,12 +47,11 @@ impl Response
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) fn full_body(mut self) -> Result<Vec<u8>, <Body as gotham::hyper::body::HttpBody>::Error>
|
pub(crate) fn full_body(mut self) -> Result<Vec<u8>, <Body as gotham::hyper::body::HttpBody>::Error> {
|
||||||
{
|
|
||||||
use futures_executor::block_on;
|
use futures_executor::block_on;
|
||||||
use gotham::hyper::body::to_bytes;
|
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())
|
Ok(bytes.to_vec())
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
use gotham_restful_derive::ResourceError;
|
use gotham_restful_derive::ResourceError;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
This is an error type that always yields a _403 Forbidden_ response. This type is best used in
|
This is an error type that always yields a _403 Forbidden_ response. This type is best used in
|
||||||
combination with [`AuthSuccess`] or [`AuthResult`].
|
combination with [`AuthSuccess`] or [`AuthResult`].
|
||||||
|
@ -9,8 +8,7 @@ combination with [`AuthSuccess`] or [`AuthResult`].
|
||||||
[`AuthResult`]: type.AuthResult.html
|
[`AuthResult`]: type.AuthResult.html
|
||||||
*/
|
*/
|
||||||
#[derive(Debug, Clone, Copy, ResourceError)]
|
#[derive(Debug, Clone, Copy, ResourceError)]
|
||||||
pub enum AuthError
|
pub enum AuthError {
|
||||||
{
|
|
||||||
#[status(FORBIDDEN)]
|
#[status(FORBIDDEN)]
|
||||||
#[display("Forbidden")]
|
#[display("Forbidden")]
|
||||||
Forbidden
|
Forbidden
|
||||||
|
@ -57,8 +55,7 @@ error, or delegates to another error type. This type is best used with [`AuthRes
|
||||||
[`AuthResult`]: type.AuthResult.html
|
[`AuthResult`]: type.AuthResult.html
|
||||||
*/
|
*/
|
||||||
#[derive(Debug, ResourceError)]
|
#[derive(Debug, ResourceError)]
|
||||||
pub enum AuthErrorOrOther<E>
|
pub enum AuthErrorOrOther<E> {
|
||||||
{
|
|
||||||
#[status(FORBIDDEN)]
|
#[status(FORBIDDEN)]
|
||||||
#[display("Forbidden")]
|
#[display("Forbidden")]
|
||||||
Forbidden,
|
Forbidden,
|
||||||
|
@ -67,10 +64,8 @@ pub enum AuthErrorOrOther<E>
|
||||||
Other(E)
|
Other(E)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E> From<AuthError> for AuthErrorOrOther<E>
|
impl<E> From<AuthError> for AuthErrorOrOther<E> {
|
||||||
{
|
fn from(err: AuthError) -> Self {
|
||||||
fn from(err : AuthError) -> Self
|
|
||||||
{
|
|
||||||
match err {
|
match err {
|
||||||
AuthError::Forbidden => Self::Forbidden
|
AuthError::Forbidden => Self::Forbidden
|
||||||
}
|
}
|
||||||
|
@ -80,10 +75,9 @@ impl<E> From<AuthError> for AuthErrorOrOther<E>
|
||||||
impl<E, F> From<F> for AuthErrorOrOther<E>
|
impl<E, F> From<F> for AuthErrorOrOther<E>
|
||||||
where
|
where
|
||||||
// TODO https://gitlab.com/msrd0/gotham-restful/-/issues/20
|
// TODO https://gitlab.com/msrd0/gotham-restful/-/issues/20
|
||||||
F : std::error::Error + Into<E>
|
F: std::error::Error + Into<E>
|
||||||
{
|
{
|
||||||
fn from(err : F) -> Self
|
fn from(err: F) -> Self {
|
||||||
{
|
|
||||||
Self::Other(err.into())
|
Self::Other(err.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
use crate::Response;
|
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
use crate::OpenapiSchema;
|
use crate::OpenapiSchema;
|
||||||
|
use crate::Response;
|
||||||
use futures_util::future::FutureExt;
|
use futures_util::future::FutureExt;
|
||||||
use mime::{Mime, STAR_STAR};
|
use mime::{Mime, STAR_STAR};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::{
|
use std::{
|
||||||
error::Error,
|
error::Error,
|
||||||
future::Future,
|
|
||||||
fmt::{Debug, Display},
|
fmt::{Debug, Display},
|
||||||
|
future::Future,
|
||||||
pin::Pin
|
pin::Pin
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -27,33 +27,26 @@ pub use result::IntoResponseError;
|
||||||
mod success;
|
mod success;
|
||||||
pub use success::Success;
|
pub use success::Success;
|
||||||
|
|
||||||
|
pub(crate) trait OrAllTypes {
|
||||||
pub(crate) trait OrAllTypes
|
|
||||||
{
|
|
||||||
fn or_all_types(self) -> Vec<Mime>;
|
fn or_all_types(self) -> Vec<Mime>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OrAllTypes for Option<Vec<Mime>>
|
impl OrAllTypes for Option<Vec<Mime>> {
|
||||||
{
|
fn or_all_types(self) -> Vec<Mime> {
|
||||||
fn or_all_types(self) -> Vec<Mime>
|
|
||||||
{
|
|
||||||
self.unwrap_or_else(|| vec![STAR_STAR])
|
self.unwrap_or_else(|| vec![STAR_STAR])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// A trait provided to convert a resource's result to json.
|
/// A trait provided to convert a resource's result to json.
|
||||||
pub trait ResourceResult
|
pub trait ResourceResult {
|
||||||
{
|
type Err: Error + Send + Sync + 'static;
|
||||||
type Err : Error + Send + 'static;
|
|
||||||
|
|
||||||
/// Turn this into a response that can be returned to the browser. This api will likely
|
/// Turn this into a response that can be returned to the browser. This api will likely
|
||||||
/// change in the future.
|
/// change in the future.
|
||||||
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, Self::Err>> + Send>>;
|
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, Self::Err>> + Send>>;
|
||||||
|
|
||||||
/// Return a list of supported mime types.
|
/// Return a list of supported mime types.
|
||||||
fn accepted_types() -> Option<Vec<Mime>>
|
fn accepted_types() -> Option<Vec<Mime>> {
|
||||||
{
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,33 +54,27 @@ pub trait ResourceResult
|
||||||
fn schema() -> OpenapiSchema;
|
fn schema() -> OpenapiSchema;
|
||||||
|
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
fn default_status() -> crate::StatusCode
|
fn default_status() -> crate::StatusCode {
|
||||||
{
|
|
||||||
crate::StatusCode::OK
|
crate::StatusCode::OK
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
impl<Res : ResourceResult> crate::OpenapiType for Res
|
impl<Res: ResourceResult> crate::OpenapiType for Res {
|
||||||
{
|
fn schema() -> OpenapiSchema {
|
||||||
fn schema() -> OpenapiSchema
|
|
||||||
{
|
|
||||||
Self::schema()
|
Self::schema()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The default json returned on an 500 Internal Server Error.
|
/// The default json returned on an 500 Internal Server Error.
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub(crate) struct ResourceError
|
pub(crate) struct ResourceError {
|
||||||
{
|
error: bool,
|
||||||
error : bool,
|
message: String
|
||||||
message : String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T : ToString> From<T> for ResourceError
|
impl<T: ToString> From<T> for ResourceError {
|
||||||
{
|
fn from(message: T) -> Self {
|
||||||
fn from(message : T) -> Self
|
|
||||||
{
|
|
||||||
Self {
|
Self {
|
||||||
error: true,
|
error: true,
|
||||||
message: message.to_string()
|
message: message.to_string()
|
||||||
|
@ -95,27 +82,26 @@ impl<T : ToString> From<T> for ResourceError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_response_helper<Err, F>(create_response : F) -> Pin<Box<dyn Future<Output = Result<Response, Err>> + Send>>
|
fn into_response_helper<Err, F>(create_response: F) -> Pin<Box<dyn Future<Output = Result<Response, Err>> + Send>>
|
||||||
where
|
where
|
||||||
Err : Send + 'static,
|
Err: Send + 'static,
|
||||||
F : FnOnce() -> Result<Response, Err>
|
F: FnOnce() -> Result<Response, Err>
|
||||||
{
|
{
|
||||||
let res = create_response();
|
let res = create_response();
|
||||||
async move { res }.boxed()
|
async move { res }.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "errorlog")]
|
#[cfg(feature = "errorlog")]
|
||||||
fn errorlog<E : Display>(e : E)
|
fn errorlog<E: Display>(e: E) {
|
||||||
{
|
|
||||||
error!("The handler encountered an error: {}", e);
|
error!("The handler encountered an error: {}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "errorlog"))]
|
#[cfg(not(feature = "errorlog"))]
|
||||||
fn errorlog<E>(_e : E) {}
|
fn errorlog<E>(_e: E) {}
|
||||||
|
|
||||||
fn handle_error<E>(e : E) -> Pin<Box<dyn Future<Output = Result<Response, E::Err>> + Send>>
|
fn handle_error<E>(e: E) -> Pin<Box<dyn Future<Output = Result<Response, E::Err>> + Send>>
|
||||||
where
|
where
|
||||||
E : Display + IntoResponseError
|
E: Display + IntoResponseError
|
||||||
{
|
{
|
||||||
into_response_helper(|| {
|
into_response_helper(|| {
|
||||||
errorlog(&e);
|
errorlog(&e);
|
||||||
|
@ -123,52 +109,41 @@ where
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl<Res> ResourceResult for Pin<Box<dyn Future<Output = Res> + Send>>
|
impl<Res> ResourceResult for Pin<Box<dyn Future<Output = Res> + Send>>
|
||||||
where
|
where
|
||||||
Res : ResourceResult + 'static
|
Res: ResourceResult + 'static
|
||||||
{
|
{
|
||||||
type Err = Res::Err;
|
type Err = Res::Err;
|
||||||
|
|
||||||
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, Self::Err>> + Send>>
|
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, Self::Err>> + Send>> {
|
||||||
{
|
self.then(|result| result.into_response()).boxed()
|
||||||
self.then(|result| {
|
|
||||||
result.into_response()
|
|
||||||
}).boxed()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn accepted_types() -> Option<Vec<Mime>>
|
fn accepted_types() -> Option<Vec<Mime>> {
|
||||||
{
|
|
||||||
Res::accepted_types()
|
Res::accepted_types()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
fn schema() -> OpenapiSchema
|
fn schema() -> OpenapiSchema {
|
||||||
{
|
|
||||||
Res::schema()
|
Res::schema()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
fn default_status() -> crate::StatusCode
|
fn default_status() -> crate::StatusCode {
|
||||||
{
|
|
||||||
Res::default_status()
|
Res::default_status()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test
|
mod test {
|
||||||
{
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use futures_executor::block_on;
|
use futures_executor::block_on;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||||
#[cfg_attr(feature = "openapi", derive(crate::OpenapiType))]
|
#[cfg_attr(feature = "openapi", derive(crate::OpenapiType))]
|
||||||
struct Msg
|
struct Msg {
|
||||||
{
|
msg: String
|
||||||
msg : String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Error)]
|
#[derive(Debug, Default, Error)]
|
||||||
|
@ -176,8 +151,7 @@ mod test
|
||||||
struct MsgError;
|
struct MsgError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn result_from_future()
|
fn result_from_future() {
|
||||||
{
|
|
||||||
let nc = NoContent::default();
|
let nc = NoContent::default();
|
||||||
let res = block_on(nc.into_response()).unwrap();
|
let res = block_on(nc.into_response()).unwrap();
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
use super::{ResourceResult, handle_error};
|
use super::{handle_error, ResourceResult};
|
||||||
use crate::{IntoResponseError, Response};
|
use crate::{IntoResponseError, Response};
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
use crate::{OpenapiSchema, OpenapiType};
|
use crate::{OpenapiSchema, OpenapiType};
|
||||||
use futures_util::{future, future::FutureExt};
|
use futures_util::{future, future::FutureExt};
|
||||||
use mime::Mime;
|
use mime::Mime;
|
||||||
use std::{
|
use std::{fmt::Display, future::Future, pin::Pin};
|
||||||
fmt::Display,
|
|
||||||
future::Future,
|
|
||||||
pin::Pin
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
This is the return type of a resource that doesn't actually return something. It will result
|
This is the return type of a resource that doesn't actually return something. It will result
|
||||||
|
@ -35,81 +31,68 @@ fn read_all(_state: &mut State) {
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
pub struct NoContent;
|
pub struct NoContent;
|
||||||
|
|
||||||
impl From<()> for NoContent
|
impl From<()> for NoContent {
|
||||||
{
|
fn from(_: ()) -> Self {
|
||||||
fn from(_ : ()) -> Self
|
|
||||||
{
|
|
||||||
Self {}
|
Self {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResourceResult for NoContent
|
impl ResourceResult for NoContent {
|
||||||
{
|
|
||||||
// TODO this shouldn't be a serde_json::Error
|
// TODO this shouldn't be a serde_json::Error
|
||||||
type Err = serde_json::Error; // just for easier handling of `Result<NoContent, E>`
|
type Err = serde_json::Error; // just for easier handling of `Result<NoContent, E>`
|
||||||
|
|
||||||
/// This will always be a _204 No Content_ together with an empty string.
|
/// This will always be a _204 No Content_ together with an empty string.
|
||||||
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, Self::Err>> + Send>>
|
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, Self::Err>> + Send>> {
|
||||||
{
|
|
||||||
future::ok(Response::no_content()).boxed()
|
future::ok(Response::no_content()).boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn accepted_types() -> Option<Vec<Mime>>
|
fn accepted_types() -> Option<Vec<Mime>> {
|
||||||
{
|
|
||||||
Some(Vec::new())
|
Some(Vec::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the schema of the `()` type.
|
/// Returns the schema of the `()` type.
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
fn schema() -> OpenapiSchema
|
fn schema() -> OpenapiSchema {
|
||||||
{
|
|
||||||
<()>::schema()
|
<()>::schema()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This will always be a _204 No Content_
|
/// This will always be a _204 No Content_
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
fn default_status() -> crate::StatusCode
|
fn default_status() -> crate::StatusCode {
|
||||||
{
|
|
||||||
crate::StatusCode::NO_CONTENT
|
crate::StatusCode::NO_CONTENT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E> ResourceResult for Result<NoContent, E>
|
impl<E> ResourceResult for Result<NoContent, E>
|
||||||
where
|
where
|
||||||
E : Display + IntoResponseError<Err = serde_json::Error>
|
E: Display + IntoResponseError<Err = serde_json::Error>
|
||||||
{
|
{
|
||||||
type Err = serde_json::Error;
|
type Err = serde_json::Error;
|
||||||
|
|
||||||
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, serde_json::Error>> + Send>>
|
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, serde_json::Error>> + Send>> {
|
||||||
{
|
|
||||||
match self {
|
match self {
|
||||||
Ok(nc) => nc.into_response(),
|
Ok(nc) => nc.into_response(),
|
||||||
Err(e) => handle_error(e)
|
Err(e) => handle_error(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn accepted_types() -> Option<Vec<Mime>>
|
fn accepted_types() -> Option<Vec<Mime>> {
|
||||||
{
|
|
||||||
NoContent::accepted_types()
|
NoContent::accepted_types()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
fn schema() -> OpenapiSchema
|
fn schema() -> OpenapiSchema {
|
||||||
{
|
|
||||||
<NoContent as ResourceResult>::schema()
|
<NoContent as ResourceResult>::schema()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
fn default_status() -> crate::StatusCode
|
fn default_status() -> crate::StatusCode {
|
||||||
{
|
|
||||||
NoContent::default_status()
|
NoContent::default_status()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test
|
mod test {
|
||||||
{
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use futures_executor::block_on;
|
use futures_executor::block_on;
|
||||||
use gotham::hyper::StatusCode;
|
use gotham::hyper::StatusCode;
|
||||||
|
@ -120,8 +103,7 @@ mod test
|
||||||
struct MsgError;
|
struct MsgError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn no_content_has_empty_response()
|
fn no_content_has_empty_response() {
|
||||||
{
|
|
||||||
let no_content = NoContent::default();
|
let no_content = NoContent::default();
|
||||||
let res = block_on(no_content.into_response()).expect("didn't expect error response");
|
let res = block_on(no_content.into_response()).expect("didn't expect error response");
|
||||||
assert_eq!(res.status, StatusCode::NO_CONTENT);
|
assert_eq!(res.status, StatusCode::NO_CONTENT);
|
||||||
|
@ -130,9 +112,8 @@ mod test
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn no_content_result()
|
fn no_content_result() {
|
||||||
{
|
let no_content: Result<NoContent, MsgError> = Ok(NoContent::default());
|
||||||
let no_content : Result<NoContent, MsgError> = Ok(NoContent::default());
|
|
||||||
let res = block_on(no_content.into_response()).expect("didn't expect error response");
|
let res = block_on(no_content.into_response()).expect("didn't expect error response");
|
||||||
assert_eq!(res.status, StatusCode::NO_CONTENT);
|
assert_eq!(res.status, StatusCode::NO_CONTENT);
|
||||||
assert_eq!(res.mime, None);
|
assert_eq!(res.mime, None);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use super::{IntoResponseError, ResourceResult, handle_error};
|
use super::{handle_error, IntoResponseError, ResourceResult};
|
||||||
use crate::{FromBody, RequestBody, ResourceType, Response, StatusCode};
|
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
use crate::OpenapiSchema;
|
use crate::OpenapiSchema;
|
||||||
|
use crate::{FromBody, RequestBody, ResourceType, Response, StatusCode};
|
||||||
use futures_core::future::Future;
|
use futures_core::future::Future;
|
||||||
use futures_util::{future, future::FutureExt};
|
use futures_util::{future, future::FutureExt};
|
||||||
use gotham::hyper::body::{Body, Bytes};
|
use gotham::hyper::body::{Body, Bytes};
|
||||||
|
@ -9,11 +9,7 @@ use mime::Mime;
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
use openapiv3::{SchemaKind, StringFormat, StringType, Type, VariantOrUnknownOrEmpty};
|
use openapiv3::{SchemaKind, StringFormat, StringType, Type, VariantOrUnknownOrEmpty};
|
||||||
use serde_json::error::Error as SerdeJsonError;
|
use serde_json::error::Error as SerdeJsonError;
|
||||||
use std::{
|
use std::{convert::Infallible, fmt::Display, pin::Pin};
|
||||||
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
|
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<Vec<u8>>) -> Raw<Vec<u8>> {
|
||||||
[`OpenapiType`]: trait.OpenapiType.html
|
[`OpenapiType`]: trait.OpenapiType.html
|
||||||
*/
|
*/
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Raw<T>
|
pub struct Raw<T> {
|
||||||
{
|
pub raw: T,
|
||||||
pub raw : T,
|
pub mime: Mime
|
||||||
pub mime : Mime
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Raw<T>
|
impl<T> Raw<T> {
|
||||||
{
|
pub fn new(raw: T, mime: Mime) -> Self {
|
||||||
pub fn new(raw : T, mime : Mime) -> Self
|
|
||||||
{
|
|
||||||
Self { raw, mime }
|
Self { raw, mime }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, U> AsMut<U> for Raw<T>
|
impl<T, U> AsMut<U> for Raw<T>
|
||||||
where
|
where
|
||||||
T : AsMut<U>
|
T: AsMut<U>
|
||||||
{
|
{
|
||||||
fn as_mut(&mut self) -> &mut U
|
fn as_mut(&mut self) -> &mut U {
|
||||||
{
|
|
||||||
self.raw.as_mut()
|
self.raw.as_mut()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, U> AsRef<U> for Raw<T>
|
impl<T, U> AsRef<U> for Raw<T>
|
||||||
where
|
where
|
||||||
T : AsRef<U>
|
T: AsRef<U>
|
||||||
{
|
{
|
||||||
fn as_ref(&self) -> &U
|
fn as_ref(&self) -> &U {
|
||||||
{
|
|
||||||
self.raw.as_ref()
|
self.raw.as_ref()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T : Clone> Clone for Raw<T>
|
impl<T: Clone> Clone for Raw<T> {
|
||||||
{
|
fn clone(&self) -> Self {
|
||||||
fn clone(&self) -> Self
|
|
||||||
{
|
|
||||||
Self {
|
Self {
|
||||||
raw: self.raw.clone(),
|
raw: self.raw.clone(),
|
||||||
mime: self.mime.clone()
|
mime: self.mime.clone()
|
||||||
|
@ -88,36 +77,28 @@ impl<T : Clone> Clone for Raw<T>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T : for<'a> From<&'a [u8]>> FromBody for Raw<T>
|
impl<T: for<'a> From<&'a [u8]>> FromBody for Raw<T> {
|
||||||
{
|
|
||||||
type Err = Infallible;
|
type Err = Infallible;
|
||||||
|
|
||||||
fn from_body(body : Bytes, mime : Mime) -> Result<Self, Self::Err>
|
fn from_body(body: Bytes, mime: Mime) -> Result<Self, Self::Err> {
|
||||||
{
|
|
||||||
Ok(Self::new(body.as_ref().into(), mime))
|
Ok(Self::new(body.as_ref().into(), mime))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> RequestBody for Raw<T>
|
impl<T> RequestBody for Raw<T> where Raw<T>: FromBody + ResourceType {}
|
||||||
where
|
|
||||||
Raw<T> : FromBody + ResourceType
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T : Into<Body>> ResourceResult for Raw<T>
|
impl<T: Into<Body>> ResourceResult for Raw<T>
|
||||||
where
|
where
|
||||||
Self : Send
|
Self: Send
|
||||||
{
|
{
|
||||||
type Err = SerdeJsonError; // just for easier handling of `Result<Raw<T>, E>`
|
type Err = SerdeJsonError; // just for easier handling of `Result<Raw<T>, E>`
|
||||||
|
|
||||||
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, SerdeJsonError>> + Send>>
|
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, SerdeJsonError>> + Send>> {
|
||||||
{
|
|
||||||
future::ok(Response::new(StatusCode::OK, self.raw, Some(self.mime.clone()))).boxed()
|
future::ok(Response::new(StatusCode::OK, self.raw, Some(self.mime.clone()))).boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
fn schema() -> OpenapiSchema
|
fn schema() -> OpenapiSchema {
|
||||||
{
|
|
||||||
OpenapiSchema::new(SchemaKind::Type(Type::String(StringType {
|
OpenapiSchema::new(SchemaKind::Type(Type::String(StringType {
|
||||||
format: VariantOrUnknownOrEmpty::Item(StringFormat::Binary),
|
format: VariantOrUnknownOrEmpty::Item(StringFormat::Binary),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -127,13 +108,12 @@ where
|
||||||
|
|
||||||
impl<T, E> ResourceResult for Result<Raw<T>, E>
|
impl<T, E> ResourceResult for Result<Raw<T>, E>
|
||||||
where
|
where
|
||||||
Raw<T> : ResourceResult,
|
Raw<T>: ResourceResult,
|
||||||
E : Display + IntoResponseError<Err = <Raw<T> as ResourceResult>::Err>
|
E: Display + IntoResponseError<Err = <Raw<T> as ResourceResult>::Err>
|
||||||
{
|
{
|
||||||
type Err = E::Err;
|
type Err = E::Err;
|
||||||
|
|
||||||
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, E::Err>> + Send>>
|
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, E::Err>> + Send>> {
|
||||||
{
|
|
||||||
match self {
|
match self {
|
||||||
Ok(raw) => raw.into_response(),
|
Ok(raw) => raw.into_response(),
|
||||||
Err(e) => handle_error(e)
|
Err(e) => handle_error(e)
|
||||||
|
@ -141,23 +121,19 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
fn schema() -> OpenapiSchema
|
fn schema() -> OpenapiSchema {
|
||||||
{
|
|
||||||
<Raw<T> as ResourceResult>::schema()
|
<Raw<T> as ResourceResult>::schema()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test
|
mod test {
|
||||||
{
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use futures_executor::block_on;
|
use futures_executor::block_on;
|
||||||
use mime::TEXT_PLAIN;
|
use mime::TEXT_PLAIN;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn raw_response()
|
fn raw_response() {
|
||||||
{
|
|
||||||
let msg = "Test";
|
let msg = "Test";
|
||||||
let raw = Raw::new(msg, TEXT_PLAIN);
|
let raw = Raw::new(msg, TEXT_PLAIN);
|
||||||
let res = block_on(raw.into_response()).expect("didn't expect error response");
|
let res = block_on(raw.into_response()).expect("didn't expect error response");
|
||||||
|
|
|
@ -1,67 +1,55 @@
|
||||||
use super::{ResourceResult, handle_error, into_response_helper};
|
use super::{handle_error, into_response_helper, ResourceResult};
|
||||||
use crate::{
|
|
||||||
result::ResourceError,
|
|
||||||
Response, ResponseBody, StatusCode
|
|
||||||
};
|
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
use crate::OpenapiSchema;
|
use crate::OpenapiSchema;
|
||||||
|
use crate::{result::ResourceError, Response, ResponseBody, StatusCode};
|
||||||
use futures_core::future::Future;
|
use futures_core::future::Future;
|
||||||
use mime::{Mime, APPLICATION_JSON};
|
use mime::{Mime, APPLICATION_JSON};
|
||||||
use std::{
|
use std::{error::Error, fmt::Display, pin::Pin};
|
||||||
error::Error,
|
|
||||||
fmt::Display,
|
|
||||||
pin::Pin
|
|
||||||
};
|
|
||||||
|
|
||||||
pub trait IntoResponseError
|
pub trait IntoResponseError {
|
||||||
{
|
type Err: Error + Send + 'static;
|
||||||
type Err : Error + Send + 'static;
|
|
||||||
|
|
||||||
fn into_response_error(self) -> Result<Response, Self::Err>;
|
fn into_response_error(self) -> Result<Response, Self::Err>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E : Error> IntoResponseError for E
|
impl<E: Error> IntoResponseError for E {
|
||||||
{
|
|
||||||
type Err = serde_json::Error;
|
type Err = serde_json::Error;
|
||||||
|
|
||||||
fn into_response_error(self) -> Result<Response, Self::Err>
|
fn into_response_error(self) -> Result<Response, Self::Err> {
|
||||||
{
|
let err: ResourceError = self.into();
|
||||||
let err : ResourceError = self.into();
|
Ok(Response::json(
|
||||||
Ok(Response::json(StatusCode::INTERNAL_SERVER_ERROR, serde_json::to_string(&err)?))
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
serde_json::to_string(&err)?
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R, E> ResourceResult for Result<R, E>
|
impl<R, E> ResourceResult for Result<R, E>
|
||||||
where
|
where
|
||||||
R : ResponseBody,
|
R: ResponseBody,
|
||||||
E : Display + IntoResponseError<Err = serde_json::Error>
|
E: Display + IntoResponseError<Err = serde_json::Error>
|
||||||
{
|
{
|
||||||
type Err = E::Err;
|
type Err = E::Err;
|
||||||
|
|
||||||
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, E::Err>> + Send>>
|
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, E::Err>> + Send>> {
|
||||||
{
|
|
||||||
match self {
|
match self {
|
||||||
Ok(r) => into_response_helper(|| Ok(Response::json(StatusCode::OK, serde_json::to_string(&r)?))),
|
Ok(r) => into_response_helper(|| Ok(Response::json(StatusCode::OK, serde_json::to_string(&r)?))),
|
||||||
Err(e) => handle_error(e)
|
Err(e) => handle_error(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn accepted_types() -> Option<Vec<Mime>>
|
fn accepted_types() -> Option<Vec<Mime>> {
|
||||||
{
|
|
||||||
Some(vec![APPLICATION_JSON])
|
Some(vec![APPLICATION_JSON])
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
fn schema() -> OpenapiSchema
|
fn schema() -> OpenapiSchema {
|
||||||
{
|
|
||||||
R::schema()
|
R::schema()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test
|
mod test {
|
||||||
{
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::result::OrAllTypes;
|
use crate::result::OrAllTypes;
|
||||||
use futures_executor::block_on;
|
use futures_executor::block_on;
|
||||||
|
@ -69,9 +57,8 @@ mod test
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, Serialize)]
|
#[derive(Debug, Default, Deserialize, Serialize)]
|
||||||
#[cfg_attr(feature = "openapi", derive(crate::OpenapiType))]
|
#[cfg_attr(feature = "openapi", derive(crate::OpenapiType))]
|
||||||
struct Msg
|
struct Msg {
|
||||||
{
|
msg: String
|
||||||
msg : String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Error)]
|
#[derive(Debug, Default, Error)]
|
||||||
|
@ -79,9 +66,8 @@ mod test
|
||||||
struct MsgError;
|
struct MsgError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn result_ok()
|
fn result_ok() {
|
||||||
{
|
let ok: Result<Msg, MsgError> = Ok(Msg::default());
|
||||||
let ok : Result<Msg, MsgError> = Ok(Msg::default());
|
|
||||||
let res = block_on(ok.into_response()).expect("didn't expect error response");
|
let res = block_on(ok.into_response()).expect("didn't expect error response");
|
||||||
assert_eq!(res.status, StatusCode::OK);
|
assert_eq!(res.status, StatusCode::OK);
|
||||||
assert_eq!(res.mime, Some(APPLICATION_JSON));
|
assert_eq!(res.mime, Some(APPLICATION_JSON));
|
||||||
|
@ -89,18 +75,21 @@ mod test
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn result_err()
|
fn result_err() {
|
||||||
{
|
let err: Result<Msg, MsgError> = Err(MsgError::default());
|
||||||
let err : Result<Msg, MsgError> = Err(MsgError::default());
|
|
||||||
let res = block_on(err.into_response()).expect("didn't expect error response");
|
let res = block_on(err.into_response()).expect("didn't expect error response");
|
||||||
assert_eq!(res.status, StatusCode::INTERNAL_SERVER_ERROR);
|
assert_eq!(res.status, StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
assert_eq!(res.mime, Some(APPLICATION_JSON));
|
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]
|
#[test]
|
||||||
fn success_accepts_json()
|
fn success_accepts_json() {
|
||||||
{
|
assert!(<Result<Msg, MsgError>>::accepted_types()
|
||||||
assert!(<Result<Msg, MsgError>>::accepted_types().or_all_types().contains(&APPLICATION_JSON))
|
.or_all_types()
|
||||||
|
.contains(&APPLICATION_JSON))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
use super::{ResourceResult, into_response_helper};
|
use super::{into_response_helper, ResourceResult};
|
||||||
use crate::{Response, ResponseBody};
|
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
use crate::OpenapiSchema;
|
use crate::OpenapiSchema;
|
||||||
|
use crate::{Response, ResponseBody};
|
||||||
use gotham::hyper::StatusCode;
|
use gotham::hyper::StatusCode;
|
||||||
use mime::{Mime, APPLICATION_JSON};
|
use mime::{Mime, APPLICATION_JSON};
|
||||||
use std::{
|
use std::{
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
future::Future,
|
future::Future,
|
||||||
pin::Pin,
|
ops::{Deref, DerefMut},
|
||||||
ops::{Deref, DerefMut}
|
pin::Pin
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,110 +45,87 @@ fn read_all(_state: &mut State) -> Success<MyResponse> {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Success<T>(T);
|
pub struct Success<T>(T);
|
||||||
|
|
||||||
impl<T> AsMut<T> for Success<T>
|
impl<T> AsMut<T> for Success<T> {
|
||||||
{
|
fn as_mut(&mut self) -> &mut T {
|
||||||
fn as_mut(&mut self) -> &mut T
|
|
||||||
{
|
|
||||||
&mut self.0
|
&mut self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> AsRef<T> for Success<T>
|
impl<T> AsRef<T> for Success<T> {
|
||||||
{
|
fn as_ref(&self) -> &T {
|
||||||
fn as_ref(&self) -> &T
|
|
||||||
{
|
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Deref for Success<T>
|
impl<T> Deref for Success<T> {
|
||||||
{
|
|
||||||
type Target = T;
|
type Target = T;
|
||||||
|
|
||||||
fn deref(&self) -> &T
|
fn deref(&self) -> &T {
|
||||||
{
|
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> DerefMut for Success<T>
|
impl<T> DerefMut for Success<T> {
|
||||||
{
|
fn deref_mut(&mut self) -> &mut T {
|
||||||
fn deref_mut(&mut self) -> &mut T
|
|
||||||
{
|
|
||||||
&mut self.0
|
&mut self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> From<T> for Success<T>
|
impl<T> From<T> for Success<T> {
|
||||||
{
|
fn from(t: T) -> Self {
|
||||||
fn from(t : T) -> Self
|
|
||||||
{
|
|
||||||
Self(t)
|
Self(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T : Clone> Clone for Success<T>
|
impl<T: Clone> Clone for Success<T> {
|
||||||
{
|
fn clone(&self) -> Self {
|
||||||
fn clone(&self) -> Self
|
|
||||||
{
|
|
||||||
Self(self.0.clone())
|
Self(self.0.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T : Copy> Copy for Success<T>
|
impl<T: Copy> Copy for Success<T> {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T : Default> Default for Success<T>
|
impl<T: Default> Default for Success<T> {
|
||||||
{
|
fn default() -> Self {
|
||||||
fn default() -> Self
|
|
||||||
{
|
|
||||||
Self(T::default())
|
Self(T::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T : ResponseBody> ResourceResult for Success<T>
|
impl<T: ResponseBody> ResourceResult for Success<T>
|
||||||
where
|
where
|
||||||
Self : Send
|
Self: Send
|
||||||
{
|
{
|
||||||
type Err = serde_json::Error;
|
type Err = serde_json::Error;
|
||||||
|
|
||||||
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, Self::Err>> + Send>>
|
fn into_response(self) -> Pin<Box<dyn Future<Output = Result<Response, Self::Err>> + Send>> {
|
||||||
{
|
|
||||||
into_response_helper(|| Ok(Response::json(StatusCode::OK, serde_json::to_string(self.as_ref())?)))
|
into_response_helper(|| Ok(Response::json(StatusCode::OK, serde_json::to_string(self.as_ref())?)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn accepted_types() -> Option<Vec<Mime>>
|
fn accepted_types() -> Option<Vec<Mime>> {
|
||||||
{
|
|
||||||
Some(vec![APPLICATION_JSON])
|
Some(vec![APPLICATION_JSON])
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
fn schema() -> OpenapiSchema
|
fn schema() -> OpenapiSchema {
|
||||||
{
|
|
||||||
T::schema()
|
T::schema()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test
|
mod test {
|
||||||
{
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::result::OrAllTypes;
|
use crate::result::OrAllTypes;
|
||||||
use futures_executor::block_on;
|
use futures_executor::block_on;
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize)]
|
#[derive(Debug, Default, Serialize)]
|
||||||
#[cfg_attr(feature = "openapi", derive(crate::OpenapiType))]
|
#[cfg_attr(feature = "openapi", derive(crate::OpenapiType))]
|
||||||
struct Msg
|
struct Msg {
|
||||||
{
|
msg: String
|
||||||
msg : String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn success_always_successfull()
|
fn success_always_successfull() {
|
||||||
{
|
let success: Success<Msg> = Msg::default().into();
|
||||||
let success : Success<Msg> = Msg::default().into();
|
|
||||||
let res = block_on(success.into_response()).expect("didn't expect error response");
|
let res = block_on(success.into_response()).expect("didn't expect error response");
|
||||||
assert_eq!(res.status, StatusCode::OK);
|
assert_eq!(res.status, StatusCode::OK);
|
||||||
assert_eq!(res.mime, Some(APPLICATION_JSON));
|
assert_eq!(res.mime, Some(APPLICATION_JSON));
|
||||||
|
@ -156,8 +133,7 @@ mod test
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn success_accepts_json()
|
fn success_accepts_json() {
|
||||||
{
|
|
||||||
assert!(<Success<Msg>>::accepted_types().or_all_types().contains(&APPLICATION_JSON))
|
assert!(<Success<Msg>>::accepted_types().or_all_types().contains(&APPLICATION_JSON))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
307
src/routing.rs
307
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")]
|
#[cfg(feature = "openapi")]
|
||||||
use crate::openapi::{
|
use crate::openapi::{
|
||||||
builder::{OpenapiBuilder, OpenapiInfo},
|
builder::{OpenapiBuilder, OpenapiInfo},
|
||||||
router::OpenapiRouter
|
router::OpenapiRouter
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "cors")]
|
||||||
|
use crate::CorsRoute;
|
||||||
|
use crate::{
|
||||||
|
resource::*,
|
||||||
|
result::{ResourceError, ResourceResult},
|
||||||
|
RequestBody, Response, StatusCode
|
||||||
|
};
|
||||||
|
|
||||||
use futures_util::{future, future::FutureExt};
|
use futures_util::{future, future::FutureExt};
|
||||||
use gotham::{
|
use gotham::{
|
||||||
handler::{HandlerError, HandlerFuture, IntoHandlerError},
|
handler::{HandlerError, HandlerFuture},
|
||||||
helpers::http::response::{create_empty_response, create_response},
|
helpers::http::response::{create_empty_response, create_response},
|
||||||
|
hyper::{body::to_bytes, header::CONTENT_TYPE, Body, HeaderMap, Method},
|
||||||
pipeline::chain::PipelineHandleChain,
|
pipeline::chain::PipelineHandleChain,
|
||||||
router::{
|
router::{
|
||||||
builder::*,
|
builder::*,
|
||||||
|
@ -25,86 +24,68 @@ use gotham::{
|
||||||
},
|
},
|
||||||
state::{FromState, State}
|
state::{FromState, State}
|
||||||
};
|
};
|
||||||
use gotham::hyper::{
|
|
||||||
body::to_bytes,
|
|
||||||
header::CONTENT_TYPE,
|
|
||||||
Body,
|
|
||||||
HeaderMap,
|
|
||||||
Method
|
|
||||||
};
|
|
||||||
use mime::{Mime, APPLICATION_JSON};
|
use mime::{Mime, APPLICATION_JSON};
|
||||||
use std::{
|
use std::{future::Future, panic::RefUnwindSafe, pin::Pin};
|
||||||
future::Future,
|
|
||||||
panic::RefUnwindSafe,
|
|
||||||
pin::Pin
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Allow us to extract an id from a path.
|
/// Allow us to extract an id from a path.
|
||||||
#[derive(Deserialize, StateData, StaticResponseExtender)]
|
#[derive(Deserialize, StateData, StaticResponseExtender)]
|
||||||
struct PathExtractor<ID : RefUnwindSafe + Send + 'static>
|
struct PathExtractor<ID: RefUnwindSafe + Send + 'static> {
|
||||||
{
|
id: ID
|
||||||
id : ID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This trait adds the `with_openapi` method to gotham's routing. It turns the default
|
/// 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
|
/// router into one that will only allow RESTful resources, but record them and generate
|
||||||
/// an OpenAPI specification on request.
|
/// an OpenAPI specification on request.
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
pub trait WithOpenapi<D>
|
pub trait WithOpenapi<D> {
|
||||||
{
|
fn with_openapi<F>(&mut self, info: OpenapiInfo, block: F)
|
||||||
fn with_openapi<F>(&mut self, info : OpenapiInfo, block : F)
|
|
||||||
where
|
where
|
||||||
F : FnOnce(OpenapiRouter<'_, D>);
|
F: FnOnce(OpenapiRouter<'_, D>);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This trait adds the `resource` method to gotham's routing. It allows you to register
|
/// This trait adds the `resource` method to gotham's routing. It allows you to register
|
||||||
/// any RESTful `Resource` with a path.
|
/// any RESTful `Resource` with a path.
|
||||||
pub trait DrawResources
|
pub trait DrawResources {
|
||||||
{
|
fn resource<R: Resource>(&mut self, path: &str);
|
||||||
fn resource<R : Resource>(&mut self, path : &str);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This trait allows to draw routes within an resource. Use this only inside the
|
/// This trait allows to draw routes within an resource. Use this only inside the
|
||||||
/// `Resource::setup` method.
|
/// `Resource::setup` method.
|
||||||
pub trait DrawResourceRoutes
|
pub trait DrawResourceRoutes {
|
||||||
{
|
fn read_all<Handler: ResourceReadAll>(&mut self);
|
||||||
fn read_all<Handler : ResourceReadAll>(&mut self);
|
|
||||||
|
|
||||||
fn read<Handler : ResourceRead>(&mut self);
|
fn read<Handler: ResourceRead>(&mut self);
|
||||||
|
|
||||||
fn search<Handler : ResourceSearch>(&mut self);
|
fn search<Handler: ResourceSearch>(&mut self);
|
||||||
|
|
||||||
fn create<Handler : ResourceCreate>(&mut self)
|
fn create<Handler: ResourceCreate>(&mut self)
|
||||||
where
|
where
|
||||||
Handler::Res : 'static,
|
Handler::Res: 'static,
|
||||||
Handler::Body : 'static;
|
Handler::Body: 'static;
|
||||||
|
|
||||||
fn change_all<Handler : ResourceChangeAll>(&mut self)
|
fn change_all<Handler: ResourceChangeAll>(&mut self)
|
||||||
where
|
where
|
||||||
Handler::Res : 'static,
|
Handler::Res: 'static,
|
||||||
Handler::Body : 'static;
|
Handler::Body: 'static;
|
||||||
|
|
||||||
fn change<Handler : ResourceChange>(&mut self)
|
fn change<Handler: ResourceChange>(&mut self)
|
||||||
where
|
where
|
||||||
Handler::Res : 'static,
|
Handler::Res: 'static,
|
||||||
Handler::Body : 'static;
|
Handler::Body: 'static;
|
||||||
|
|
||||||
fn remove_all<Handler : ResourceRemoveAll>(&mut self);
|
fn remove_all<Handler: ResourceRemoveAll>(&mut self);
|
||||||
|
|
||||||
fn remove<Handler : ResourceRemove>(&mut self);
|
fn remove<Handler: ResourceRemove>(&mut self);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn response_from(res : Response, state : &State) -> gotham::hyper::Response<Body>
|
fn response_from(res: Response, state: &State) -> gotham::hyper::Response<Body> {
|
||||||
{
|
|
||||||
let mut r = create_empty_response(state, res.status);
|
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());
|
r.headers_mut().insert(CONTENT_TYPE, mime.as_ref().parse().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
let method = Method::borrow_from(state);
|
let method = Method::borrow_from(state);
|
||||||
if method != Method::HEAD
|
if method != Method::HEAD {
|
||||||
{
|
|
||||||
*r.body_mut() = res.body;
|
*r.body_mut() = res.body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,10 +95,13 @@ fn response_from(res : Response, state : &State) -> gotham::hyper::Response<Body
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn to_handler_future<F, R>(state : State, get_result : F) -> Result<(State, gotham::hyper::Response<Body>), (State, HandlerError)>
|
async fn to_handler_future<F, R>(
|
||||||
|
state: State,
|
||||||
|
get_result: F
|
||||||
|
) -> Result<(State, gotham::hyper::Response<Body>), (State, HandlerError)>
|
||||||
where
|
where
|
||||||
F : FnOnce(State) -> Pin<Box<dyn Future<Output = (State, R)> + Send>>,
|
F: FnOnce(State) -> Pin<Box<dyn Future<Output = (State, R)> + Send>>,
|
||||||
R : ResourceResult
|
R: ResourceResult
|
||||||
{
|
{
|
||||||
let (state, res) = get_result(state).await;
|
let (state, res) = get_result(state).await;
|
||||||
let res = res.into_response().await;
|
let res = res.into_response().await;
|
||||||
|
@ -126,28 +110,31 @@ where
|
||||||
let r = response_from(res, &state);
|
let r = response_from(res, &state);
|
||||||
Ok((state, r))
|
Ok((state, r))
|
||||||
},
|
},
|
||||||
Err(e) => Err((state, e.into_handler_error()))
|
Err(e) => Err((state, e.into()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn body_to_res<B, F, R>(mut state : State, get_result : F) -> (State, Result<gotham::hyper::Response<Body>, HandlerError>)
|
async fn body_to_res<B, F, R>(
|
||||||
|
mut state: State,
|
||||||
|
get_result: F
|
||||||
|
) -> (State, Result<gotham::hyper::Response<Body>, HandlerError>)
|
||||||
where
|
where
|
||||||
B : RequestBody,
|
B: RequestBody,
|
||||||
F : FnOnce(State, B) -> Pin<Box<dyn Future<Output = (State, R)> + Send>>,
|
F: FnOnce(State, B) -> Pin<Box<dyn Future<Output = (State, R)> + Send>>,
|
||||||
R : ResourceResult
|
R: ResourceResult
|
||||||
{
|
{
|
||||||
let body = to_bytes(Body::take_from(&mut state)).await;
|
let body = to_bytes(Body::take_from(&mut state)).await;
|
||||||
|
|
||||||
let body = match body {
|
let body = match body {
|
||||||
Ok(body) => 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(),
|
Some(content_type) => content_type.to_str().unwrap().parse().unwrap(),
|
||||||
None => {
|
None => {
|
||||||
let res = create_empty_response(&state, StatusCode::UNSUPPORTED_MEDIA_TYPE);
|
let res = create_empty_response(&state, StatusCode::UNSUPPORTED_MEDIA_TYPE);
|
||||||
return (state, Ok(res))
|
return (state, Ok(res));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -155,15 +142,15 @@ where
|
||||||
let body = match B::from_body(body, content_type) {
|
let body = match B::from_body(body, content_type) {
|
||||||
Ok(body) => body,
|
Ok(body) => body,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let error : ResourceError = e.into();
|
let error: ResourceError = e.into();
|
||||||
let res = match serde_json::to_string(&error) {
|
let res = match serde_json::to_string(&error) {
|
||||||
Ok(json) => {
|
Ok(json) => {
|
||||||
let res = create_response(&state, StatusCode::BAD_REQUEST, APPLICATION_JSON, json);
|
let res = create_response(&state, StatusCode::BAD_REQUEST, APPLICATION_JSON, json);
|
||||||
Ok(res)
|
Ok(res)
|
||||||
},
|
},
|
||||||
Err(e) => Err(e.into_handler_error())
|
Err(e) => Err(e.into())
|
||||||
};
|
};
|
||||||
return (state, res)
|
return (state, res);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
get_result(state, body)
|
get_result(state, body)
|
||||||
|
@ -177,16 +164,16 @@ where
|
||||||
let r = response_from(res, &state);
|
let r = response_from(res, &state);
|
||||||
Ok(r)
|
Ok(r)
|
||||||
},
|
},
|
||||||
Err(e) => Err(e.into_handler_error())
|
Err(e) => Err(e.into())
|
||||||
};
|
};
|
||||||
(state, res)
|
(state, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_with_body<B, F, R>(state : State, get_result : F) -> Pin<Box<HandlerFuture>>
|
fn handle_with_body<B, F, R>(state: State, get_result: F) -> Pin<Box<HandlerFuture>>
|
||||||
where
|
where
|
||||||
B : RequestBody + 'static,
|
B: RequestBody + 'static,
|
||||||
F : FnOnce(State, B) -> Pin<Box<dyn Future<Output = (State, R)> + Send>> + Send + 'static,
|
F: FnOnce(State, B) -> Pin<Box<dyn Future<Output = (State, R)> + Send>> + Send + 'static,
|
||||||
R : ResourceResult + Send + 'static
|
R: ResourceResult + Send + 'static
|
||||||
{
|
{
|
||||||
body_to_res(state, get_result)
|
body_to_res(state, get_result)
|
||||||
.then(|(state, res)| match res {
|
.then(|(state, res)| match res {
|
||||||
|
@ -196,78 +183,70 @@ where
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_all_handler<Handler : ResourceReadAll>(state : State) -> Pin<Box<HandlerFuture>>
|
fn read_all_handler<Handler: ResourceReadAll>(state: State) -> Pin<Box<HandlerFuture>> {
|
||||||
{
|
|
||||||
to_handler_future(state, |state| Handler::read_all(state)).boxed()
|
to_handler_future(state, |state| Handler::read_all(state)).boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_handler<Handler : ResourceRead>(state : State) -> Pin<Box<HandlerFuture>>
|
fn read_handler<Handler: ResourceRead>(state: State) -> Pin<Box<HandlerFuture>> {
|
||||||
{
|
|
||||||
let id = {
|
let id = {
|
||||||
let path : &PathExtractor<Handler::ID> = PathExtractor::borrow_from(&state);
|
let path: &PathExtractor<Handler::ID> = PathExtractor::borrow_from(&state);
|
||||||
path.id.clone()
|
path.id.clone()
|
||||||
};
|
};
|
||||||
to_handler_future(state, |state| Handler::read(state, id)).boxed()
|
to_handler_future(state, |state| Handler::read(state, id)).boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_handler<Handler : ResourceSearch>(mut state : State) -> Pin<Box<HandlerFuture>>
|
fn search_handler<Handler: ResourceSearch>(mut state: State) -> Pin<Box<HandlerFuture>> {
|
||||||
{
|
|
||||||
let query = Handler::Query::take_from(&mut state);
|
let query = Handler::Query::take_from(&mut state);
|
||||||
to_handler_future(state, |state| Handler::search(state, query)).boxed()
|
to_handler_future(state, |state| Handler::search(state, query)).boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_handler<Handler : ResourceCreate>(state : State) -> Pin<Box<HandlerFuture>>
|
fn create_handler<Handler: ResourceCreate>(state: State) -> Pin<Box<HandlerFuture>>
|
||||||
where
|
where
|
||||||
Handler::Res : 'static,
|
Handler::Res: 'static,
|
||||||
Handler::Body : 'static
|
Handler::Body: 'static
|
||||||
{
|
{
|
||||||
handle_with_body::<Handler::Body, _, _>(state, |state, body| Handler::create(state, body))
|
handle_with_body::<Handler::Body, _, _>(state, |state, body| Handler::create(state, body))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn change_all_handler<Handler : ResourceChangeAll>(state : State) -> Pin<Box<HandlerFuture>>
|
fn change_all_handler<Handler: ResourceChangeAll>(state: State) -> Pin<Box<HandlerFuture>>
|
||||||
where
|
where
|
||||||
Handler::Res : 'static,
|
Handler::Res: 'static,
|
||||||
Handler::Body : 'static
|
Handler::Body: 'static
|
||||||
{
|
{
|
||||||
handle_with_body::<Handler::Body, _, _>(state, |state, body| Handler::change_all(state, body))
|
handle_with_body::<Handler::Body, _, _>(state, |state, body| Handler::change_all(state, body))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn change_handler<Handler : ResourceChange>(state : State) -> Pin<Box<HandlerFuture>>
|
fn change_handler<Handler: ResourceChange>(state: State) -> Pin<Box<HandlerFuture>>
|
||||||
where
|
where
|
||||||
Handler::Res : 'static,
|
Handler::Res: 'static,
|
||||||
Handler::Body : 'static
|
Handler::Body: 'static
|
||||||
{
|
{
|
||||||
let id = {
|
let id = {
|
||||||
let path : &PathExtractor<Handler::ID> = PathExtractor::borrow_from(&state);
|
let path: &PathExtractor<Handler::ID> = PathExtractor::borrow_from(&state);
|
||||||
path.id.clone()
|
path.id.clone()
|
||||||
};
|
};
|
||||||
handle_with_body::<Handler::Body, _, _>(state, |state, body| Handler::change(state, id, body))
|
handle_with_body::<Handler::Body, _, _>(state, |state, body| Handler::change(state, id, body))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_all_handler<Handler : ResourceRemoveAll>(state : State) -> Pin<Box<HandlerFuture>>
|
fn remove_all_handler<Handler: ResourceRemoveAll>(state: State) -> Pin<Box<HandlerFuture>> {
|
||||||
{
|
|
||||||
to_handler_future(state, |state| Handler::remove_all(state)).boxed()
|
to_handler_future(state, |state| Handler::remove_all(state)).boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_handler<Handler : ResourceRemove>(state : State) -> Pin<Box<HandlerFuture>>
|
fn remove_handler<Handler: ResourceRemove>(state: State) -> Pin<Box<HandlerFuture>> {
|
||||||
{
|
|
||||||
let id = {
|
let id = {
|
||||||
let path : &PathExtractor<Handler::ID> = PathExtractor::borrow_from(&state);
|
let path: &PathExtractor<Handler::ID> = PathExtractor::borrow_from(&state);
|
||||||
path.id.clone()
|
path.id.clone()
|
||||||
};
|
};
|
||||||
to_handler_future(state, |state| Handler::remove(state, id)).boxed()
|
to_handler_future(state, |state| Handler::remove(state, id)).boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct MaybeMatchAcceptHeader
|
struct MaybeMatchAcceptHeader {
|
||||||
{
|
matcher: Option<AcceptHeaderRouteMatcher>
|
||||||
matcher : Option<AcceptHeaderRouteMatcher>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RouteMatcher for MaybeMatchAcceptHeader
|
impl RouteMatcher for MaybeMatchAcceptHeader {
|
||||||
{
|
fn is_match(&self, state: &State) -> Result<(), RouteNonMatch> {
|
||||||
fn is_match(&self, state : &State) -> Result<(), RouteNonMatch>
|
|
||||||
{
|
|
||||||
match &self.matcher {
|
match &self.matcher {
|
||||||
Some(matcher) => matcher.is_match(state),
|
Some(matcher) => matcher.is_match(state),
|
||||||
None => Ok(())
|
None => Ok(())
|
||||||
|
@ -275,10 +254,8 @@ impl RouteMatcher for MaybeMatchAcceptHeader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Option<Vec<Mime>>> for MaybeMatchAcceptHeader
|
impl From<Option<Vec<Mime>>> for MaybeMatchAcceptHeader {
|
||||||
{
|
fn from(types: Option<Vec<Mime>>) -> Self {
|
||||||
fn from(types : Option<Vec<Mime>>) -> Self
|
|
||||||
{
|
|
||||||
let types = match types {
|
let types = match types {
|
||||||
Some(types) if types.is_empty() => None,
|
Some(types) if types.is_empty() => None,
|
||||||
types => types
|
types => types
|
||||||
|
@ -290,15 +267,12 @@ impl From<Option<Vec<Mime>>> for MaybeMatchAcceptHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct MaybeMatchContentTypeHeader
|
struct MaybeMatchContentTypeHeader {
|
||||||
{
|
matcher: Option<ContentTypeHeaderRouteMatcher>
|
||||||
matcher : Option<ContentTypeHeaderRouteMatcher>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RouteMatcher for MaybeMatchContentTypeHeader
|
impl RouteMatcher for MaybeMatchContentTypeHeader {
|
||||||
{
|
fn is_match(&self, state: &State) -> Result<(), RouteNonMatch> {
|
||||||
fn is_match(&self, state : &State) -> Result<(), RouteNonMatch>
|
|
||||||
{
|
|
||||||
match &self.matcher {
|
match &self.matcher {
|
||||||
Some(matcher) => matcher.is_match(state),
|
Some(matcher) => matcher.is_match(state),
|
||||||
None => Ok(())
|
None => Ok(())
|
||||||
|
@ -306,10 +280,8 @@ impl RouteMatcher for MaybeMatchContentTypeHeader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Option<Vec<Mime>>> for MaybeMatchContentTypeHeader
|
impl From<Option<Vec<Mime>>> for MaybeMatchContentTypeHeader {
|
||||||
{
|
fn from(types: Option<Vec<Mime>>) -> Self {
|
||||||
fn from(types : Option<Vec<Mime>>) -> Self
|
|
||||||
{
|
|
||||||
Self {
|
Self {
|
||||||
matcher: types.map(|types| ContentTypeHeaderRouteMatcher::new(types).allow_no_type())
|
matcher: types.map(|types| ContentTypeHeaderRouteMatcher::new(types).allow_no_type())
|
||||||
}
|
}
|
||||||
|
@ -318,16 +290,15 @@ impl From<Option<Vec<Mime>>> for MaybeMatchContentTypeHeader
|
||||||
|
|
||||||
macro_rules! implDrawResourceRoutes {
|
macro_rules! implDrawResourceRoutes {
|
||||||
($implType:ident) => {
|
($implType:ident) => {
|
||||||
|
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
impl<'a, C, P> WithOpenapi<Self> for $implType<'a, C, P>
|
impl<'a, C, P> WithOpenapi<Self> for $implType<'a, C, P>
|
||||||
where
|
where
|
||||||
C : PipelineHandleChain<P> + Copy + Send + Sync + 'static,
|
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
|
||||||
P : RefUnwindSafe + Send + Sync + 'static
|
P: RefUnwindSafe + Send + Sync + 'static
|
||||||
{
|
{
|
||||||
fn with_openapi<F>(&mut self, info : OpenapiInfo, block : F)
|
fn with_openapi<F>(&mut self, info: OpenapiInfo, block: F)
|
||||||
where
|
where
|
||||||
F : FnOnce(OpenapiRouter<'_, $implType<'a, C, P>>)
|
F: FnOnce(OpenapiRouter<'_, $implType<'a, C, P>>)
|
||||||
{
|
{
|
||||||
let router = OpenapiRouter {
|
let router = OpenapiRouter {
|
||||||
router: self,
|
router: self,
|
||||||
|
@ -340,11 +311,10 @@ macro_rules! implDrawResourceRoutes {
|
||||||
|
|
||||||
impl<'a, C, P> DrawResources for $implType<'a, C, P>
|
impl<'a, C, P> DrawResources for $implType<'a, C, P>
|
||||||
where
|
where
|
||||||
C : PipelineHandleChain<P> + Copy + Send + Sync + 'static,
|
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
|
||||||
P : RefUnwindSafe + Send + Sync + 'static
|
P: RefUnwindSafe + Send + Sync + 'static
|
||||||
{
|
{
|
||||||
fn resource<R : Resource>(&mut self, path : &str)
|
fn resource<R: Resource>(&mut self, path: &str) {
|
||||||
{
|
|
||||||
R::setup((self, path));
|
R::setup((self, path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -352,43 +322,44 @@ macro_rules! implDrawResourceRoutes {
|
||||||
#[allow(clippy::redundant_closure)] // doesn't work because of type parameters
|
#[allow(clippy::redundant_closure)] // doesn't work because of type parameters
|
||||||
impl<'a, C, P> DrawResourceRoutes for (&mut $implType<'a, C, P>, &str)
|
impl<'a, C, P> DrawResourceRoutes for (&mut $implType<'a, C, P>, &str)
|
||||||
where
|
where
|
||||||
C : PipelineHandleChain<P> + Copy + Send + Sync + 'static,
|
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
|
||||||
P : RefUnwindSafe + Send + Sync + 'static
|
P: RefUnwindSafe + Send + Sync + 'static
|
||||||
{
|
{
|
||||||
fn read_all<Handler : ResourceReadAll>(&mut self)
|
fn read_all<Handler: ResourceReadAll>(&mut self) {
|
||||||
{
|
let matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
|
||||||
let matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
|
self.0
|
||||||
self.0.get(&self.1)
|
.get(&self.1)
|
||||||
.extend_route_matcher(matcher)
|
.extend_route_matcher(matcher)
|
||||||
.to(|state| read_all_handler::<Handler>(state));
|
.to(|state| read_all_handler::<Handler>(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read<Handler : ResourceRead>(&mut self)
|
fn read<Handler: ResourceRead>(&mut self) {
|
||||||
{
|
let matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
|
||||||
let matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
|
self.0
|
||||||
self.0.get(&format!("{}/:id", self.1))
|
.get(&format!("{}/:id", self.1))
|
||||||
.extend_route_matcher(matcher)
|
.extend_route_matcher(matcher)
|
||||||
.with_path_extractor::<PathExtractor<Handler::ID>>()
|
.with_path_extractor::<PathExtractor<Handler::ID>>()
|
||||||
.to(|state| read_handler::<Handler>(state));
|
.to(|state| read_handler::<Handler>(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search<Handler : ResourceSearch>(&mut self)
|
fn search<Handler: ResourceSearch>(&mut self) {
|
||||||
{
|
let matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
|
||||||
let matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
|
self.0
|
||||||
self.0.get(&format!("{}/search", self.1))
|
.get(&format!("{}/search", self.1))
|
||||||
.extend_route_matcher(matcher)
|
.extend_route_matcher(matcher)
|
||||||
.with_query_string_extractor::<Handler::Query>()
|
.with_query_string_extractor::<Handler::Query>()
|
||||||
.to(|state| search_handler::<Handler>(state));
|
.to(|state| search_handler::<Handler>(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create<Handler : ResourceCreate>(&mut self)
|
fn create<Handler: ResourceCreate>(&mut self)
|
||||||
where
|
where
|
||||||
Handler::Res : Send + 'static,
|
Handler::Res: Send + 'static,
|
||||||
Handler::Body : 'static
|
Handler::Body: 'static
|
||||||
{
|
{
|
||||||
let accept_matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
|
let accept_matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
|
||||||
let content_matcher : MaybeMatchContentTypeHeader = Handler::Body::supported_types().into();
|
let content_matcher: MaybeMatchContentTypeHeader = Handler::Body::supported_types().into();
|
||||||
self.0.post(&self.1)
|
self.0
|
||||||
|
.post(&self.1)
|
||||||
.extend_route_matcher(accept_matcher)
|
.extend_route_matcher(accept_matcher)
|
||||||
.extend_route_matcher(content_matcher)
|
.extend_route_matcher(content_matcher)
|
||||||
.to(|state| create_handler::<Handler>(state));
|
.to(|state| create_handler::<Handler>(state));
|
||||||
|
@ -396,14 +367,15 @@ macro_rules! implDrawResourceRoutes {
|
||||||
self.0.cors(&self.1, Method::POST);
|
self.0.cors(&self.1, Method::POST);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn change_all<Handler : ResourceChangeAll>(&mut self)
|
fn change_all<Handler: ResourceChangeAll>(&mut self)
|
||||||
where
|
where
|
||||||
Handler::Res : Send + 'static,
|
Handler::Res: Send + 'static,
|
||||||
Handler::Body : 'static
|
Handler::Body: 'static
|
||||||
{
|
{
|
||||||
let accept_matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
|
let accept_matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
|
||||||
let content_matcher : MaybeMatchContentTypeHeader = Handler::Body::supported_types().into();
|
let content_matcher: MaybeMatchContentTypeHeader = Handler::Body::supported_types().into();
|
||||||
self.0.put(&self.1)
|
self.0
|
||||||
|
.put(&self.1)
|
||||||
.extend_route_matcher(accept_matcher)
|
.extend_route_matcher(accept_matcher)
|
||||||
.extend_route_matcher(content_matcher)
|
.extend_route_matcher(content_matcher)
|
||||||
.to(|state| change_all_handler::<Handler>(state));
|
.to(|state| change_all_handler::<Handler>(state));
|
||||||
|
@ -411,15 +383,16 @@ macro_rules! implDrawResourceRoutes {
|
||||||
self.0.cors(&self.1, Method::PUT);
|
self.0.cors(&self.1, Method::PUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn change<Handler : ResourceChange>(&mut self)
|
fn change<Handler: ResourceChange>(&mut self)
|
||||||
where
|
where
|
||||||
Handler::Res : Send + 'static,
|
Handler::Res: Send + 'static,
|
||||||
Handler::Body : 'static
|
Handler::Body: 'static
|
||||||
{
|
{
|
||||||
let accept_matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
|
let accept_matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
|
||||||
let content_matcher : MaybeMatchContentTypeHeader = Handler::Body::supported_types().into();
|
let content_matcher: MaybeMatchContentTypeHeader = Handler::Body::supported_types().into();
|
||||||
let path = format!("{}/:id", self.1);
|
let path = format!("{}/:id", self.1);
|
||||||
self.0.put(&path)
|
self.0
|
||||||
|
.put(&path)
|
||||||
.extend_route_matcher(accept_matcher)
|
.extend_route_matcher(accept_matcher)
|
||||||
.extend_route_matcher(content_matcher)
|
.extend_route_matcher(content_matcher)
|
||||||
.with_path_extractor::<PathExtractor<Handler::ID>>()
|
.with_path_extractor::<PathExtractor<Handler::ID>>()
|
||||||
|
@ -428,21 +401,21 @@ macro_rules! implDrawResourceRoutes {
|
||||||
self.0.cors(&path, Method::PUT);
|
self.0.cors(&path, Method::PUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_all<Handler : ResourceRemoveAll>(&mut self)
|
fn remove_all<Handler: ResourceRemoveAll>(&mut self) {
|
||||||
{
|
let matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
|
||||||
let matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
|
self.0
|
||||||
self.0.delete(&self.1)
|
.delete(&self.1)
|
||||||
.extend_route_matcher(matcher)
|
.extend_route_matcher(matcher)
|
||||||
.to(|state| remove_all_handler::<Handler>(state));
|
.to(|state| remove_all_handler::<Handler>(state));
|
||||||
#[cfg(feature = "cors")]
|
#[cfg(feature = "cors")]
|
||||||
self.0.cors(&self.1, Method::DELETE);
|
self.0.cors(&self.1, Method::DELETE);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove<Handler : ResourceRemove>(&mut self)
|
fn remove<Handler: ResourceRemove>(&mut self) {
|
||||||
{
|
let matcher: MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
|
||||||
let matcher : MaybeMatchAcceptHeader = Handler::Res::accepted_types().into();
|
|
||||||
let path = format!("{}/:id", self.1);
|
let path = format!("{}/:id", self.1);
|
||||||
self.0.delete(&path)
|
self.0
|
||||||
|
.delete(&path)
|
||||||
.extend_route_matcher(matcher)
|
.extend_route_matcher(matcher)
|
||||||
.with_path_extractor::<PathExtractor<Handler::ID>>()
|
.with_path_extractor::<PathExtractor<Handler::ID>>()
|
||||||
.to(|state| remove_handler::<Handler>(state));
|
.to(|state| remove_handler::<Handler>(state));
|
||||||
|
@ -450,7 +423,7 @@ macro_rules! implDrawResourceRoutes {
|
||||||
self.0.cors(&path, Method::POST);
|
self.0.cors(&path, Method::POST);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
implDrawResourceRoutes!(RouterBuilder);
|
implDrawResourceRoutes!(RouterBuilder);
|
||||||
|
|
65
src/types.rs
65
src/types.rs
|
@ -4,43 +4,26 @@ use crate::OpenapiType;
|
||||||
use gotham::hyper::body::Bytes;
|
use gotham::hyper::body::Bytes;
|
||||||
use mime::{Mime, APPLICATION_JSON};
|
use mime::{Mime, APPLICATION_JSON};
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
use std::{
|
use std::{error::Error, panic::RefUnwindSafe};
|
||||||
error::Error,
|
|
||||||
panic::RefUnwindSafe
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(not(feature = "openapi"))]
|
#[cfg(not(feature = "openapi"))]
|
||||||
pub trait ResourceType
|
pub trait ResourceType {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "openapi"))]
|
#[cfg(not(feature = "openapi"))]
|
||||||
impl<T> ResourceType for T
|
impl<T> ResourceType for T {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
pub trait ResourceType : OpenapiType
|
pub trait ResourceType: OpenapiType {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "openapi")]
|
#[cfg(feature = "openapi")]
|
||||||
impl<T : OpenapiType> ResourceType for T
|
impl<T: OpenapiType> ResourceType for T {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// A type that can be used inside a response body. Implemented for every type that is
|
/// 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
|
/// serializable with serde. If the `openapi` feature is used, it must also be of type
|
||||||
/// `OpenapiType`.
|
/// `OpenapiType`.
|
||||||
pub trait ResponseBody : ResourceType + Serialize
|
pub trait ResponseBody: ResourceType + Serialize {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T : ResourceType + Serialize> ResponseBody for T
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
|
impl<T: ResourceType + Serialize> ResponseBody for T {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
This trait should be implemented for every type that can be built from an HTTP request body
|
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
|
[`Bytes`]: ../bytes/struct.Bytes.html
|
||||||
[`Mime`]: ../mime/struct.Mime.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
|
/// 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
|
/// macro, there is no way to trigger an error, so `Infallible` is used here. However, this
|
||||||
/// might change in the future.
|
/// might change in the future.
|
||||||
type Err : Error;
|
type Err: Error;
|
||||||
|
|
||||||
/// Perform the conversion.
|
/// Perform the conversion.
|
||||||
fn from_body(body : Bytes, content_type : Mime) -> Result<Self, Self::Err>;
|
fn from_body(body: Bytes, content_type: Mime) -> Result<Self, Self::Err>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T : DeserializeOwned> FromBody for T
|
impl<T: DeserializeOwned> FromBody for T {
|
||||||
{
|
|
||||||
type Err = serde_json::Error;
|
type Err = serde_json::Error;
|
||||||
|
|
||||||
fn from_body(body : Bytes, _content_type : Mime) -> Result<Self, Self::Err>
|
fn from_body(body: Bytes, _content_type: Mime) -> Result<Self, Self::Err> {
|
||||||
{
|
|
||||||
serde_json::from_slice(&body)
|
serde_json::from_slice(&body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
A type that can be used inside a request body. Implemented for every type that is deserializable
|
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`].
|
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
|
[`FromBody`]: trait.FromBody.html
|
||||||
[`OpenapiType`]: trait.OpenapiType.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.
|
/// Return all types that are supported as content types. Use `None` if all types are supported.
|
||||||
fn supported_types() -> Option<Vec<Mime>>
|
fn supported_types() -> Option<Vec<Mime>> {
|
||||||
{
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T : ResourceType + DeserializeOwned> RequestBody for T
|
impl<T: ResourceType + DeserializeOwned> RequestBody for T {
|
||||||
{
|
fn supported_types() -> Option<Vec<Mime>> {
|
||||||
fn supported_types() -> Option<Vec<Mime>>
|
|
||||||
{
|
|
||||||
Some(vec![APPLICATION_JSON])
|
Some(vec![APPLICATION_JSON])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,10 +103,6 @@ impl<T : ResourceType + DeserializeOwned> RequestBody for T
|
||||||
/// A type than can be used as a parameter to a resource method. Implemented for every type
|
/// 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
|
/// that is deserialize and thread-safe. If the `openapi` feature is used, it must also be of
|
||||||
/// type `OpenapiType`.
|
/// type `OpenapiType`.
|
||||||
pub trait ResourceID : ResourceType + DeserializeOwned + Clone + RefUnwindSafe + Send + Sync
|
pub trait ResourceID: ResourceType + DeserializeOwned + Clone + RefUnwindSafe + Send + Sync {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T : ResourceType + DeserializeOwned + Clone + RefUnwindSafe + Send + Sync> ResourceID for T
|
impl<T: ResourceType + DeserializeOwned + Clone + RefUnwindSafe + Send + Sync> ResourceID for T {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
#[macro_use] extern crate gotham_derive;
|
#[macro_use]
|
||||||
|
extern crate gotham_derive;
|
||||||
|
|
||||||
use gotham::{
|
use gotham::{router::builder::*, test::TestServer};
|
||||||
router::builder::*,
|
|
||||||
test::TestServer
|
|
||||||
};
|
|
||||||
use gotham_restful::*;
|
use gotham_restful::*;
|
||||||
use mime::{APPLICATION_JSON, TEXT_PLAIN};
|
use mime::{APPLICATION_JSON, TEXT_PLAIN};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
mod util { include!("util/mod.rs"); }
|
mod util {
|
||||||
use util::{test_get_response, test_post_response, test_put_response, test_delete_response};
|
include!("util/mod.rs");
|
||||||
|
}
|
||||||
|
use util::{test_delete_response, test_get_response, test_post_response, test_put_response};
|
||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
#[resource(read_all, read, search, create, change_all, change, remove_all, remove)]
|
#[resource(read_all, read, search, create, change_all, change, remove_all, remove)]
|
||||||
|
@ -19,88 +18,96 @@ struct FooResource;
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[cfg_attr(feature = "openapi", derive(OpenapiType))]
|
#[cfg_attr(feature = "openapi", derive(OpenapiType))]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
struct FooBody
|
struct FooBody {
|
||||||
{
|
data: String
|
||||||
data : String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, StateData, StaticResponseExtender)]
|
#[derive(Deserialize, StateData, StaticResponseExtender)]
|
||||||
#[cfg_attr(feature = "openapi", derive(OpenapiType))]
|
#[cfg_attr(feature = "openapi", derive(OpenapiType))]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
struct FooSearch
|
struct FooSearch {
|
||||||
{
|
query: String
|
||||||
query : String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const READ_ALL_RESPONSE : &[u8] = b"1ARwwSPVyOKpJKrYwqGgECPVWDl1BqajAAj7g7WJ3e";
|
const READ_ALL_RESPONSE: &[u8] = b"1ARwwSPVyOKpJKrYwqGgECPVWDl1BqajAAj7g7WJ3e";
|
||||||
#[read_all(FooResource)]
|
#[read_all(FooResource)]
|
||||||
async fn read_all() -> Raw<&'static [u8]>
|
async fn read_all() -> Raw<&'static [u8]> {
|
||||||
{
|
|
||||||
Raw::new(READ_ALL_RESPONSE, TEXT_PLAIN)
|
Raw::new(READ_ALL_RESPONSE, TEXT_PLAIN)
|
||||||
}
|
}
|
||||||
|
|
||||||
const READ_RESPONSE : &[u8] = b"FEReHoeBKU17X2bBpVAd1iUvktFL43CDu0cFYHdaP9";
|
const READ_RESPONSE: &[u8] = b"FEReHoeBKU17X2bBpVAd1iUvktFL43CDu0cFYHdaP9";
|
||||||
#[read(FooResource)]
|
#[read(FooResource)]
|
||||||
async fn read(_id : u64) -> Raw<&'static [u8]>
|
async fn read(_id: u64) -> Raw<&'static [u8]> {
|
||||||
{
|
|
||||||
Raw::new(READ_RESPONSE, TEXT_PLAIN)
|
Raw::new(READ_RESPONSE, TEXT_PLAIN)
|
||||||
}
|
}
|
||||||
|
|
||||||
const SEARCH_RESPONSE : &[u8] = b"AWqcQUdBRHXKh3at4u79mdupOAfEbnTcx71ogCVF0E";
|
const SEARCH_RESPONSE: &[u8] = b"AWqcQUdBRHXKh3at4u79mdupOAfEbnTcx71ogCVF0E";
|
||||||
#[search(FooResource)]
|
#[search(FooResource)]
|
||||||
async fn search(_body : FooSearch) -> Raw<&'static [u8]>
|
async fn search(_body: FooSearch) -> Raw<&'static [u8]> {
|
||||||
{
|
|
||||||
Raw::new(SEARCH_RESPONSE, TEXT_PLAIN)
|
Raw::new(SEARCH_RESPONSE, TEXT_PLAIN)
|
||||||
}
|
}
|
||||||
|
|
||||||
const CREATE_RESPONSE : &[u8] = b"y6POY7wOMAB0jBRBw0FJT7DOpUNbhmT8KdpQPLkI83";
|
const CREATE_RESPONSE: &[u8] = b"y6POY7wOMAB0jBRBw0FJT7DOpUNbhmT8KdpQPLkI83";
|
||||||
#[create(FooResource)]
|
#[create(FooResource)]
|
||||||
async fn create(_body : FooBody) -> Raw<&'static [u8]>
|
async fn create(_body: FooBody) -> Raw<&'static [u8]> {
|
||||||
{
|
|
||||||
Raw::new(CREATE_RESPONSE, TEXT_PLAIN)
|
Raw::new(CREATE_RESPONSE, TEXT_PLAIN)
|
||||||
}
|
}
|
||||||
|
|
||||||
const CHANGE_ALL_RESPONSE : &[u8] = b"QlbYg8gHE9OQvvk3yKjXJLTSXlIrg9mcqhfMXJmQkv";
|
const CHANGE_ALL_RESPONSE: &[u8] = b"QlbYg8gHE9OQvvk3yKjXJLTSXlIrg9mcqhfMXJmQkv";
|
||||||
#[change_all(FooResource)]
|
#[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)
|
Raw::new(CHANGE_ALL_RESPONSE, TEXT_PLAIN)
|
||||||
}
|
}
|
||||||
|
|
||||||
const CHANGE_RESPONSE : &[u8] = b"qGod55RUXkT1lgPO8h0uVM6l368O2S0GrwENZFFuRu";
|
const CHANGE_RESPONSE: &[u8] = b"qGod55RUXkT1lgPO8h0uVM6l368O2S0GrwENZFFuRu";
|
||||||
#[change(FooResource)]
|
#[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)
|
Raw::new(CHANGE_RESPONSE, TEXT_PLAIN)
|
||||||
}
|
}
|
||||||
|
|
||||||
const REMOVE_ALL_RESPONSE : &[u8] = b"Y36kZ749MRk2Nem4BedJABOZiZWPLOtiwLfJlGTwm5";
|
const REMOVE_ALL_RESPONSE: &[u8] = b"Y36kZ749MRk2Nem4BedJABOZiZWPLOtiwLfJlGTwm5";
|
||||||
#[remove_all(FooResource)]
|
#[remove_all(FooResource)]
|
||||||
async fn remove_all() -> Raw<&'static [u8]>
|
async fn remove_all() -> Raw<&'static [u8]> {
|
||||||
{
|
|
||||||
Raw::new(REMOVE_ALL_RESPONSE, TEXT_PLAIN)
|
Raw::new(REMOVE_ALL_RESPONSE, TEXT_PLAIN)
|
||||||
}
|
}
|
||||||
|
|
||||||
const REMOVE_RESPONSE : &[u8] = b"CwRzBrKErsVZ1N7yeNfjZuUn1MacvgBqk4uPOFfDDq";
|
const REMOVE_RESPONSE: &[u8] = b"CwRzBrKErsVZ1N7yeNfjZuUn1MacvgBqk4uPOFfDDq";
|
||||||
#[remove(FooResource)]
|
#[remove(FooResource)]
|
||||||
async fn remove(_id : u64) -> Raw<&'static [u8]>
|
async fn remove(_id: u64) -> Raw<&'static [u8]> {
|
||||||
{
|
|
||||||
Raw::new(REMOVE_RESPONSE, TEXT_PLAIN)
|
Raw::new(REMOVE_RESPONSE, TEXT_PLAIN)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn async_methods()
|
fn async_methods() {
|
||||||
{
|
|
||||||
let server = TestServer::new(build_simple_router(|router| {
|
let server = TestServer::new(build_simple_router(|router| {
|
||||||
router.resource::<FooResource>("foo");
|
router.resource::<FooResource>("foo");
|
||||||
})).unwrap();
|
}))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
test_get_response(&server, "http://localhost/foo", READ_ALL_RESPONSE);
|
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/1", READ_RESPONSE);
|
||||||
test_get_response(&server, "http://localhost/foo/search?query=hello+world", SEARCH_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_post_response(
|
||||||
test_put_response(&server, "http://localhost/foo", r#"{"data":"hello world"}"#, APPLICATION_JSON, CHANGE_ALL_RESPONSE);
|
&server,
|
||||||
test_put_response(&server, "http://localhost/foo/1", r#"{"data":"hello world"}"#, APPLICATION_JSON, CHANGE_RESPONSE);
|
"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", REMOVE_ALL_RESPONSE);
|
||||||
test_delete_response(&server, "http://localhost/foo/1", REMOVE_RESPONSE);
|
test_delete_response(&server, "http://localhost/foo/1", REMOVE_RESPONSE);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use gotham::{
|
||||||
router::builder::*,
|
router::builder::*,
|
||||||
test::{Server, TestRequest, TestServer}
|
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 itertools::Itertools;
|
||||||
use mime::TEXT_PLAIN;
|
use mime::TEXT_PLAIN;
|
||||||
|
|
||||||
|
@ -14,70 +14,108 @@ use mime::TEXT_PLAIN;
|
||||||
struct FooResource;
|
struct FooResource;
|
||||||
|
|
||||||
#[read_all(FooResource)]
|
#[read_all(FooResource)]
|
||||||
fn read_all()
|
fn read_all() {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
#[change_all(FooResource)]
|
#[change_all(FooResource)]
|
||||||
fn change_all(_body : Raw<Vec<u8>>)
|
fn change_all(_body: Raw<Vec<u8>>) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
fn test_server(cfg : CorsConfig) -> TestServer
|
fn test_server(cfg: CorsConfig) -> TestServer {
|
||||||
{
|
|
||||||
let (chain, pipeline) = single_pipeline(new_pipeline().add(cfg).build());
|
let (chain, pipeline) = single_pipeline(new_pipeline().add(cfg).build());
|
||||||
TestServer::new(build_router(chain, pipeline, |router| {
|
TestServer::new(build_router(chain, pipeline, |router| router.resource::<FooResource>("/foo"))).unwrap()
|
||||||
router.resource::<FooResource>("/foo")
|
|
||||||
})).unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_response<TS, C>(req : TestRequest<TS, C>, origin : Option<&str>, vary : Option<&str>, credentials : bool)
|
fn test_response<TS, C>(req: TestRequest<TS, C>, origin: Option<&str>, vary: Option<&str>, credentials: bool)
|
||||||
where
|
where
|
||||||
TS : Server + 'static,
|
TS: Server + 'static,
|
||||||
C : Connect + Clone + Send + Sync + '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);
|
assert_eq!(res.status(), StatusCode::NO_CONTENT);
|
||||||
let headers = res.headers();
|
let headers = res.headers();
|
||||||
println!("{}", headers.keys().join(","));
|
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(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());
|
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)
|
fn test_preflight(server: &TestServer, method: &str, origin: Option<&str>, vary: &str, credentials: bool, max_age: u64) {
|
||||||
{
|
let res = server
|
||||||
let res = server.client().options("http://example.org/foo")
|
.client()
|
||||||
|
.options("http://example.org/foo")
|
||||||
.with_header(ACCESS_CONTROL_REQUEST_METHOD, method.parse().unwrap())
|
.with_header(ACCESS_CONTROL_REQUEST_METHOD, method.parse().unwrap())
|
||||||
.with_header(ORIGIN, "http://example.org".parse().unwrap())
|
.with_header(ORIGIN, "http://example.org".parse().unwrap())
|
||||||
.perform().unwrap();
|
.perform()
|
||||||
|
.unwrap();
|
||||||
assert_eq!(res.status(), StatusCode::NO_CONTENT);
|
assert_eq!(res.status(), StatusCode::NO_CONTENT);
|
||||||
let headers = res.headers();
|
let headers = res.headers();
|
||||||
println!("{}", headers.keys().join(","));
|
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!(
|
||||||
assert_eq!(headers.get(ACCESS_CONTROL_ALLOW_ORIGIN).and_then(|value| value.to_str().ok()).as_deref(), origin);
|
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(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!(
|
||||||
assert_eq!(headers.get(ACCESS_CONTROL_MAX_AGE).and_then(|value| value.to_str().ok()).and_then(|value| value.parse().ok()), Some(max_age));
|
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]
|
#[test]
|
||||||
fn cors_origin_none()
|
fn cors_origin_none() {
|
||||||
{
|
|
||||||
let cfg = Default::default();
|
let cfg = Default::default();
|
||||||
let server = test_server(cfg);
|
let server = test_server(cfg);
|
||||||
|
|
||||||
test_preflight(&server, "PUT", None, "Access-Control-Request-Method", false, 0);
|
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().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]
|
#[test]
|
||||||
fn cors_origin_star()
|
fn cors_origin_star() {
|
||||||
{
|
|
||||||
let cfg = CorsConfig {
|
let cfg = CorsConfig {
|
||||||
origin: Origin::Star,
|
origin: Origin::Star,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -87,42 +125,78 @@ fn cors_origin_star()
|
||||||
test_preflight(&server, "PUT", Some("*"), "Access-Control-Request-Method", false, 0);
|
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().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]
|
#[test]
|
||||||
fn cors_origin_single()
|
fn cors_origin_single() {
|
||||||
{
|
|
||||||
let cfg = CorsConfig {
|
let cfg = CorsConfig {
|
||||||
origin: Origin::Single("https://foo.com".to_owned()),
|
origin: Origin::Single("https://foo.com".to_owned()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let server = test_server(cfg);
|
let server = test_server(cfg);
|
||||||
|
|
||||||
test_preflight(&server, "PUT", Some("https://foo.com"), "Access-Control-Request-Method", false, 0);
|
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(
|
||||||
test_response(server.client().put("http://example.org/foo", Body::empty(), TEXT_PLAIN), Some("https://foo.com"), None, false);
|
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]
|
#[test]
|
||||||
fn cors_origin_copy()
|
fn cors_origin_copy() {
|
||||||
{
|
|
||||||
let cfg = CorsConfig {
|
let cfg = CorsConfig {
|
||||||
origin: Origin::Copy,
|
origin: Origin::Copy,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let server = test_server(cfg);
|
let server = test_server(cfg);
|
||||||
|
|
||||||
test_preflight(&server, "PUT", Some("http://example.org"), "Access-Control-Request-Method,Origin", false, 0);
|
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(
|
||||||
test_response(server.client().put("http://example.org/foo", Body::empty(), TEXT_PLAIN), Some("http://example.org"), Some("Origin"), false);
|
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]
|
#[test]
|
||||||
fn cors_credentials()
|
fn cors_credentials() {
|
||||||
{
|
|
||||||
let cfg = CorsConfig {
|
let cfg = CorsConfig {
|
||||||
origin: Origin::None,
|
origin: Origin::None,
|
||||||
credentials: true,
|
credentials: true,
|
||||||
|
@ -133,12 +207,16 @@ fn cors_credentials()
|
||||||
test_preflight(&server, "PUT", None, "Access-Control-Request-Method", true, 0);
|
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().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]
|
#[test]
|
||||||
fn cors_max_age()
|
fn cors_max_age() {
|
||||||
{
|
|
||||||
let cfg = CorsConfig {
|
let cfg = CorsConfig {
|
||||||
origin: Origin::None,
|
origin: Origin::None,
|
||||||
max_age: 31536000,
|
max_age: 31536000,
|
||||||
|
@ -149,5 +227,10 @@ fn cors_max_age()
|
||||||
test_preflight(&server, "PUT", None, "Access-Control-Request-Method", false, 31536000);
|
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().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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,8 @@
|
||||||
use gotham::{
|
use gotham::{hyper::header::CONTENT_TYPE, router::builder::*, test::TestServer};
|
||||||
hyper::header::CONTENT_TYPE,
|
|
||||||
router::builder::*,
|
|
||||||
test::TestServer
|
|
||||||
};
|
|
||||||
use gotham_restful::*;
|
use gotham_restful::*;
|
||||||
use mime::TEXT_PLAIN;
|
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)]
|
#[derive(Resource)]
|
||||||
#[resource(create)]
|
#[resource(create)]
|
||||||
|
@ -21,23 +16,24 @@ struct Foo {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[create(FooResource)]
|
#[create(FooResource)]
|
||||||
fn create(body : Foo) -> Raw<Vec<u8>> {
|
fn create(body: Foo) -> Raw<Vec<u8>> {
|
||||||
Raw::new(body.content, body.content_type)
|
Raw::new(body.content, body.content_type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn custom_request_body()
|
fn custom_request_body() {
|
||||||
{
|
|
||||||
let server = TestServer::new(build_simple_router(|router| {
|
let server = TestServer::new(build_simple_router(|router| {
|
||||||
router.resource::<FooResource>("foo");
|
router.resource::<FooResource>("foo");
|
||||||
})).unwrap();
|
}))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let res = server.client()
|
let res = server
|
||||||
|
.client()
|
||||||
.post("http://localhost/foo", RESPONSE, TEXT_PLAIN)
|
.post("http://localhost/foo", RESPONSE, TEXT_PLAIN)
|
||||||
.perform().unwrap();
|
.perform()
|
||||||
|
.unwrap();
|
||||||
assert_eq!(res.headers().get(CONTENT_TYPE).unwrap().to_str().unwrap(), "text/plain");
|
assert_eq!(res.headers().get(CONTENT_TYPE).unwrap().to_str().unwrap(), "text/plain");
|
||||||
let res = res.read_body().unwrap();
|
let res = res.read_body().unwrap();
|
||||||
let body : &[u8] = res.as_ref();
|
let body: &[u8] = res.as_ref();
|
||||||
assert_eq!(body, RESPONSE);
|
assert_eq!(body, RESPONSE);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#![cfg(all(feature = "auth", feature = "chrono", feature = "openapi"))]
|
#![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 chrono::{NaiveDate, NaiveDateTime};
|
||||||
use gotham::{
|
use gotham::{
|
||||||
|
@ -13,10 +14,11 @@ use mime::IMAGE_PNG;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
mod util { include!("util/mod.rs"); }
|
mod util {
|
||||||
|
include!("util/mod.rs");
|
||||||
|
}
|
||||||
use util::{test_get_response, test_openapi_response};
|
use util::{test_get_response, test_openapi_response};
|
||||||
|
|
||||||
|
|
||||||
const IMAGE_RESPONSE : &[u8] = b"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUA/wA0XsCoAAAAAXRSTlN/gFy0ywAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII=";
|
const IMAGE_RESPONSE : &[u8] = b"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUA/wA0XsCoAAAAAXRSTlN/gFy0ywAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII=";
|
||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
|
@ -28,71 +30,59 @@ struct ImageResource;
|
||||||
struct Image(Vec<u8>);
|
struct Image(Vec<u8>);
|
||||||
|
|
||||||
#[read(ImageResource, operation_id = "getImage")]
|
#[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())
|
Raw::new(IMAGE_RESPONSE, "image/png;base64".parse().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[change(ImageResource, operation_id = "setImage")]
|
#[change(ImageResource, operation_id = "setImage")]
|
||||||
fn set_image(_id : u64, _image : Image)
|
fn set_image(_id: u64, _image: Image) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
#[resource(read, search)]
|
#[resource(read, search)]
|
||||||
struct SecretResource;
|
struct SecretResource;
|
||||||
|
|
||||||
#[derive(Deserialize, Clone)]
|
#[derive(Deserialize, Clone)]
|
||||||
struct AuthData
|
struct AuthData {
|
||||||
{
|
sub: String,
|
||||||
sub : String,
|
iat: u64,
|
||||||
iat : u64,
|
exp: u64
|
||||||
exp : u64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthStatus = gotham_restful::AuthStatus<AuthData>;
|
type AuthStatus = gotham_restful::AuthStatus<AuthData>;
|
||||||
|
|
||||||
#[derive(OpenapiType, Serialize)]
|
#[derive(OpenapiType, Serialize)]
|
||||||
struct Secret
|
struct Secret {
|
||||||
{
|
code: f32
|
||||||
code : f32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(OpenapiType, Serialize)]
|
#[derive(OpenapiType, Serialize)]
|
||||||
struct Secrets
|
struct Secrets {
|
||||||
{
|
secrets: Vec<Secret>
|
||||||
secrets : Vec<Secret>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, OpenapiType, StateData, StaticResponseExtender)]
|
#[derive(Deserialize, OpenapiType, StateData, StaticResponseExtender)]
|
||||||
struct SecretQuery
|
struct SecretQuery {
|
||||||
{
|
date: NaiveDate,
|
||||||
date : NaiveDate,
|
hour: Option<u16>,
|
||||||
hour : Option<u16>,
|
minute: Option<u16>
|
||||||
minute : Option<u16>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[read(SecretResource)]
|
#[read(SecretResource)]
|
||||||
fn read_secret(auth : AuthStatus, _id : NaiveDateTime) -> AuthSuccess<Secret>
|
fn read_secret(auth: AuthStatus, _id: NaiveDateTime) -> AuthSuccess<Secret> {
|
||||||
{
|
|
||||||
auth.ok()?;
|
auth.ok()?;
|
||||||
Ok(Secret { code: 4.2 })
|
Ok(Secret { code: 4.2 })
|
||||||
}
|
}
|
||||||
|
|
||||||
#[search(SecretResource)]
|
#[search(SecretResource)]
|
||||||
fn search_secret(auth : AuthStatus, _query : SecretQuery) -> AuthSuccess<Secrets>
|
fn search_secret(auth: AuthStatus, _query: SecretQuery) -> AuthSuccess<Secrets> {
|
||||||
{
|
|
||||||
auth.ok()?;
|
auth.ok()?;
|
||||||
Ok(Secrets {
|
Ok(Secrets {
|
||||||
secrets: vec![Secret { code: 4.2 }, Secret { code: 3.14 }]
|
secrets: vec![Secret { code: 4.2 }, Secret { code: 3.14 }]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn openapi_supports_scope()
|
fn openapi_supports_scope() {
|
||||||
{
|
|
||||||
let info = OpenapiInfo {
|
let info = OpenapiInfo {
|
||||||
title: "This is just a test".to_owned(),
|
title: "This is just a test".to_owned(),
|
||||||
version: "1.2.3".to_owned(),
|
version: "1.2.3".to_owned(),
|
||||||
|
@ -110,7 +100,8 @@ fn openapi_supports_scope()
|
||||||
router.get_openapi("openapi");
|
router.get_openapi("openapi");
|
||||||
router.resource::<SecretResource>("secret");
|
router.resource::<SecretResource>("secret");
|
||||||
});
|
});
|
||||||
})).unwrap();
|
}))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
test_openapi_response(&server, "http://localhost/openapi", "tests/openapi_specification.json");
|
test_openapi_response(&server, "http://localhost/openapi", "tests/openapi_specification.json");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,27 @@
|
||||||
#![cfg(feature = "openapi")]
|
#![cfg(feature = "openapi")]
|
||||||
use gotham::{
|
use gotham::{router::builder::*, test::TestServer};
|
||||||
router::builder::*,
|
|
||||||
test::TestServer
|
|
||||||
};
|
|
||||||
use gotham_restful::*;
|
use gotham_restful::*;
|
||||||
use mime::TEXT_PLAIN;
|
use mime::TEXT_PLAIN;
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
mod util { include!("util/mod.rs"); }
|
mod util {
|
||||||
|
include!("util/mod.rs");
|
||||||
|
}
|
||||||
use util::{test_get_response, test_openapi_response};
|
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)]
|
#[derive(Resource)]
|
||||||
#[resource(read_all)]
|
#[resource(read_all)]
|
||||||
struct FooResource;
|
struct FooResource;
|
||||||
|
|
||||||
#[read_all(FooResource)]
|
#[read_all(FooResource)]
|
||||||
fn read_all() -> Raw<&'static [u8]>
|
fn read_all() -> Raw<&'static [u8]> {
|
||||||
{
|
|
||||||
Raw::new(RESPONSE, TEXT_PLAIN)
|
Raw::new(RESPONSE, TEXT_PLAIN)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn openapi_supports_scope()
|
fn openapi_supports_scope() {
|
||||||
{
|
|
||||||
let info = OpenapiInfo {
|
let info = OpenapiInfo {
|
||||||
title: "Test".to_owned(),
|
title: "Test".to_owned(),
|
||||||
version: "1.2.3".to_owned(),
|
version: "1.2.3".to_owned(),
|
||||||
|
@ -44,7 +39,8 @@ fn openapi_supports_scope()
|
||||||
});
|
});
|
||||||
router.resource::<FooResource>("foo4");
|
router.resource::<FooResource>("foo4");
|
||||||
});
|
});
|
||||||
})).unwrap();
|
}))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
test_get_response(&server, "http://localhost/foo1", RESPONSE);
|
test_get_response(&server, "http://localhost/foo1", RESPONSE);
|
||||||
test_get_response(&server, "http://localhost/bar/foo2", RESPONSE);
|
test_get_response(&server, "http://localhost/bar/foo2", RESPONSE);
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
#[macro_use] extern crate gotham_derive;
|
#[macro_use]
|
||||||
|
extern crate gotham_derive;
|
||||||
|
|
||||||
use gotham::{
|
use gotham::{router::builder::*, test::TestServer};
|
||||||
router::builder::*,
|
|
||||||
test::TestServer
|
|
||||||
};
|
|
||||||
use gotham_restful::*;
|
use gotham_restful::*;
|
||||||
use mime::{APPLICATION_JSON, TEXT_PLAIN};
|
use mime::{APPLICATION_JSON, TEXT_PLAIN};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
mod util { include!("util/mod.rs"); }
|
mod util {
|
||||||
use util::{test_get_response, test_post_response, test_put_response, test_delete_response};
|
include!("util/mod.rs");
|
||||||
|
}
|
||||||
|
use util::{test_delete_response, test_get_response, test_post_response, test_put_response};
|
||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
#[resource(read_all, read, search, create, change_all, change, remove_all, remove)]
|
#[resource(read_all, read, search, create, change_all, change, remove_all, remove)]
|
||||||
|
@ -19,88 +18,96 @@ struct FooResource;
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[cfg_attr(feature = "openapi", derive(OpenapiType))]
|
#[cfg_attr(feature = "openapi", derive(OpenapiType))]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
struct FooBody
|
struct FooBody {
|
||||||
{
|
data: String
|
||||||
data : String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, StateData, StaticResponseExtender)]
|
#[derive(Deserialize, StateData, StaticResponseExtender)]
|
||||||
#[cfg_attr(feature = "openapi", derive(OpenapiType))]
|
#[cfg_attr(feature = "openapi", derive(OpenapiType))]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
struct FooSearch
|
struct FooSearch {
|
||||||
{
|
query: String
|
||||||
query : String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const READ_ALL_RESPONSE : &[u8] = b"1ARwwSPVyOKpJKrYwqGgECPVWDl1BqajAAj7g7WJ3e";
|
const READ_ALL_RESPONSE: &[u8] = b"1ARwwSPVyOKpJKrYwqGgECPVWDl1BqajAAj7g7WJ3e";
|
||||||
#[read_all(FooResource)]
|
#[read_all(FooResource)]
|
||||||
fn read_all() -> Raw<&'static [u8]>
|
fn read_all() -> Raw<&'static [u8]> {
|
||||||
{
|
|
||||||
Raw::new(READ_ALL_RESPONSE, TEXT_PLAIN)
|
Raw::new(READ_ALL_RESPONSE, TEXT_PLAIN)
|
||||||
}
|
}
|
||||||
|
|
||||||
const READ_RESPONSE : &[u8] = b"FEReHoeBKU17X2bBpVAd1iUvktFL43CDu0cFYHdaP9";
|
const READ_RESPONSE: &[u8] = b"FEReHoeBKU17X2bBpVAd1iUvktFL43CDu0cFYHdaP9";
|
||||||
#[read(FooResource)]
|
#[read(FooResource)]
|
||||||
fn read(_id : u64) -> Raw<&'static [u8]>
|
fn read(_id: u64) -> Raw<&'static [u8]> {
|
||||||
{
|
|
||||||
Raw::new(READ_RESPONSE, TEXT_PLAIN)
|
Raw::new(READ_RESPONSE, TEXT_PLAIN)
|
||||||
}
|
}
|
||||||
|
|
||||||
const SEARCH_RESPONSE : &[u8] = b"AWqcQUdBRHXKh3at4u79mdupOAfEbnTcx71ogCVF0E";
|
const SEARCH_RESPONSE: &[u8] = b"AWqcQUdBRHXKh3at4u79mdupOAfEbnTcx71ogCVF0E";
|
||||||
#[search(FooResource)]
|
#[search(FooResource)]
|
||||||
fn search(_body : FooSearch) -> Raw<&'static [u8]>
|
fn search(_body: FooSearch) -> Raw<&'static [u8]> {
|
||||||
{
|
|
||||||
Raw::new(SEARCH_RESPONSE, TEXT_PLAIN)
|
Raw::new(SEARCH_RESPONSE, TEXT_PLAIN)
|
||||||
}
|
}
|
||||||
|
|
||||||
const CREATE_RESPONSE : &[u8] = b"y6POY7wOMAB0jBRBw0FJT7DOpUNbhmT8KdpQPLkI83";
|
const CREATE_RESPONSE: &[u8] = b"y6POY7wOMAB0jBRBw0FJT7DOpUNbhmT8KdpQPLkI83";
|
||||||
#[create(FooResource)]
|
#[create(FooResource)]
|
||||||
fn create(_body : FooBody) -> Raw<&'static [u8]>
|
fn create(_body: FooBody) -> Raw<&'static [u8]> {
|
||||||
{
|
|
||||||
Raw::new(CREATE_RESPONSE, TEXT_PLAIN)
|
Raw::new(CREATE_RESPONSE, TEXT_PLAIN)
|
||||||
}
|
}
|
||||||
|
|
||||||
const CHANGE_ALL_RESPONSE : &[u8] = b"QlbYg8gHE9OQvvk3yKjXJLTSXlIrg9mcqhfMXJmQkv";
|
const CHANGE_ALL_RESPONSE: &[u8] = b"QlbYg8gHE9OQvvk3yKjXJLTSXlIrg9mcqhfMXJmQkv";
|
||||||
#[change_all(FooResource)]
|
#[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)
|
Raw::new(CHANGE_ALL_RESPONSE, TEXT_PLAIN)
|
||||||
}
|
}
|
||||||
|
|
||||||
const CHANGE_RESPONSE : &[u8] = b"qGod55RUXkT1lgPO8h0uVM6l368O2S0GrwENZFFuRu";
|
const CHANGE_RESPONSE: &[u8] = b"qGod55RUXkT1lgPO8h0uVM6l368O2S0GrwENZFFuRu";
|
||||||
#[change(FooResource)]
|
#[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)
|
Raw::new(CHANGE_RESPONSE, TEXT_PLAIN)
|
||||||
}
|
}
|
||||||
|
|
||||||
const REMOVE_ALL_RESPONSE : &[u8] = b"Y36kZ749MRk2Nem4BedJABOZiZWPLOtiwLfJlGTwm5";
|
const REMOVE_ALL_RESPONSE: &[u8] = b"Y36kZ749MRk2Nem4BedJABOZiZWPLOtiwLfJlGTwm5";
|
||||||
#[remove_all(FooResource)]
|
#[remove_all(FooResource)]
|
||||||
fn remove_all() -> Raw<&'static [u8]>
|
fn remove_all() -> Raw<&'static [u8]> {
|
||||||
{
|
|
||||||
Raw::new(REMOVE_ALL_RESPONSE, TEXT_PLAIN)
|
Raw::new(REMOVE_ALL_RESPONSE, TEXT_PLAIN)
|
||||||
}
|
}
|
||||||
|
|
||||||
const REMOVE_RESPONSE : &[u8] = b"CwRzBrKErsVZ1N7yeNfjZuUn1MacvgBqk4uPOFfDDq";
|
const REMOVE_RESPONSE: &[u8] = b"CwRzBrKErsVZ1N7yeNfjZuUn1MacvgBqk4uPOFfDDq";
|
||||||
#[remove(FooResource)]
|
#[remove(FooResource)]
|
||||||
fn remove(_id : u64) -> Raw<&'static [u8]>
|
fn remove(_id: u64) -> Raw<&'static [u8]> {
|
||||||
{
|
|
||||||
Raw::new(REMOVE_RESPONSE, TEXT_PLAIN)
|
Raw::new(REMOVE_RESPONSE, TEXT_PLAIN)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sync_methods()
|
fn sync_methods() {
|
||||||
{
|
|
||||||
let server = TestServer::new(build_simple_router(|router| {
|
let server = TestServer::new(build_simple_router(|router| {
|
||||||
router.resource::<FooResource>("foo");
|
router.resource::<FooResource>("foo");
|
||||||
})).unwrap();
|
}))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
test_get_response(&server, "http://localhost/foo", READ_ALL_RESPONSE);
|
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/1", READ_RESPONSE);
|
||||||
test_get_response(&server, "http://localhost/foo/search?query=hello+world", SEARCH_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_post_response(
|
||||||
test_put_response(&server, "http://localhost/foo", r#"{"data":"hello world"}"#, APPLICATION_JSON, CHANGE_ALL_RESPONSE);
|
&server,
|
||||||
test_put_response(&server, "http://localhost/foo/1", r#"{"data":"hello world"}"#, APPLICATION_JSON, CHANGE_RESPONSE);
|
"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", REMOVE_ALL_RESPONSE);
|
||||||
test_delete_response(&server, "http://localhost/foo/1", REMOVE_RESPONSE);
|
test_delete_response(&server, "http://localhost/foo/1", REMOVE_RESPONSE);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,7 @@ use trybuild::TestCases;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore]
|
#[ignore]
|
||||||
fn trybuild_ui()
|
fn trybuild_ui() {
|
||||||
{
|
|
||||||
let t = TestCases::new();
|
let t = TestCases::new();
|
||||||
|
|
||||||
// always enabled
|
// always enabled
|
||||||
|
@ -18,8 +17,7 @@ fn trybuild_ui()
|
||||||
t.compile_fail("tests/ui/resource_unknown_method.rs");
|
t.compile_fail("tests/ui/resource_unknown_method.rs");
|
||||||
|
|
||||||
// require the openapi feature
|
// 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_enum_with_fields.rs");
|
||||||
t.compile_fail("tests/ui/openapi_type_nullable_non_bool.rs");
|
t.compile_fail("tests/ui/openapi_type_nullable_non_bool.rs");
|
||||||
t.compile_fail("tests/ui/openapi_type_rename_non_string.rs");
|
t.compile_fail("tests/ui/openapi_type_rename_non_string.rs");
|
||||||
|
|
Loading…
Add table
Reference in a new issue