+
+
gotham-restful
+
+
-This repository contains the following crates:
-
- - **gotham_restful**
- [](https://crates.io/crates/gotham_restful)
- [](https://docs.rs/gotham_restful)
- - **gotham_restful_derive**
- [](https://crates.io/crates/gotham_restful_derive)
- [](https://docs.rs/gotham_restful_derive)
- - **openapi_type**
- [](https://crates.io/crates/openapi_type)
- [](https://docs.rs/crate/openapi_type)
- - **openapi_type_derive**
- [](https://crates.io/crates/openapi_type_derive)
- [](https://docs.rs/crate/openapi_type_derive)
-
-# gotham-restful
+**Note:** The `stable` branch contains some bugfixes against the last release. The `master`
+branch currently tracks gotham's master branch and the next release will use gotham 0.5.0 and be
+compatible with the new future / async stuff.
{{readme}}
-
-## Versioning
-
-Like all rust crates, this crate will follow semantic versioning guidelines. However, changing
-the MSRV (minimum supported rust version) is not considered a breaking change.
-
-## License
-
-Copyright (C) 2020-2021 Dominic Meiser and [contributors](https://gitlab.com/msrd0/gotham-restful/-/graphs/master).
-
-```
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- https://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-```
diff --git a/derive/Cargo.toml b/derive/Cargo.toml
index 58c877e..3511f73 100644
--- a/derive/Cargo.toml
+++ b/derive/Cargo.toml
@@ -2,14 +2,13 @@
[package]
name = "gotham_restful_derive"
-version = "0.3.0-dev"
+version = "0.1.0-rc0"
authors = ["Dominic Meiser
"]
edition = "2018"
-description = "Derive macros for gotham_restful"
-keywords = ["gotham", "rest", "restful", "web", "http"]
-license = "Apache-2.0"
+description = "RESTful additions for the gotham web framework - Derive"
+keywords = ["gotham", "rest", "restful", "web", "http", "derive"]
+license = "EPL-2.0 OR Apache-2.0"
repository = "https://gitlab.com/msrd0/gotham-restful"
-workspace = ".."
[lib]
proc-macro = true
@@ -18,12 +17,10 @@ proc-macro = true
gitlab = { repository = "msrd0/gotham-restful", branch = "master" }
[dependencies]
-once_cell = "1.5"
-paste = "1.0"
+heck = "0.3.1"
proc-macro2 = "1.0.13"
quote = "1.0.6"
-regex = "1.4"
-syn = { version = "1.0.22", features = ["full"] }
+syn = "1.0.22"
[features]
default = []
diff --git a/derive/LICENSE b/derive/LICENSE
deleted file mode 120000
index ea5b606..0000000
--- a/derive/LICENSE
+++ /dev/null
@@ -1 +0,0 @@
-../LICENSE
\ No newline at end of file
diff --git a/derive/LICENSE-Apache b/derive/LICENSE-Apache
new file mode 120000
index 0000000..0cd69a3
--- /dev/null
+++ b/derive/LICENSE-Apache
@@ -0,0 +1 @@
+../LICENSE-Apache
\ No newline at end of file
diff --git a/derive/LICENSE-EPL b/derive/LICENSE-EPL
new file mode 120000
index 0000000..2004d06
--- /dev/null
+++ b/derive/LICENSE-EPL
@@ -0,0 +1 @@
+../LICENSE-EPL
\ No newline at end of file
diff --git a/derive/LICENSE.md b/derive/LICENSE.md
new file mode 120000
index 0000000..7eabdb1
--- /dev/null
+++ b/derive/LICENSE.md
@@ -0,0 +1 @@
+../LICENSE.md
\ No newline at end of file
diff --git a/derive/src/endpoint.rs b/derive/src/endpoint.rs
deleted file mode 100644
index 457f8ee..0000000
--- a/derive/src/endpoint.rs
+++ /dev/null
@@ -1,590 +0,0 @@
-use crate::util::{CollectToResult, ExpectLit, PathEndsWith};
-use once_cell::sync::Lazy;
-use paste::paste;
-use proc_macro2::{Ident, Span, TokenStream};
-use quote::{format_ident, quote, quote_spanned, ToTokens};
-use regex::Regex;
-use std::str::FromStr;
-use syn::{
- parse::Parse, spanned::Spanned, Attribute, AttributeArgs, Error, Expr, FnArg, ItemFn, LitBool, LitStr, Meta, NestedMeta,
- PatType, Result, ReturnType, Type
-};
-
-pub enum EndpointType {
- ReadAll,
- Read,
- Search,
- Create,
- UpdateAll,
- Update,
- DeleteAll,
- Delete,
- Custom {
- method: Option,
- uri: Option,
- params: Option,
- body: Option
- }
-}
-
-impl EndpointType {
- pub fn custom() -> Self {
- Self::Custom {
- method: None,
- uri: None,
- params: None,
- body: None
- }
- }
-}
-
-macro_rules! endpoint_type_setter {
- ($name:ident : $ty:ty) => {
- impl EndpointType {
- paste! {
- fn [](&mut self, span: Span, []: $ty) -> Result<()> {
- match self {
- Self::Custom { $name, .. } if $name.is_some() => {
- Err(Error::new(span, concat!("`", concat!(stringify!($name), "` must not appear more than once"))))
- },
- Self::Custom { $name, .. } => {
- *$name = Some([]);
- Ok(())
- },
- _ => Err(Error::new(span, concat!("`", concat!(stringify!($name), "` can only be used on custom endpoints"))))
- }
- }
- }
- }
- };
-}
-
-endpoint_type_setter!(method: Expr);
-endpoint_type_setter!(uri: LitStr);
-endpoint_type_setter!(params: LitBool);
-endpoint_type_setter!(body: LitBool);
-
-impl FromStr for EndpointType {
- type Err = Error;
-
- fn from_str(str: &str) -> Result {
- match str {
- "ReadAll" | "read_all" => Ok(Self::ReadAll),
- "Read" | "read" => Ok(Self::Read),
- "Search" | "search" => Ok(Self::Search),
- "Create" | "create" => Ok(Self::Create),
- "ChangeAll" | "change_all" => Ok(Self::UpdateAll),
- "Change" | "change" => Ok(Self::Update),
- "RemoveAll" | "remove_all" => Ok(Self::DeleteAll),
- "Remove" | "remove" => Ok(Self::Delete),
- _ => Err(Error::new(Span::call_site(), format!("Unknown method: `{}'", str)))
- }
- }
-}
-
-static URI_PLACEHOLDER_REGEX: Lazy = Lazy::new(|| Regex::new(r#"(^|/):(?P[^/]+)(/|$)"#).unwrap());
-
-impl EndpointType {
- fn http_method(&self) -> Option {
- let hyper_method = quote!(::gotham_restful::gotham::hyper::Method);
- match self {
- Self::ReadAll | Self::Read | Self::Search => Some(quote!(#hyper_method::GET)),
- Self::Create => Some(quote!(#hyper_method::POST)),
- Self::UpdateAll | Self::Update => Some(quote!(#hyper_method::PUT)),
- Self::DeleteAll | Self::Delete => Some(quote!(#hyper_method::DELETE)),
- Self::Custom { method, .. } => method.as_ref().map(ToTokens::to_token_stream)
- }
- }
-
- fn uri(&self) -> Option {
- match self {
- Self::ReadAll | Self::Create | Self::UpdateAll | Self::DeleteAll => Some(quote!("")),
- Self::Read | Self::Update | Self::Delete => Some(quote!(":id")),
- Self::Search => Some(quote!("search")),
- Self::Custom { uri, .. } => uri.as_ref().map(ToTokens::to_token_stream)
- }
- }
-
- fn has_placeholders(&self) -> LitBool {
- match self {
- Self::ReadAll | Self::Search | Self::Create | Self::UpdateAll | Self::DeleteAll => LitBool {
- value: false,
- span: Span::call_site()
- },
- Self::Read | Self::Update | Self::Delete => LitBool {
- value: true,
- span: Span::call_site()
- },
- Self::Custom { uri, .. } => LitBool {
- value: uri
- .as_ref()
- .map(|uri| URI_PLACEHOLDER_REGEX.is_match(&uri.value()))
- .unwrap_or(false),
- span: Span::call_site()
- }
- }
- }
-
- fn placeholders_ty(&self, arg_ty: Option<&Type>) -> TokenStream {
- match self {
- Self::ReadAll | Self::Search | Self::Create | Self::UpdateAll | Self::DeleteAll => {
- quote!(::gotham_restful::NoopExtractor)
- },
- Self::Read | Self::Update | Self::Delete => quote!(::gotham_restful::private::IdPlaceholder::<#arg_ty>),
- Self::Custom { .. } => {
- if self.has_placeholders().value {
- arg_ty.to_token_stream()
- } else {
- quote!(::gotham_restful::NoopExtractor)
- }
- },
- }
- }
-
- fn needs_params(&self) -> LitBool {
- match self {
- Self::ReadAll | Self::Read | Self::Create | Self::UpdateAll | Self::Update | Self::DeleteAll | Self::Delete => {
- LitBool {
- value: false,
- span: Span::call_site()
- }
- },
- Self::Search => LitBool {
- value: true,
- span: Span::call_site()
- },
- Self::Custom { params, .. } => params.clone().unwrap_or_else(|| LitBool {
- value: false,
- span: Span::call_site()
- })
- }
- }
-
- fn params_ty(&self, arg_ty: Option<&Type>) -> TokenStream {
- match self {
- Self::ReadAll | Self::Read | Self::Create | Self::UpdateAll | Self::Update | Self::DeleteAll | Self::Delete => {
- quote!(::gotham_restful::NoopExtractor)
- },
- Self::Search => quote!(#arg_ty),
- Self::Custom { .. } => {
- if self.needs_params().value {
- arg_ty.to_token_stream()
- } else {
- quote!(::gotham_restful::NoopExtractor)
- }
- },
- }
- }
-
- fn needs_body(&self) -> LitBool {
- match self {
- Self::ReadAll | Self::Read | Self::Search | Self::DeleteAll | Self::Delete => LitBool {
- value: false,
- span: Span::call_site()
- },
- Self::Create | Self::UpdateAll | Self::Update => LitBool {
- value: true,
- span: Span::call_site()
- },
- Self::Custom { body, .. } => body.clone().unwrap_or_else(|| LitBool {
- value: false,
- span: Span::call_site()
- })
- }
- }
-
- fn body_ty(&self, arg_ty: Option<&Type>) -> TokenStream {
- match self {
- Self::ReadAll | Self::Read | Self::Search | Self::DeleteAll | Self::Delete => quote!(()),
- Self::Create | Self::UpdateAll | Self::Update => quote!(#arg_ty),
- Self::Custom { .. } => {
- if self.needs_body().value {
- arg_ty.to_token_stream()
- } else {
- quote!(())
- }
- },
- }
- }
-}
-
-#[allow(clippy::large_enum_variant)]
-enum HandlerArgType {
- StateRef,
- StateMutRef,
- MethodArg(Type),
- DatabaseConnection(Type),
- AuthStatus(Type),
- AuthStatusRef(Type)
-}
-
-impl HandlerArgType {
- fn is_method_arg(&self) -> bool {
- matches!(self, Self::MethodArg(_))
- }
-
- fn is_database_conn(&self) -> bool {
- matches!(self, Self::DatabaseConnection(_))
- }
-
- fn is_auth_status(&self) -> bool {
- matches!(self, Self::AuthStatus(_) | Self::AuthStatusRef(_))
- }
-
- fn ty(&self) -> Option<&Type> {
- match self {
- Self::MethodArg(ty) | Self::DatabaseConnection(ty) | Self::AuthStatus(ty) | Self::AuthStatusRef(ty) => Some(ty),
- _ => None
- }
- }
-
- fn quote_ty(&self) -> Option {
- self.ty().map(|ty| quote!(#ty))
- }
-}
-
-struct HandlerArg {
- ident_span: Span,
- ty: HandlerArgType
-}
-
-impl Spanned for HandlerArg {
- fn span(&self) -> Span {
- self.ident_span
- }
-}
-
-fn interpret_arg_ty(attrs: &[Attribute], name: &str, ty: Type) -> Result {
- let attr = attrs
- .iter()
- .find(|arg| arg.path.segments.iter().any(|path| &path.ident.to_string() == "rest_arg"))
- .map(|arg| arg.tokens.to_string());
-
- // TODO issue a warning for _state usage once diagnostics become stable
- if attr.as_deref() == Some("state") || (attr.is_none() && (name == "state" || name == "_state")) {
- return match ty {
- Type::Reference(ty) => Ok(if ty.mutability.is_none() {
- HandlerArgType::StateRef
- } else {
- HandlerArgType::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")) {
- return Ok(match ty {
- Type::Reference(ty) => HandlerArgType::AuthStatusRef(*ty.elem),
- ty => HandlerArgType::AuthStatus(ty)
- });
- }
-
- if cfg!(feature = "database")
- && (attr.as_deref() == Some("connection") || attr.as_deref() == Some("conn") || (attr.is_none() && name == "conn"))
- {
- return Ok(HandlerArgType::DatabaseConnection(match ty {
- Type::Reference(ty) => *ty.elem,
- ty => ty
- }));
- }
-
- Ok(HandlerArgType::MethodArg(ty))
-}
-
-fn interpret_arg(_index: usize, arg: &PatType) -> Result {
- let pat = &arg.pat;
- let orig_name = quote!(#pat);
- let ty = interpret_arg_ty(&arg.attrs, &orig_name.to_string(), *arg.ty.clone())?;
-
- Ok(HandlerArg {
- ident_span: arg.pat.span(),
- ty
- })
-}
-
-#[cfg(feature = "openapi")]
-fn expand_operation_id(operation_id: Option) -> Option {
- match operation_id {
- Some(operation_id) => Some(quote! {
- fn operation_id() -> Option {
- Some(#operation_id.to_string())
- }
- }),
- None => None
- }
-}
-
-#[cfg(not(feature = "openapi"))]
-fn expand_operation_id(_: Option) -> Option {
- None
-}
-
-fn expand_wants_auth(wants_auth: Option, default: bool) -> TokenStream {
- let wants_auth = wants_auth.unwrap_or_else(|| LitBool {
- value: default,
- span: Span::call_site()
- });
-
- quote! {
- fn wants_auth() -> bool {
- #wants_auth
- }
- }
-}
-
-pub fn endpoint_ident(fn_ident: &Ident) -> Ident {
- format_ident!("{}___gotham_restful_endpoint", fn_ident)
-}
-
-// clippy doesn't realize that vectors can be used in closures
-#[cfg_attr(feature = "cargo-clippy", allow(clippy::needless_collect))]
-fn expand_endpoint_type(mut ty: EndpointType, attrs: AttributeArgs, fun: &ItemFn) -> Result {
- // reject unsafe functions
- if let Some(unsafety) = fun.sig.unsafety {
- return Err(Error::new(unsafety.span(), "Endpoint handler methods must not be unsafe"));
- }
-
- // parse arguments
- let mut debug: bool = false;
- let mut operation_id: Option = None;
- let mut wants_auth: Option = None;
- for meta in attrs {
- match meta {
- NestedMeta::Meta(Meta::NameValue(kv)) => {
- if kv.path.ends_with("debug") {
- debug = kv.lit.expect_bool()?.value;
- } else if kv.path.ends_with("operation_id") {
- operation_id = Some(kv.lit.expect_str()?);
- } else if kv.path.ends_with("wants_auth") {
- wants_auth = Some(kv.lit.expect_bool()?);
- } else if kv.path.ends_with("method") {
- ty.set_method(kv.path.span(), kv.lit.expect_str()?.parse_with(Expr::parse)?)?;
- } else if kv.path.ends_with("uri") {
- ty.set_uri(kv.path.span(), kv.lit.expect_str()?)?;
- } else if kv.path.ends_with("params") {
- ty.set_params(kv.path.span(), kv.lit.expect_bool()?)?;
- } else if kv.path.ends_with("body") {
- ty.set_body(kv.path.span(), kv.lit.expect_bool()?)?;
- } else {
- return Err(Error::new(kv.path.span(), "Unknown attribute"));
- }
- },
- _ => return Err(Error::new(meta.span(), "Invalid attribute syntax"))
- }
- }
- #[cfg(not(feature = "openapi"))]
- if let Some(operation_id) = operation_id {
- return Err(Error::new(
- operation_id.span(),
- "`operation_id` is only supported with the openapi feature"
- ));
- }
-
- // extract arguments into pattern, ident and type
- let args = fun
- .sig
- .inputs
- .iter()
- .enumerate()
- .map(|(i, arg)| match arg {
- FnArg::Typed(arg) => interpret_arg(i, arg),
- FnArg::Receiver(_) => Err(Error::new(arg.span(), "Didn't expect self parameter"))
- })
- .collect_to_result()?;
-
- let fun_vis = &fun.vis;
- let fun_ident = &fun.sig.ident;
- let fun_is_async = fun.sig.asyncness.is_some();
-
- let ident = endpoint_ident(fun_ident);
- let dummy_ident = format_ident!("_IMPL_Endpoint_for_{}", ident);
- let (output_ty, is_no_content) = match &fun.sig.output {
- ReturnType::Default => (quote!(::gotham_restful::NoContent), true),
- ReturnType::Type(_, ty) => (quote!(#ty), false)
- };
- let output_typedef = quote_spanned!(output_ty.span() => type Output = #output_ty;);
-
- let arg_tys = args.iter().filter(|arg| arg.ty.is_method_arg()).collect::>();
- let mut arg_ty_idx = 0;
- let mut next_arg_ty = |return_none: bool| {
- if return_none {
- return Ok(None);
- }
- if arg_ty_idx >= arg_tys.len() {
- return Err(Error::new(fun_ident.span(), "Too few arguments"));
- }
- let ty = arg_tys[arg_ty_idx].ty.ty().unwrap();
- arg_ty_idx += 1;
- Ok(Some(ty))
- };
-
- let http_method = ty.http_method().ok_or_else(|| {
- Error::new(
- Span::call_site(),
- "Missing `method` attribute (e.g. `#[endpoint(method = \"gotham_restful::gotham::hyper::Method::GET\")]`)"
- )
- })?;
- let uri = ty.uri().ok_or_else(|| {
- Error::new(
- Span::call_site(),
- "Missing `uri` attribute (e.g. `#[endpoint(uri = \"custom_endpoint\")]`)"
- )
- })?;
- let has_placeholders = ty.has_placeholders();
- let placeholder_ty = ty.placeholders_ty(next_arg_ty(!has_placeholders.value)?);
- let placeholder_typedef = quote_spanned!(placeholder_ty.span() => type Placeholders = #placeholder_ty;);
- let needs_params = ty.needs_params();
- let params_ty = ty.params_ty(next_arg_ty(!needs_params.value)?);
- let params_typedef = quote_spanned!(params_ty.span() => type Params = #params_ty;);
- let needs_body = ty.needs_body();
- let body_ty = ty.body_ty(next_arg_ty(!needs_body.value)?);
- let body_typedef = quote_spanned!(body_ty.span() => type Body = #body_ty;);
-
- if arg_ty_idx < arg_tys.len() {
- return Err(Error::new(fun_ident.span(), "Too many arguments"));
- }
-
- let mut handle_args: Vec = Vec::new();
- if has_placeholders.value {
- if matches!(ty, EndpointType::Custom { .. }) {
- handle_args.push(quote!(placeholders));
- } else {
- handle_args.push(quote!(placeholders.id));
- }
- }
- if needs_params.value {
- handle_args.push(quote!(params));
- }
- if needs_body.value {
- handle_args.push(quote!(body.unwrap()));
- }
- let handle_args = args.iter().map(|arg| match arg.ty {
- HandlerArgType::StateRef | HandlerArgType::StateMutRef => quote!(state),
- HandlerArgType::MethodArg(_) => handle_args.remove(0),
- HandlerArgType::DatabaseConnection(_) => quote!(&conn),
- HandlerArgType::AuthStatus(_) => quote!(auth),
- HandlerArgType::AuthStatusRef(_) => quote!(&auth)
- });
-
- let expand_handle_content = || {
- let mut state_block = quote!();
- if let Some(arg) = args.iter().find(|arg| arg.ty.is_auth_status()) {
- let auth_ty = arg.ty.quote_ty();
- state_block = quote! {
- #state_block
- let auth: #auth_ty = state.borrow::<#auth_ty>().clone();
- }
- }
-
- let mut handle_content = quote!(#fun_ident(#(#handle_args),*));
- if fun_is_async {
- if let Some(arg) = args.iter().find(|arg| matches!(arg.ty, HandlerArgType::StateRef)) {
- return Err(Error::new(arg.span(), "Endpoint handler functions that are async must not take `&State` as an argument, consider taking `&mut State`"));
- }
- handle_content = quote!(#handle_content.await);
- }
- if is_no_content {
- handle_content = quote!(#handle_content; <::gotham_restful::NoContent as ::std::default::Default>::default())
- }
-
- if let Some(arg) = args.iter().find(|arg| arg.ty.is_database_conn()) {
- let conn_ty = arg.ty.quote_ty();
- state_block = quote! {
- #state_block
- let repo = <::gotham_restful::private::Repo<#conn_ty>>::borrow_from(state).clone();
- };
- handle_content = quote! {
- repo.run::<_, _, ()>(move |conn| {
- Ok({ #handle_content })
- }).await.unwrap()
- };
- }
-
- Ok(quote! {
- use ::gotham_restful::private::FutureExt as _;
- use ::gotham_restful::gotham::state::FromState as _;
- #state_block
- async move {
- #handle_content
- }.boxed()
- })
- };
- let handle_content = match expand_handle_content() {
- Ok(content) => content,
- Err(err) => err.to_compile_error()
- };
-
- let tr8 = if cfg!(feature = "openapi") {
- quote!(::gotham_restful::EndpointWithSchema)
- } else {
- quote!(::gotham_restful::Endpoint)
- };
- let operation_id = expand_operation_id(operation_id);
- let wants_auth = expand_wants_auth(wants_auth, args.iter().any(|arg| arg.ty.is_auth_status()));
- let code = quote! {
- #[doc(hidden)]
- /// `gotham_restful` implementation detail
- #[allow(non_camel_case_types)]
- #fun_vis struct #ident;
-
- #[allow(non_upper_case_globals)]
- static #dummy_ident: () = {
- impl #tr8 for #ident {
- fn http_method() -> ::gotham_restful::gotham::hyper::Method {
- #http_method
- }
-
- fn uri() -> ::std::borrow::Cow<'static, str> {
- { #uri }.into()
- }
-
- #output_typedef
-
- fn has_placeholders() -> bool {
- #has_placeholders
- }
- #placeholder_typedef
-
- fn needs_params() -> bool {
- #needs_params
- }
- #params_typedef
-
- fn needs_body() -> bool {
- #needs_body
- }
- #body_typedef
-
- fn handle<'a>(
- state: &'a mut ::gotham_restful::gotham::state::State,
- placeholders: Self::Placeholders,
- params: Self::Params,
- body: ::std::option::Option
- ) -> ::gotham_restful::private::BoxFuture<'a, Self::Output> {
- #handle_content
- }
-
- #operation_id
- #wants_auth
- }
- };
- };
- if debug {
- eprintln!("{}", code);
- }
- Ok(code)
-}
-
-pub fn expand_endpoint(ty: EndpointType, attrs: AttributeArgs, fun: ItemFn) -> Result {
- let endpoint_type = match expand_endpoint_type(ty, attrs, &fun) {
- Ok(code) => code,
- Err(err) => err.to_compile_error()
- };
- Ok(quote! {
- #fun
- #endpoint_type
- })
-}
diff --git a/derive/src/from_body.rs b/derive/src/from_body.rs
index a4ec7b2..f7c04ad 100644
--- a/derive/src/from_body.rs
+++ b/derive/src/from_body.rs
@@ -1,65 +1,73 @@
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use std::cmp::min;
-use syn::{spanned::Spanned, Data, DeriveInput, Error, Field, Fields, Ident, Result, Type};
+use syn::{
+ spanned::Spanned,
+ Data,
+ DeriveInput,
+ Error,
+ Field,
+ Fields,
+ Ident,
+ Result,
+ Type
+};
-struct ParsedFields {
- fields: Vec<(Ident, Type)>,
- named: bool
+struct ParsedFields
+{
+ fields : Vec<(Ident, Type)>,
+ named : bool
}
-impl ParsedFields {
- fn from_named(fields: I) -> Self
+impl ParsedFields
+{
+ fn from_named(fields : I) -> Self
where
- I: Iterator-
+ I : Iterator
-
{
let fields = fields.map(|field| (field.ident.unwrap(), field.ty)).collect();
Self { fields, named: true }
}
-
- fn from_unnamed(fields: I) -> Self
+
+ fn from_unnamed(fields : I) -> Self
where
- I: Iterator
-
+ I : Iterator
-
{
- let fields = fields
- .enumerate()
- .map(|(i, field)| (format_ident!("arg{}", i), field.ty))
- .collect();
+ let fields = fields.enumerate().map(|(i, field)| (format_ident!("arg{}", i), field.ty)).collect();
Self { fields, named: false }
}
-
- fn from_unit() -> Self {
- Self {
- fields: Vec::new(),
- named: false
- }
+
+ fn from_unit() -> Self
+ {
+ Self { fields: Vec::new(), named: false }
}
}
-pub fn expand_from_body(input: DeriveInput) -> Result {
+pub fn expand_from_body(input : DeriveInput) -> Result
+{
let krate = super::krate();
let ident = input.ident;
let generics = input.generics;
-
+
let strukt = match input.data {
Data::Enum(inum) => Err(inum.enum_token.span()),
Data::Struct(strukt) => Ok(strukt),
Data::Union(uni) => Err(uni.union_token.span())
- }
- .map_err(|span| Error::new(span, "#[derive(FromBody)] only works for structs"))?;
-
+ }.map_err(|span| Error::new(span, "#[derive(FromBody)] only works for structs"))?;
+
let fields = match strukt.fields {
Fields::Named(named) => ParsedFields::from_named(named.named.into_iter()),
Fields::Unnamed(unnamed) => ParsedFields::from_unnamed(unnamed.unnamed.into_iter()),
Fields::Unit => ParsedFields::from_unit()
};
-
+
let mut where_clause = quote!();
let mut block = quote!();
let mut body_ident = format_ident!("_body");
let mut type_ident = format_ident!("_type");
-
- if let Some(body_field) = fields.fields.get(0) {
+
+ if let Some(body_field) = fields.fields.get(0)
+ {
body_ident = body_field.0.clone();
let body_ty = &body_field.1;
where_clause = quote! {
@@ -72,8 +80,9 @@ pub fn expand_from_body(input: DeriveInput) -> Result {
let #body_ident : #body_ty = #body_ident.into();
};
}
-
- if let Some(type_field) = fields.fields.get(1) {
+
+ if let Some(type_field) = fields.fields.get(1)
+ {
type_ident = type_field.0.clone();
let type_ty = &type_field.1;
where_clause = quote! {
@@ -85,8 +94,9 @@ pub fn expand_from_body(input: DeriveInput) -> Result {
let #type_ident : #type_ty = #type_ident.into();
};
}
-
- for field in &fields.fields[min(2, fields.fields.len())..] {
+
+ for field in &fields.fields[min(2, fields.fields.len())..]
+ {
let field_ident = &field.0;
let field_ty = &field.1;
where_clause = quote! {
@@ -98,20 +108,20 @@ pub fn expand_from_body(input: DeriveInput) -> Result {
let #field_ident : #field_ty = Default::default();
};
}
-
- let field_names: Vec<&Ident> = fields.fields.iter().map(|field| &field.0).collect();
+
+ let field_names : Vec<&Ident> = fields.fields.iter().map(|field| &field.0).collect();
let ctor = if fields.named {
quote!(Self { #(#field_names),* })
} else {
quote!(Self ( #(#field_names),* ))
};
-
+
Ok(quote! {
impl #generics #krate::FromBody for #ident #generics
where #where_clause
{
type Err = ::std::convert::Infallible;
-
+
fn from_body(#body_ident : #krate::gotham::hyper::body::Bytes, #type_ident : #krate::Mime) -> Result
{
#block
diff --git a/derive/src/lib.rs b/derive/src/lib.rs
index 59ee8b6..7e0dc00 100644
--- a/derive/src/lib.rs
+++ b/derive/src/lib.rs
@@ -1,7 +1,3 @@
-#![warn(missing_debug_implementations, rust_2018_idioms)]
-#![deny(broken_intra_doc_links)]
-#![forbid(unsafe_code)]
-
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
@@ -9,121 +5,130 @@ use syn::{parse_macro_input, parse_macro_input::ParseMacroInput, DeriveInput, Re
mod util;
-mod endpoint;
-use endpoint::{expand_endpoint, EndpointType};
-
mod from_body;
use from_body::expand_from_body;
-
+mod method;
+use method::{expand_method, Method};
mod request_body;
use request_body::expand_request_body;
-
mod resource;
use resource::expand_resource;
-
mod resource_error;
use resource_error::expand_resource_error;
-
-mod private_openapi_trait;
-use private_openapi_trait::expand_private_openapi_trait;
+#[cfg(feature = "openapi")]
+mod openapi_type;
+#[cfg(feature = "openapi")]
+use openapi_type::expand_openapi_type;
#[inline]
-fn print_tokens(tokens: TokenStream2) -> TokenStream {
- // eprintln!("{}", tokens);
+fn print_tokens(tokens : TokenStream2) -> TokenStream
+{
+ //eprintln!("{}", tokens);
tokens.into()
}
#[inline]
-fn expand_derive(input: TokenStream, expand: F) -> TokenStream
+fn expand_derive(input : TokenStream, expand : F) -> TokenStream
where
- F: FnOnce(DeriveInput) -> Result
+ F : FnOnce(DeriveInput) -> Result
{
- print_tokens(expand(parse_macro_input!(input)).unwrap_or_else(|err| err.to_compile_error()))
+ print_tokens(expand(parse_macro_input!(input))
+ .unwrap_or_else(|err| err.to_compile_error()))
}
#[inline]
-fn expand_macro(attrs: TokenStream, item: TokenStream, expand: F) -> TokenStream
+fn expand_macro(attrs : TokenStream, item : TokenStream, expand : F) -> TokenStream
where
- F: FnOnce(A, I) -> Result,
- A: ParseMacroInput,
- I: ParseMacroInput
+ F : FnOnce(A, I) -> Result,
+ A : ParseMacroInput,
+ I : ParseMacroInput
{
- print_tokens(expand(parse_macro_input!(attrs), parse_macro_input!(item)).unwrap_or_else(|err| err.to_compile_error()))
+ print_tokens(expand(parse_macro_input!(attrs), parse_macro_input!(item))
+ .unwrap_or_else(|err| err.to_compile_error()))
}
#[inline]
-fn krate() -> TokenStream2 {
+fn krate() -> TokenStream2
+{
quote!(::gotham_restful)
}
#[proc_macro_derive(FromBody)]
-pub fn derive_from_body(input: TokenStream) -> TokenStream {
+pub fn derive_from_body(input : TokenStream) -> TokenStream
+{
expand_derive(input, expand_from_body)
}
+#[cfg(feature = "openapi")]
+#[proc_macro_derive(OpenapiType, attributes(openapi))]
+pub fn derive_openapi_type(input : TokenStream) -> TokenStream
+{
+ expand_derive(input, expand_openapi_type)
+}
+
#[proc_macro_derive(RequestBody, attributes(supported_types))]
-pub fn derive_request_body(input: TokenStream) -> TokenStream {
+pub fn derive_request_body(input : TokenStream) -> TokenStream
+{
expand_derive(input, expand_request_body)
}
#[proc_macro_derive(Resource, attributes(resource))]
-pub fn derive_resource(input: TokenStream) -> TokenStream {
+pub fn derive_resource(input : TokenStream) -> TokenStream
+{
expand_derive(input, expand_resource)
}
#[proc_macro_derive(ResourceError, attributes(display, from, status))]
-pub fn derive_resource_error(input: TokenStream) -> TokenStream {
+pub fn derive_resource_error(input : TokenStream) -> TokenStream
+{
expand_derive(input, expand_resource_error)
}
+
#[proc_macro_attribute]
-pub fn endpoint(attr: TokenStream, item: TokenStream) -> TokenStream {
- expand_macro(attr, item, |attr, item| expand_endpoint(EndpointType::custom(), attr, item))
+pub fn read_all(attr : TokenStream, item : TokenStream) -> TokenStream
+{
+ expand_macro(attr, item, |attr, item| expand_method(Method::ReadAll, attr, item))
}
#[proc_macro_attribute]
-pub fn read_all(attr: TokenStream, item: TokenStream) -> TokenStream {
- expand_macro(attr, item, |attr, item| expand_endpoint(EndpointType::ReadAll, attr, item))
+pub fn read(attr : TokenStream, item : TokenStream) -> TokenStream
+{
+ expand_macro(attr, item, |attr, item| expand_method(Method::Read, attr, item))
}
#[proc_macro_attribute]
-pub fn read(attr: TokenStream, item: TokenStream) -> TokenStream {
- expand_macro(attr, item, |attr, item| expand_endpoint(EndpointType::Read, attr, item))
+pub fn search(attr : TokenStream, item : TokenStream) -> TokenStream
+{
+ expand_macro(attr, item, |attr, item| expand_method(Method::Search, attr, item))
}
#[proc_macro_attribute]
-pub fn search(attr: TokenStream, item: TokenStream) -> TokenStream {
- expand_macro(attr, item, |attr, item| expand_endpoint(EndpointType::Search, attr, item))
+pub fn create(attr : TokenStream, item : TokenStream) -> TokenStream
+{
+ expand_macro(attr, item, |attr, item| expand_method(Method::Create, attr, item))
}
#[proc_macro_attribute]
-pub fn create(attr: TokenStream, item: TokenStream) -> TokenStream {
- expand_macro(attr, item, |attr, item| expand_endpoint(EndpointType::Create, attr, item))
+pub fn change_all(attr : TokenStream, item : TokenStream) -> TokenStream
+{
+ expand_macro(attr, item, |attr, item| expand_method(Method::ChangeAll, attr, item))
}
#[proc_macro_attribute]
-pub fn change_all(attr: TokenStream, item: TokenStream) -> TokenStream {
- expand_macro(attr, item, |attr, item| expand_endpoint(EndpointType::UpdateAll, attr, item))
+pub fn change(attr : TokenStream, item : TokenStream) -> TokenStream
+{
+ expand_macro(attr, item, |attr, item| expand_method(Method::Change, attr, item))
}
#[proc_macro_attribute]
-pub fn change(attr: TokenStream, item: TokenStream) -> TokenStream {
- expand_macro(attr, item, |attr, item| expand_endpoint(EndpointType::Update, attr, item))
+pub fn remove_all(attr : TokenStream, item : TokenStream) -> TokenStream
+{
+ expand_macro(attr, item, |attr, item| expand_method(Method::RemoveAll, attr, item))
}
#[proc_macro_attribute]
-pub fn remove_all(attr: TokenStream, item: TokenStream) -> TokenStream {
- expand_macro(attr, item, |attr, item| expand_endpoint(EndpointType::DeleteAll, attr, item))
-}
-
-#[proc_macro_attribute]
-pub fn remove(attr: TokenStream, item: TokenStream) -> TokenStream {
- expand_macro(attr, item, |attr, item| expand_endpoint(EndpointType::Delete, attr, item))
-}
-
-/// PRIVATE MACRO - DO NOT USE
-#[doc(hidden)]
-#[proc_macro_attribute]
-pub fn _private_openapi_trait(attr: TokenStream, item: TokenStream) -> TokenStream {
- expand_macro(attr, item, expand_private_openapi_trait)
+pub fn remove(attr : TokenStream, item : TokenStream) -> TokenStream
+{
+ expand_macro(attr, item, |attr, item| expand_method(Method::Remove, attr, item))
}
diff --git a/derive/src/method.rs b/derive/src/method.rs
new file mode 100644
index 0000000..be19e0b
--- /dev/null
+++ b/derive/src/method.rs
@@ -0,0 +1,480 @@
+use crate::util::CollectToResult;
+use heck::{CamelCase, SnakeCase};
+use proc_macro2::{Ident, Span, TokenStream};
+use quote::{format_ident, quote};
+use syn::{
+ spanned::Spanned,
+ Attribute,
+ AttributeArgs,
+ Error,
+ FnArg,
+ ItemFn,
+ Lit,
+ LitBool,
+ Meta,
+ NestedMeta,
+ PatType,
+ Result,
+ ReturnType,
+ Type
+};
+use std::str::FromStr;
+
+pub enum Method
+{
+ ReadAll,
+ Read,
+ Search,
+ Create,
+ ChangeAll,
+ Change,
+ RemoveAll,
+ Remove
+}
+
+impl FromStr for Method
+{
+ type Err = Error;
+
+ fn from_str(str : &str) -> Result
+ {
+ match str {
+ "ReadAll" | "read_all" => Ok(Self::ReadAll),
+ "Read" | "read" => Ok(Self::Read),
+ "Search" | "search" => Ok(Self::Search),
+ "Create" | "create" => Ok(Self::Create),
+ "ChangeAll" | "change_all" => Ok(Self::ChangeAll),
+ "Change" | "change" => Ok(Self::Change),
+ "RemoveAll" | "remove_all" => Ok(Self::RemoveAll),
+ "Remove" | "remove" => Ok(Self::Remove),
+ _ => Err(Error::new(Span::call_site(), format!("Unknown method: `{}'", str)))
+ }
+ }
+}
+
+impl Method
+{
+ pub fn type_names(&self) -> Vec<&'static str>
+ {
+ use Method::*;
+
+ match self {
+ ReadAll | RemoveAll => vec![],
+ Read | Remove => vec!["ID"],
+ Search => vec!["Query"],
+ Create | ChangeAll => vec!["Body"],
+ Change => vec!["ID", "Body"]
+ }
+ }
+
+ pub fn trait_ident(&self) -> Ident
+ {
+ use Method::*;
+
+ let name = match self {
+ ReadAll => "ReadAll",
+ Read => "Read",
+ Search => "Search",
+ Create => "Create",
+ ChangeAll => "ChangeAll",
+ Change => "Change",
+ RemoveAll => "RemoveAll",
+ Remove => "Remove"
+ };
+ format_ident!("Resource{}", name)
+ }
+
+ pub fn fn_ident(&self) -> Ident
+ {
+ use Method::*;
+
+ let name = match self {
+ ReadAll => "read_all",
+ Read => "read",
+ Search => "search",
+ Create => "create",
+ ChangeAll => "change_all",
+ Change => "change",
+ RemoveAll => "remove_all",
+ Remove => "remove"
+ };
+ format_ident!("{}", name)
+ }
+
+ pub fn mod_ident(&self, resource : &str) -> Ident
+ {
+ format_ident!("_gotham_restful_resource_{}_method_{}", resource.to_snake_case(), self.fn_ident())
+ }
+
+ pub fn handler_struct_ident(&self, resource : &str) -> Ident
+ {
+ format_ident!("{}{}Handler", resource.to_camel_case(), self.trait_ident())
+ }
+
+ pub fn setup_ident(&self, resource : &str) -> Ident
+ {
+ format_ident!("{}_{}_setup_impl", resource.to_snake_case(), self.fn_ident())
+ }
+}
+
+#[allow(clippy::large_enum_variant)]
+enum MethodArgumentType
+{
+ StateRef,
+ StateMutRef,
+ MethodArg(Type),
+ DatabaseConnection(Type),
+ AuthStatus(Type),
+ AuthStatusRef(Type)
+}
+
+impl MethodArgumentType
+{
+ fn is_state_ref(&self) -> bool
+ {
+ matches!(self, Self::StateRef | Self::StateMutRef)
+ }
+
+ fn is_method_arg(&self) -> bool
+ {
+ matches!(self, Self::MethodArg(_))
+ }
+
+ fn is_database_conn(&self) -> bool
+ {
+ matches!(self, Self::DatabaseConnection(_))
+ }
+
+ fn is_auth_status(&self) -> bool
+ {
+ matches!(self, Self::AuthStatus(_) | Self::AuthStatusRef(_))
+ }
+
+ fn ty(&self) -> Option<&Type>
+ {
+ match self {
+ Self::MethodArg(ty) | Self::DatabaseConnection(ty) | Self::AuthStatus(ty) | Self::AuthStatusRef(ty) => Some(ty),
+ _ => None
+ }
+ }
+
+ fn quote_ty(&self) -> Option
+ {
+ self.ty().map(|ty| quote!(#ty))
+ }
+}
+
+struct MethodArgument
+{
+ ident : Ident,
+ ident_span : Span,
+ ty : MethodArgumentType
+}
+
+impl Spanned for MethodArgument
+{
+ fn span(&self) -> Span
+ {
+ self.ident_span
+ }
+}
+
+fn interpret_arg_ty(attrs : &[Attribute], name : &str, ty : Type) -> Result
+{
+ let attr = attrs.iter()
+ .find(|arg| arg.path.segments.iter().any(|path| &path.ident.to_string() == "rest_arg"))
+ .map(|arg| arg.tokens.to_string());
+
+ // TODO issue a warning for _state usage once diagnostics become stable
+ if attr.as_deref() == Some("state") || (attr.is_none() && (name == "state" || name == "_state"))
+ {
+ return match ty {
+ Type::Reference(ty) => Ok(if ty.mutability.is_none() { MethodArgumentType::StateRef } else { MethodArgumentType::StateMutRef }),
+ _ => Err(Error::new(ty.span(), "The state parameter has to be a (mutable) reference to gotham_restful::State"))
+ };
+ }
+
+ if cfg!(feature = "auth") && (attr.as_deref() == Some("auth") || (attr.is_none() && name == "auth"))
+ {
+ return Ok(match ty {
+ Type::Reference(ty) => MethodArgumentType::AuthStatusRef(*ty.elem),
+ ty => MethodArgumentType::AuthStatus(ty)
+ });
+ }
+
+ if cfg!(feature = "database") && (attr.as_deref() == Some("connection") || attr.as_deref() == Some("conn") || (attr.is_none() && name == "conn"))
+ {
+ return Ok(MethodArgumentType::DatabaseConnection(match ty {
+ Type::Reference(ty) => *ty.elem,
+ ty => ty
+ }));
+ }
+
+ Ok(MethodArgumentType::MethodArg(ty))
+}
+
+fn interpret_arg(index : usize, arg : &PatType) -> Result
+{
+ let pat = &arg.pat;
+ let ident = format_ident!("arg{}", index);
+ let orig_name = quote!(#pat);
+ let ty = interpret_arg_ty(&arg.attrs, &orig_name.to_string(), *arg.ty.clone())?;
+
+ Ok(MethodArgument { ident, ident_span: arg.pat.span(), ty })
+}
+
+#[cfg(feature = "openapi")]
+fn expand_operation_id(attrs : &[NestedMeta]) -> TokenStream
+{
+ let mut operation_id : Option<&Lit> = None;
+ for meta in attrs
+ {
+ if let NestedMeta::Meta(Meta::NameValue(kv)) = meta
+ {
+ if kv.path.segments.last().map(|p| p.ident.to_string()) == Some("operation_id".to_owned())
+ {
+ operation_id = Some(&kv.lit)
+ }
+ }
+ }
+
+ match operation_id {
+ Some(operation_id) => quote! {
+ fn operation_id() -> Option
+ {
+ Some(#operation_id.to_string())
+ }
+ },
+ None => quote!()
+ }
+}
+
+#[cfg(not(feature = "openapi"))]
+fn expand_operation_id(_ : &[NestedMeta]) -> TokenStream
+{
+ quote!()
+}
+
+fn expand_wants_auth(attrs : &[NestedMeta], default : bool) -> TokenStream
+{
+ let default_lit = Lit::Bool(LitBool { value: default, span: Span::call_site() });
+ let mut wants_auth = &default_lit;
+ for meta in attrs
+ {
+ if let NestedMeta::Meta(Meta::NameValue(kv)) = meta
+ {
+ if kv.path.segments.last().map(|p| p.ident.to_string()) == Some("wants_auth".to_owned())
+ {
+ wants_auth = &kv.lit
+ }
+ }
+ }
+
+ quote! {
+ fn wants_auth() -> bool
+ {
+ #wants_auth
+ }
+ }
+}
+
+#[allow(clippy::comparison_chain)]
+pub fn expand_method(method : Method, mut attrs : AttributeArgs, fun : ItemFn) -> Result
+{
+ let krate = super::krate();
+
+ // parse attributes
+ if attrs.len() < 1
+ {
+ return Err(Error::new(Span::call_site(), "Missing Resource struct. Example: #[read_all(MyResource)]"));
+ }
+ let resource_path = match attrs.remove(0) {
+ NestedMeta::Meta(Meta::Path(path)) => path,
+ p => return Err(Error::new(p.span(), "Expected name of the Resource struct this method belongs to"))
+ };
+ let resource_name = resource_path.segments.last().map(|s| s.ident.to_string())
+ .ok_or_else(|| Error::new(resource_path.span(), "Resource name must not be empty"))?;
+
+ let fun_ident = &fun.sig.ident;
+ let fun_vis = &fun.vis;
+ let fun_is_async = fun.sig.asyncness.is_some();
+
+ if let Some(unsafety) = fun.sig.unsafety
+ {
+ return Err(Error::new(unsafety.span(), "Resource methods must not be unsafe"));
+ }
+
+ let trait_ident = method.trait_ident();
+ let method_ident = method.fn_ident();
+ let mod_ident = method.mod_ident(&resource_name);
+ let handler_ident = method.handler_struct_ident(&resource_name);
+ let setup_ident = method.setup_ident(&resource_name);
+
+ let (ret, is_no_content) = match &fun.sig.output {
+ ReturnType::Default => (quote!(#krate::NoContent), true),
+ ReturnType::Type(_, ty) => (quote!(#ty), false)
+ };
+
+ // some default idents we'll need
+ let state_ident = format_ident!("state");
+ let repo_ident = format_ident!("repo");
+ let conn_ident = format_ident!("conn");
+ let auth_ident = format_ident!("auth");
+ let res_ident = format_ident!("res");
+
+ // extract arguments into pattern, ident and type
+ let args = fun.sig.inputs.iter()
+ .enumerate()
+ .map(|(i, arg)| match arg {
+ FnArg::Typed(arg) => interpret_arg(i, arg),
+ FnArg::Receiver(_) => Err(Error::new(arg.span(), "Didn't expect self parameter"))
+ })
+ .collect_to_result()?;
+
+ // extract the generic parameters to use
+ let ty_names = method.type_names();
+ let ty_len = ty_names.len();
+ let generics_args : Vec<&MethodArgument> = args.iter()
+ .filter(|arg| (*arg).ty.is_method_arg())
+ .collect();
+ if generics_args.len() > ty_len
+ {
+ return Err(Error::new(generics_args[ty_len].span(), "Too many arguments"));
+ }
+ else if generics_args.len() < ty_len
+ {
+ return Err(Error::new(fun_ident.span(), "Too few arguments"));
+ }
+ let generics : Vec = generics_args.iter()
+ .map(|arg| arg.ty.quote_ty().unwrap())
+ .zip(ty_names)
+ .map(|(arg, name)| {
+ let ident = format_ident!("{}", name);
+ quote!(type #ident = #arg;)
+ })
+ .collect();
+
+ // extract the definition of our method
+ let mut args_def : Vec = args.iter()
+ .filter(|arg| (*arg).ty.is_method_arg())
+ .map(|arg| {
+ let ident = &arg.ident;
+ let ty = arg.ty.quote_ty();
+ quote!(#ident : #ty)
+ }).collect();
+ args_def.insert(0, quote!(mut #state_ident : #krate::State));
+
+ // extract the arguments to pass over to the supplied method
+ let args_pass : Vec = args.iter().map(|arg| match (&arg.ty, &arg.ident) {
+ (MethodArgumentType::StateRef, _) => quote!(state_ident),
+ (MethodArgumentType::StateMutRef, _) => quote!(&mut #state_ident),
+ (MethodArgumentType::MethodArg(_), ident) => quote!(#ident),
+ (MethodArgumentType::DatabaseConnection(_), _) => quote!(conn_ident),
+ (MethodArgumentType::AuthStatus(_), _) => quote!(#auth_ident),
+ (MethodArgumentType::AuthStatusRef(_), _) => quote!(auth_ident)
+ }).collect();
+
+ // prepare the method block
+ let mut block = quote!(#fun_ident(#(#args_pass),*));
+ let mut state_block = quote!();
+ if fun_is_async
+ {
+ if let Some(arg) = args.iter().find(|arg| (*arg).ty.is_state_ref())
+ {
+ return Err(Error::new(arg.span(), "async fn must not take &State as an argument as State is not Sync, consider boxing"));
+ }
+ block = quote!(#block.await);
+ }
+ if is_no_content
+ {
+ block = quote!(#block; Default::default())
+ }
+ if let Some(arg) = args.iter().find(|arg| (*arg).ty.is_database_conn())
+ {
+ if fun_is_async
+ {
+ return Err(Error::new(arg.span(), "async fn is not supported when database support is required, consider boxing"));
+ }
+ let conn_ty = arg.ty.quote_ty();
+ state_block = quote! {
+ #state_block
+ let #repo_ident = <#krate::export::Repo<#conn_ty>>::borrow_from(state_ident).clone();
+ };
+ block = quote! {
+ {
+ let #res_ident = #repo_ident.run::<_, (#krate::State, #ret), ()>(move |#conn_ident| {
+ let #res_ident = { #block };
+ Ok((#state_ident, #res_ident))
+ }).await.unwrap();
+ #state_ident = #res_ident.0;
+ #res_ident.1
+ }
+ };
+ }
+ if let Some(arg) = args.iter().find(|arg| (*arg).ty.is_auth_status())
+ {
+ let auth_ty = arg.ty.quote_ty();
+ state_block = quote! {
+ #state_block
+ let #auth_ident : #auth_ty = <#auth_ty>::borrow_from(state_ident).clone();
+ };
+ }
+
+ // prepare the where clause
+ let mut where_clause = quote!(#resource_path : #krate::Resource,);
+ for arg in args.iter().filter(|arg| (*arg).ty.is_auth_status())
+ {
+ let auth_ty = arg.ty.quote_ty();
+ where_clause = quote!(#where_clause #auth_ty : Clone,);
+ }
+
+ // attribute generated code
+ let operation_id = expand_operation_id(&attrs);
+ let wants_auth = expand_wants_auth(&attrs, args.iter().any(|arg| (*arg).ty.is_auth_status()));
+
+ // put everything together
+ Ok(quote! {
+ #fun
+
+ #fun_vis mod #mod_ident
+ {
+ use super::*;
+
+ struct #handler_ident;
+
+ impl #krate::ResourceMethod for #handler_ident
+ {
+ type Res = #ret;
+
+ #operation_id
+ #wants_auth
+ }
+
+ impl #krate::#trait_ident for #handler_ident
+ where #where_clause
+ {
+ #(#generics)*
+
+ fn #method_ident(#(#args_def),*) -> std::pin::Pin + Send>>
+ {
+ #[allow(unused_imports)]
+ use #krate::{export::FutureExt, FromState};
+
+ #state_block
+
+ async move {
+ let #res_ident = { #block };
+ (#state_ident, #res_ident)
+ }.boxed()
+ }
+ }
+
+ #[deny(dead_code)]
+ pub fn #setup_ident(route : &mut D)
+ {
+ route.#method_ident::<#handler_ident>();
+ }
+
+ }
+ })
+}
diff --git a/derive/src/openapi_type.rs b/derive/src/openapi_type.rs
new file mode 100644
index 0000000..57a7ceb
--- /dev/null
+++ b/derive/src/openapi_type.rs
@@ -0,0 +1,282 @@
+use crate::util::{CollectToResult, remove_parens};
+use proc_macro2::{Ident, TokenStream};
+use quote::quote;
+use syn::{
+ parse_macro_input,
+ spanned::Spanned,
+ Attribute,
+ AttributeArgs,
+ Data,
+ DataEnum,
+ DataStruct,
+ DeriveInput,
+ Error,
+ Field,
+ Fields,
+ Generics,
+ GenericParam,
+ Lit,
+ LitStr,
+ Meta,
+ NestedMeta,
+ Result,
+ Variant
+};
+
+pub fn expand_openapi_type(input : DeriveInput) -> Result
+{
+ match (input.ident, input.generics, input.attrs, input.data) {
+ (ident, generics, attrs, Data::Enum(inum)) => expand_enum(ident, generics, attrs, inum),
+ (ident, generics, attrs, Data::Struct(strukt)) => expand_struct(ident, generics, attrs, strukt),
+ (_, _, _, Data::Union(uni)) => Err(Error::new(uni.union_token.span(), "#[derive(OpenapiType)] only works for structs and enums"))
+ }
+}
+
+fn expand_where(generics : &Generics) -> TokenStream
+{
+ if generics.params.is_empty()
+ {
+ return quote!();
+ }
+
+ let krate = super::krate();
+ let idents = generics.params.iter()
+ .map(|param| match param {
+ GenericParam::Type(ty) => Some(ty.ident.clone()),
+ _ => None
+ })
+ .filter(|param| param.is_some())
+ .map(|param| param.unwrap());
+
+ quote! {
+ where #(#idents : #krate::OpenapiType),*
+ }
+}
+
+#[derive(Debug, Default)]
+struct Attrs
+{
+ nullable : bool,
+ rename : Option
+}
+
+fn to_string(lit : &Lit) -> Result
+{
+ match lit {
+ Lit::Str(str) => Ok(str.value()),
+ _ => Err(Error::new(lit.span(), "Expected string literal"))
+ }
+}
+
+fn to_bool(lit : &Lit) -> Result
+{
+ match lit {
+ Lit::Bool(bool) => Ok(bool.value),
+ _ => Err(Error::new(lit.span(), "Expected bool"))
+ }
+}
+
+fn parse_attributes(input : &[Attribute]) -> Result
+{
+ let mut parsed = Attrs::default();
+ for attr in input
+ {
+ if attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("openapi".to_owned())
+ {
+ let tokens = remove_parens(attr.tokens.clone());
+ // TODO this is not public api but syn currently doesn't offer another convenient way to parse AttributeArgs
+ let nested = parse_macro_input::parse::(tokens.into())?;
+ for meta in nested
+ {
+ match &meta {
+ NestedMeta::Meta(Meta::NameValue(kv)) => match kv.path.segments.last().map(|s| s.ident.to_string()) {
+ Some(key) => match key.as_ref() {
+ "nullable" => parsed.nullable = to_bool(&kv.lit)?,
+ "rename" => parsed.rename = Some(to_string(&kv.lit)?),
+ _ => return Err(Error::new(kv.path.span(), "Unknown key")),
+ },
+ _ => return Err(Error::new(meta.span(), "Unexpected token"))
+ },
+ _ => return Err(Error::new(meta.span(), "Unexpected token"))
+ }
+ }
+ }
+ }
+ Ok(parsed)
+}
+
+fn expand_variant(variant : &Variant) -> Result
+{
+ if variant.fields != Fields::Unit
+ {
+ return Err(Error::new(variant.span(), "#[derive(OpenapiType)] does not support enum variants with fields"));
+ }
+
+ let ident = &variant.ident;
+
+ let attrs = parse_attributes(&variant.attrs)?;
+ let name = match attrs.rename {
+ Some(rename) => rename,
+ None => ident.to_string()
+ };
+
+ Ok(quote! {
+ enumeration.push(#name.to_string());
+ })
+}
+
+fn expand_enum(ident : Ident, generics : Generics, attrs : Vec, input : DataEnum) -> Result
+{
+ let krate = super::krate();
+ let where_clause = expand_where(&generics);
+
+ let attrs = parse_attributes(&attrs)?;
+ let nullable = attrs.nullable;
+ let name = match attrs.rename {
+ Some(rename) => rename,
+ None => ident.to_string()
+ };
+
+ let variants = input.variants.iter()
+ .map(expand_variant)
+ .collect_to_result()?;
+
+ Ok(quote! {
+ impl #generics #krate::OpenapiType for #ident #generics
+ #where_clause
+ {
+ fn schema() -> #krate::OpenapiSchema
+ {
+ use #krate::{export::openapi::*, OpenapiSchema};
+
+ let mut enumeration : Vec = Vec::new();
+
+ #(#variants)*
+
+ let schema = SchemaKind::Type(Type::String(StringType {
+ format: VariantOrUnknownOrEmpty::Empty,
+ enumeration,
+ ..Default::default()
+ }));
+
+ OpenapiSchema {
+ name: Some(#name.to_string()),
+ nullable: #nullable,
+ schema,
+ dependencies: Default::default()
+ }
+ }
+ }
+ })
+}
+
+fn expand_field(field : &Field) -> Result
+{
+ let ident = match &field.ident {
+ Some(ident) => ident,
+ None => return Err(Error::new(field.span(), "#[derive(OpenapiType)] does not support fields without an ident"))
+ };
+ let ident_str = LitStr::new(&ident.to_string(), ident.span());
+ let ty = &field.ty;
+
+ let attrs = parse_attributes(&field.attrs)?;
+ let nullable = attrs.nullable;
+ let name = match attrs.rename {
+ Some(rename) => rename,
+ None => ident.to_string()
+ };
+
+ Ok(quote! {{
+ let mut schema = <#ty>::schema();
+
+ if schema.nullable
+ {
+ schema.nullable = false;
+ }
+ else if !#nullable
+ {
+ required.push(#ident_str.to_string());
+ }
+
+ let keys : Vec = schema.dependencies.keys().map(|k| k.to_string()).collect();
+ for dep in keys
+ {
+ let dep_schema = schema.dependencies.swap_remove(&dep);
+ if let Some(dep_schema) = dep_schema
+ {
+ dependencies.insert(dep, dep_schema);
+ }
+ }
+
+ match schema.name.clone() {
+ Some(schema_name) => {
+ properties.insert(
+ #name.to_string(),
+ ReferenceOr::Reference { reference: format!("#/components/schemas/{}", schema_name) }
+ );
+ dependencies.insert(schema_name, schema);
+ },
+ None => {
+ properties.insert(
+ #name.to_string(),
+ ReferenceOr::Item(Box::new(schema.into_schema()))
+ );
+ }
+ }
+ }})
+}
+
+fn expand_struct(ident : Ident, generics : Generics, attrs : Vec, input : DataStruct) -> Result
+{
+ let krate = super::krate();
+ let where_clause = expand_where(&generics);
+
+ let attrs = parse_attributes(&attrs)?;
+ let nullable = attrs.nullable;
+ let name = match attrs.rename {
+ Some(rename) => rename,
+ None => ident.to_string()
+ };
+
+ let fields : Vec = match input.fields {
+ Fields::Named(named_fields) => {
+ named_fields.named.iter()
+ .map(expand_field)
+ .collect_to_result()?
+ },
+ Fields::Unnamed(fields) => return Err(Error::new(fields.span(), "#[derive(OpenapiType)] does not support unnamed fields")),
+ Fields::Unit => Vec::new()
+ };
+
+ Ok(quote!{
+ impl #generics #krate::OpenapiType for #ident #generics
+ #where_clause
+ {
+ fn schema() -> #krate::OpenapiSchema
+ {
+ use #krate::{export::{openapi::*, IndexMap}, OpenapiSchema};
+
+ let mut properties : IndexMap>> = IndexMap::new();
+ let mut required : Vec = Vec::new();
+ let mut dependencies : IndexMap = IndexMap::new();
+
+ #(#fields)*
+
+ let schema = SchemaKind::Type(Type::Object(ObjectType {
+ properties,
+ required,
+ additional_properties: None,
+ min_properties: None,
+ max_properties: None
+ }));
+
+ OpenapiSchema {
+ name: Some(#name.to_string()),
+ nullable: #nullable,
+ schema,
+ dependencies
+ }
+ }
+ }
+ })
+}
diff --git a/derive/src/private_openapi_trait.rs b/derive/src/private_openapi_trait.rs
deleted file mode 100644
index d590c7e..0000000
--- a/derive/src/private_openapi_trait.rs
+++ /dev/null
@@ -1,171 +0,0 @@
-use crate::util::{remove_parens, CollectToResult, PathEndsWith};
-use proc_macro2::{Span, TokenStream};
-use quote::{quote, ToTokens};
-use syn::{
- parse::Parse, spanned::Spanned, Attribute, AttributeArgs, Error, ItemTrait, LitStr, Meta, NestedMeta, PredicateType,
- Result, TraitItem, WherePredicate
-};
-
-struct TraitItemAttrs {
- openapi_only: bool,
- openapi_bound: Vec,
- non_openapi_bound: Vec,
- other_attrs: Vec
-}
-
-impl TraitItemAttrs {
- fn parse(attrs: Vec) -> Result {
- let mut openapi_only = false;
- let mut openapi_bound = Vec::new();
- let mut non_openapi_bound = Vec::new();
- let mut other = Vec::new();
-
- for attr in attrs {
- if attr.path.ends_with("openapi_only") {
- openapi_only = true;
- } else if attr.path.ends_with("openapi_bound") {
- let attr_arg: LitStr = syn::parse2(remove_parens(attr.tokens))?;
- let predicate = attr_arg.parse_with(WherePredicate::parse)?;
- openapi_bound.push(match predicate {
- WherePredicate::Type(ty) => ty,
- _ => return Err(Error::new(predicate.span(), "Expected type bound"))
- });
- } else if attr.path.ends_with("non_openapi_bound") {
- let attr_arg: LitStr = syn::parse2(remove_parens(attr.tokens))?;
- let predicate = attr_arg.parse_with(WherePredicate::parse)?;
- non_openapi_bound.push(match predicate {
- WherePredicate::Type(ty) => ty,
- _ => return Err(Error::new(predicate.span(), "Expected type bound"))
- });
- } else {
- other.push(attr);
- }
- }
-
- Ok(Self {
- openapi_only,
- openapi_bound,
- non_openapi_bound,
- other_attrs: other
- })
- }
-}
-
-pub(crate) fn expand_private_openapi_trait(mut attrs: AttributeArgs, tr8: ItemTrait) -> Result {
- let tr8_attrs = &tr8.attrs;
- let vis = &tr8.vis;
- let ident = &tr8.ident;
- let generics = &tr8.generics;
- let colon_token = &tr8.colon_token;
- let supertraits = &tr8.supertraits;
-
- if attrs.len() != 1 {
- return Err(Error::new(
- Span::call_site(),
- "Expected one argument. Example: #[_private_openapi_trait(OpenapiTraitName)]"
- ));
- }
- let openapi_ident = match attrs.remove(0) {
- NestedMeta::Meta(Meta::Path(path)) => path,
- p => {
- return Err(Error::new(
- p.span(),
- "Expected name of the Resource struct this method belongs to"
- ))
- },
- };
-
- let orig_trait = {
- let items = tr8
- .items
- .clone()
- .into_iter()
- .map(|item| {
- Ok(match item {
- TraitItem::Method(mut method) => {
- let attrs = TraitItemAttrs::parse(method.attrs)?;
- method.attrs = attrs.other_attrs;
- for bound in attrs.non_openapi_bound {
- // we compare two incompatible types using their `Display` implementation
- // this triggers a false positive in clippy
- #[cfg_attr(feature = "cargo-clippy", allow(clippy::cmp_owned))]
- method
- .sig
- .generics
- .type_params_mut()
- .filter(|param| param.ident.to_string() == bound.bounded_ty.to_token_stream().to_string())
- .for_each(|param| param.bounds.extend(bound.bounds.clone()));
- }
- if attrs.openapi_only {
- None
- } else {
- Some(TraitItem::Method(method))
- }
- },
- TraitItem::Type(mut ty) => {
- let attrs = TraitItemAttrs::parse(ty.attrs)?;
- ty.attrs = attrs.other_attrs;
- Some(TraitItem::Type(ty))
- },
- item => Some(item)
- })
- })
- .collect_to_result()?;
- quote! {
- #(#tr8_attrs)*
- #vis trait #ident #generics #colon_token #supertraits {
- #(#items)*
- }
- }
- };
-
- let openapi_trait = if !cfg!(feature = "openapi") {
- None
- } else {
- let items = tr8
- .items
- .clone()
- .into_iter()
- .map(|item| {
- Ok(match item {
- TraitItem::Method(mut method) => {
- let attrs = TraitItemAttrs::parse(method.attrs)?;
- method.attrs = attrs.other_attrs;
- for bound in attrs.openapi_bound {
- // we compare two incompatible types using their `Display` implementation
- // this triggers a false positive in clippy
- #[cfg_attr(feature = "cargo-clippy", allow(clippy::cmp_owned))]
- method
- .sig
- .generics
- .type_params_mut()
- .filter(|param| param.ident.to_string() == bound.bounded_ty.to_token_stream().to_string())
- .for_each(|param| param.bounds.extend(bound.bounds.clone()));
- }
- TraitItem::Method(method)
- },
- TraitItem::Type(mut ty) => {
- let attrs = TraitItemAttrs::parse(ty.attrs)?;
- ty.attrs = attrs.other_attrs;
- for bound in attrs.openapi_bound {
- ty.bounds.extend(bound.bounds.clone());
- }
- TraitItem::Type(ty)
- },
- item => item
- })
- })
- .collect_to_result()?;
- Some(quote! {
- #(#tr8_attrs)*
- #vis trait #openapi_ident #generics #colon_token #supertraits {
- #(#items)*
- }
- })
- };
-
- Ok(quote! {
- #orig_trait
- #openapi_trait
- })
-}
diff --git a/derive/src/request_body.rs b/derive/src/request_body.rs
index c543dfa..76c80aa 100644
--- a/derive/src/request_body.rs
+++ b/derive/src/request_body.rs
@@ -6,82 +6,78 @@ use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
spanned::Spanned,
- DeriveInput, Error, Generics, Path, Result, Token
+ DeriveInput,
+ Error,
+ Generics,
+ Path,
+ Result,
+ Token
};
struct MimeList(Punctuated);
-impl Parse for MimeList {
- fn parse(input: ParseStream<'_>) -> Result {
+impl Parse for MimeList
+{
+ fn parse(input: ParseStream) -> Result
+ {
let list = Punctuated::parse_separated_nonempty(&input)?;
Ok(Self(list))
}
}
#[cfg(not(feature = "openapi"))]
-fn impl_openapi_type(_ident: &Ident, _generics: &Generics) -> TokenStream {
+fn impl_openapi_type(_ident : &Ident, _generics : &Generics) -> TokenStream
+{
quote!()
}
#[cfg(feature = "openapi")]
-fn impl_openapi_type(ident: &Ident, generics: &Generics) -> TokenStream {
+fn impl_openapi_type(ident : &Ident, generics : &Generics) -> TokenStream
+{
let krate = super::krate();
- let openapi = quote!(#krate::private::openapi);
-
+
quote! {
- impl #generics #krate::private::OpenapiType for #ident #generics
+ impl #generics #krate::OpenapiType for #ident #generics
{
- fn schema() -> #krate::private::OpenapiSchema
+ fn schema() -> #krate::OpenapiSchema
{
- #krate::private::OpenapiSchema::new(
- #openapi::SchemaKind::Type(
- #openapi::Type::String(
- #openapi::StringType {
- format: #openapi::VariantOrUnknownOrEmpty::Item(
- #openapi::StringFormat::Binary
- ),
- .. ::std::default::Default::default()
- }
- )
- )
- )
+ use #krate::{export::openapi::*, OpenapiSchema};
+
+ OpenapiSchema::new(SchemaKind::Type(Type::String(StringType {
+ format: VariantOrUnknownOrEmpty::Item(StringFormat::Binary),
+ ..Default::default()
+ })))
}
}
}
}
-pub fn expand_request_body(input: DeriveInput) -> Result {
+pub fn expand_request_body(input : DeriveInput) -> Result
+{
let krate = super::krate();
let ident = input.ident;
let generics = input.generics;
-
- let types = input
- .attrs
- .into_iter()
- .filter(|attr| {
- attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("supported_types".to_string())
- })
+
+ let types = input.attrs.into_iter()
+ .filter(|attr| attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("supported_types".to_string()))
.flat_map(|attr| {
let span = attr.span();
attr.parse_args::()
.map(|list| Box::new(list.0.into_iter().map(Ok)) as Box>>)
.unwrap_or_else(|mut err| {
- err.combine(Error::new(
- span,
- "Hint: Types list should look like #[supported_types(TEXT_PLAIN, APPLICATION_JSON)]"
- ));
+ err.combine(Error::new(span, "Hint: Types list should look like #[supported_types(TEXT_PLAIN, APPLICATION_JSON)]"));
Box::new(iter::once(Err(err)))
})
})
.collect_to_result()?;
-
+
let types = match types {
ref types if types.is_empty() => quote!(None),
types => quote!(Some(vec![#(#types),*]))
};
-
+
let impl_openapi_type = impl_openapi_type(&ident, &generics);
-
+
Ok(quote! {
impl #generics #krate::RequestBody for #ident #generics
where #ident #generics : #krate::FromBody
@@ -91,7 +87,7 @@ pub fn expand_request_body(input: DeriveInput) -> Result {
#types
}
}
-
+
#impl_openapi_type
})
}
diff --git a/derive/src/resource.rs b/derive/src/resource.rs
index 8e30275..79482e6 100644
--- a/derive/src/resource.rs
+++ b/derive/src/resource.rs
@@ -1,21 +1,23 @@
-use crate::{
- endpoint::endpoint_ident,
- util::{CollectToResult, PathEndsWith}
-};
+use crate::{method::Method, util::CollectToResult};
use proc_macro2::{Ident, TokenStream};
use quote::quote;
-use std::iter;
use syn::{
parenthesized,
parse::{Parse, ParseStream},
punctuated::Punctuated,
- DeriveInput, Result, Token
+ DeriveInput,
+ Error,
+ Result,
+ Token
};
+use std::{iter, str::FromStr};
struct MethodList(Punctuated);
-impl Parse for MethodList {
- fn parse(input: ParseStream<'_>) -> Result {
+impl Parse for MethodList
+{
+ fn parse(input: ParseStream) -> Result
+ {
let content;
let _paren = parenthesized!(content in input);
let list = Punctuated::parse_separated_nonempty(&content)?;
@@ -23,25 +25,27 @@ impl Parse for MethodList {
}
}
-pub fn expand_resource(input: DeriveInput) -> Result {
+pub fn expand_resource(input : DeriveInput) -> Result
+{
let krate = super::krate();
let ident = input.ident;
-
- let methods = input
- .attrs
- .into_iter()
- .filter(|attr| attr.path.ends_with("resource"))
- .map(|attr| syn::parse2(attr.tokens).map(|m: MethodList| m.0.into_iter()))
- .flat_map(|list| match list {
- Ok(iter) => Box::new(iter.map(|method| {
- let ident = endpoint_ident(&method);
- Ok(quote!(route.endpoint::<#ident>();))
- })) as Box>>,
- Err(err) => Box::new(iter::once(Err(err)))
- })
- .collect_to_result()?;
-
- let non_openapi_impl = quote! {
+ let name = ident.to_string();
+
+ let methods = input.attrs.into_iter().filter(|attr|
+ attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("resource".to_string()) // TODO wtf
+ ).map(|attr| {
+ syn::parse2(attr.tokens).map(|m : MethodList| m.0.into_iter())
+ }).flat_map(|list| match list {
+ Ok(iter) => Box::new(iter.map(|method| {
+ let method = Method::from_str(&method.to_string()).map_err(|err| Error::new(method.span(), err))?;
+ let mod_ident = method.mod_ident(&name);
+ let ident = method.setup_ident(&name);
+ Ok(quote!(#mod_ident::#ident(&mut route);))
+ })) as Box>>,
+ Err(err) => Box::new(iter::once(Err(err)))
+ }).collect_to_result()?;
+
+ Ok(quote! {
impl #krate::Resource for #ident
{
fn setup(mut route : D)
@@ -49,22 +53,5 @@ pub fn expand_resource(input: DeriveInput) -> Result {
#(#methods)*
}
}
- };
- let openapi_impl = if !cfg!(feature = "openapi") {
- None
- } else {
- Some(quote! {
- impl #krate::ResourceWithSchema for #ident
- {
- fn setup(mut route : D)
- {
- #(#methods)*
- }
- }
- })
- };
- Ok(quote! {
- #non_openapi_impl
- #openapi_impl
})
}
diff --git a/derive/src/resource_error.rs b/derive/src/resource_error.rs
index 9239c80..032151b 100644
--- a/derive/src/resource_error.rs
+++ b/derive/src/resource_error.rs
@@ -1,54 +1,68 @@
-use crate::util::{remove_parens, CollectToResult};
+use crate::util::{CollectToResult, remove_parens};
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};
use std::iter;
use syn::{
- spanned::Spanned, Attribute, Data, DeriveInput, Error, Fields, GenericParam, LitStr, Path, PathSegment, Result, Type,
+ spanned::Spanned,
+ Attribute,
+ Data,
+ DeriveInput,
+ Error,
+ Fields,
+ GenericParam,
+ LitStr,
+ Path,
+ PathSegment,
+ Result,
+ Type,
Variant
};
-struct ErrorVariantField {
- attrs: Vec,
- ident: Ident,
- ty: Type
+
+struct ErrorVariantField
+{
+ attrs : Vec,
+ ident : Ident,
+ ty : Type
}
-struct ErrorVariant {
- ident: Ident,
- status: Option,
- is_named: bool,
- fields: Vec,
- from_ty: Option<(usize, Type)>,
- display: Option
+struct ErrorVariant
+{
+ ident : Ident,
+ status : Option,
+ is_named : bool,
+ fields : Vec,
+ from_ty : Option<(usize, Type)>,
+ display : Option
}
-fn process_variant(variant: Variant) -> Result {
- let status =
- match variant.attrs.iter().find(|attr| {
- attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("status".to_string())
- }) {
- Some(attr) => Some(syn::parse2(remove_parens(attr.tokens.clone()))?),
- None => None
- };
-
+fn process_variant(variant : Variant) -> Result
+{
+ let status = match variant.attrs.iter()
+ .find(|attr| attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("status".to_string()))
+ {
+ Some(attr) => Some(syn::parse2(remove_parens(attr.tokens.clone()))?),
+ None => None
+ };
+
let mut is_named = false;
let mut fields = Vec::new();
match variant.fields {
Fields::Named(named) => {
is_named = true;
- for field in named.named {
+ for field in named.named
+ {
let span = field.span();
fields.push(ErrorVariantField {
attrs: field.attrs,
- ident: field
- .ident
- .ok_or_else(|| Error::new(span, "Missing ident for this enum variant field"))?,
+ ident: field.ident.ok_or_else(|| Error::new(span, "Missing ident for this enum variant field"))?,
ty: field.ty
});
}
},
Fields::Unnamed(unnamed) => {
- for (i, field) in unnamed.unnamed.into_iter().enumerate() {
+ for (i, field) in unnamed.unnamed.into_iter().enumerate()
+ {
fields.push(ErrorVariantField {
attrs: field.attrs,
ident: format_ident!("arg{}", i),
@@ -58,25 +72,19 @@ fn process_variant(variant: Variant) -> Result {
},
Fields::Unit => {}
}
-
- let from_ty = fields
- .iter()
+
+ let from_ty = fields.iter()
.enumerate()
- .find(|(_, field)| {
- field
- .attrs
- .iter()
- .any(|attr| attr.path.segments.last().map(|segment| segment.ident.to_string()) == Some("from".to_string()))
- })
+ .find(|(_, field)| field.attrs.iter().any(|attr| attr.path.segments.last().map(|segment| segment.ident.to_string()) == Some("from".to_string())))
.map(|(i, field)| (i, field.ty.clone()));
-
- let display = match variant.attrs.iter().find(|attr| {
- attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("display".to_string())
- }) {
+
+ let display = match variant.attrs.iter()
+ .find(|attr| attr.path.segments.iter().last().map(|segment| segment.ident.to_string()) == Some("display".to_string()))
+ {
Some(attr) => Some(syn::parse2(remove_parens(attr.tokens.clone()))?),
None => None
};
-
+
Ok(ErrorVariant {
ident: variant.ident,
status,
@@ -87,15 +95,18 @@ fn process_variant(variant: Variant) -> Result {
})
}
-fn path_segment(name: &str) -> PathSegment {
+fn path_segment(name : &str) -> PathSegment
+{
PathSegment {
ident: format_ident!("{}", name),
arguments: Default::default()
}
}
-impl ErrorVariant {
- fn fields_pat(&self) -> TokenStream {
+impl ErrorVariant
+{
+ fn fields_pat(&self) -> TokenStream
+ {
let mut fields = self.fields.iter().map(|field| &field.ident).peekable();
if fields.peek().is_none() {
quote!()
@@ -105,90 +116,74 @@ impl ErrorVariant {
quote!( ( #( #fields ),* ) )
}
}
-
- fn to_display_match_arm(&self, formatter_ident: &Ident, enum_ident: &Ident) -> Result {
+
+ fn to_display_match_arm(&self, formatter_ident : &Ident, enum_ident : &Ident) -> Result
+ {
let ident = &self.ident;
- let display = self
- .display
- .as_ref()
- .ok_or_else(|| Error::new(self.ident.span(), "Missing display string for this variant"))?;
-
+ let display = self.display.as_ref().ok_or_else(|| Error::new(self.ident.span(), "Missing display string for this variant"))?;
+
// lets find all required format parameters
let display_str = display.value();
- let mut params: Vec<&str> = Vec::new();
+ let mut params : Vec<&str> = Vec::new();
let len = display_str.len();
let mut start = len;
let mut iter = display_str.chars().enumerate().peekable();
- while let Some((i, c)) = iter.next() {
+ while let Some((i, c)) = iter.next()
+ {
// we found a new opening brace
- if start == len && c == '{' {
+ if start == len && c == '{'
+ {
start = i + 1;
}
// we found a duplicate opening brace
- else if start == i && c == '{' {
+ else if start == i && c == '{'
+ {
start = len;
}
// we found a closing brace
- else if start < i && c == '}' {
+ else if start < i && c == '}'
+ {
match iter.peek() {
- Some((_, '}')) => {
- return Err(Error::new(
- display.span(),
- "Error parsing format string: curly braces not allowed inside parameter name"
- ))
- },
+ Some((_, '}')) => return Err(Error::new(display.span(), "Error parsing format string: curly braces not allowed inside parameter name")),
_ => params.push(&display_str[start..i])
};
start = len;
}
// we found a closing brace without content
- else if start == i && c == '}' {
- return Err(Error::new(
- display.span(),
- "Error parsing format string: parameter name must not be empty"
- ));
+ else if start == i && c == '}'
+ {
+ return Err(Error::new(display.span(), "Error parsing format string: parameter name must not be empty"))
}
}
- if start != len {
- return Err(Error::new(
- display.span(),
- "Error parsing format string: Unmatched opening brace"
- ));
+ if start != len
+ {
+ return Err(Error::new(display.span(), "Error parsing format string: Unmatched opening brace"));
}
- let params = params
- .into_iter()
- .map(|name| format_ident!("{}{}", if self.is_named { "" } else { "arg" }, name));
-
+ let params = params.into_iter().map(|name| format_ident!("{}{}", if self.is_named { "" } else { "arg" }, name));
+
let fields_pat = self.fields_pat();
Ok(quote! {
#enum_ident::#ident #fields_pat => write!(#formatter_ident, #display #(, #params = #params)*)
})
}
-
- fn into_match_arm(self, krate: &TokenStream, enum_ident: &Ident) -> Result {
+
+ fn into_match_arm(self, krate : &TokenStream, enum_ident : &Ident) -> Result
+ {
let ident = &self.ident;
let fields_pat = self.fields_pat();
let status = self.status.map(|status| {
// the status might be relative to StatusCode, so let's fix that
- if status.leading_colon.is_none() && status.segments.len() < 2 {
+ if status.leading_colon.is_none() && status.segments.len() < 2
+ {
let status_ident = status.segments.first().cloned().unwrap_or_else(|| path_segment("OK"));
Path {
leading_colon: Some(Default::default()),
- segments: vec![
- path_segment("gotham_restful"),
- path_segment("gotham"),
- path_segment("hyper"),
- path_segment("StatusCode"),
- status_ident,
- ]
- .into_iter()
- .collect()
+ segments: vec![path_segment("gotham_restful"), path_segment("gotham"), path_segment("hyper"), path_segment("StatusCode"), status_ident].into_iter().collect()
}
- } else {
- status
}
+ else { status }
});
-
+
// the response will come directly from the from_ty if present
let res = match (self.from_ty, status) {
(Some((from_index, _)), None) => {
@@ -196,20 +191,21 @@ impl ErrorVariant {
quote!(#from_field.into_response_error())
},
(Some(_), Some(_)) => return Err(Error::new(ident.span(), "When #[from] is used, #[status] must not be used!")),
- (None, Some(status)) => quote!(Ok(#krate::Response::new(
- { #status }.into(),
- #krate::gotham::hyper::Body::empty(),
- None
- ))),
+ (None, Some(status)) => quote!(Ok(#krate::Response {
+ status: { #status }.into(),
+ body: #krate::gotham::hyper::Body::empty(),
+ mime: None
+ })),
(None, None) => return Err(Error::new(ident.span(), "Missing #[status(code)] for this variant"))
};
-
+
Ok(quote! {
#enum_ident::#ident #fields_pat => #res
})
}
-
- fn were(&self) -> Option {
+
+ fn were(&self) -> Option
+ {
match self.from_ty.as_ref() {
Some((_, ty)) => Some(quote!( #ty : ::std::error::Error )),
None => None
@@ -217,22 +213,22 @@ impl ErrorVariant {
}
}
-pub fn expand_resource_error(input: DeriveInput) -> Result {
+pub fn expand_resource_error(input : DeriveInput) -> Result
+{
let krate = super::krate();
let ident = input.ident;
let generics = input.generics;
-
+
let inum = match input.data {
Data::Enum(inum) => Ok(inum),
Data::Struct(strukt) => Err(strukt.struct_token.span()),
Data::Union(uni) => Err(uni.union_token.span())
- }
- .map_err(|span| Error::new(span, "#[derive(ResourceError)] only works for enums"))?;
- let variants = inum.variants.into_iter().map(process_variant).collect_to_result()?;
-
- let display_impl = if variants.iter().any(|v| v.display.is_none()) {
- None // TODO issue warning if display is present on some but not all
- } else {
+ }.map_err(|span| Error::new(span, "#[derive(ResourceError)] only works for enums"))?;
+ let variants = inum.variants.into_iter()
+ .map(process_variant)
+ .collect_to_result()?;
+
+ let display_impl = if variants.iter().any(|v| v.display.is_none()) { None } else {
let were = generics.params.iter().filter_map(|param| match param {
GenericParam::Type(ty) => {
let ident = &ty.ident;
@@ -241,8 +237,7 @@ pub fn expand_resource_error(input: DeriveInput) -> Result {
_ => None
});
let formatter_ident = format_ident!("resource_error_display_formatter");
- let match_arms = variants
- .iter()
+ let match_arms = variants.iter()
.map(|v| v.to_display_match_arm(&formatter_ident, &ident))
.collect_to_result()?;
Some(quote! {
@@ -258,39 +253,34 @@ pub fn expand_resource_error(input: DeriveInput) -> Result {
}
})
};
-
- let mut from_impls: Vec = Vec::new();
-
- for var in &variants {
+
+ let mut from_impls : Vec = Vec::new();
+
+ for var in &variants
+ {
let var_ident = &var.ident;
let (from_index, from_ty) = match var.from_ty.as_ref() {
Some(f) => f,
None => continue
};
let from_ident = &var.fields[*from_index].ident;
-
+
let fields_pat = var.fields_pat();
- let fields_where = var
- .fields
- .iter()
- .enumerate()
+ let fields_where = var.fields.iter().enumerate()
.filter(|(i, _)| i != from_index)
.map(|(_, field)| {
let ty = &field.ty;
quote!( #ty : Default )
})
.chain(iter::once(quote!( #from_ty : ::std::error::Error )));
- let fields_let = var
- .fields
- .iter()
- .enumerate()
+ let fields_let = var.fields.iter().enumerate()
.filter(|(i, _)| i != from_index)
.map(|(_, field)| {
let id = &field.ident;
let ty = &field.ty;
quote!( let #id : #ty = Default::default(); )
});
-
+
from_impls.push(quote! {
impl #generics ::std::convert::From<#from_ty> for #ident #generics
where #( #fields_where ),*
@@ -303,21 +293,20 @@ pub fn expand_resource_error(input: DeriveInput) -> Result {
}
});
}
-
+
let were = variants.iter().filter_map(|variant| variant.were()).collect::>();
- let variants = variants
- .into_iter()
+ let variants = variants.into_iter()
.map(|variant| variant.into_match_arm(&krate, &ident))
- .collect_to_result()?;
-
+ .collect_to_result()?;
+
Ok(quote! {
#display_impl
-
+
impl #generics #krate::IntoResponseError for #ident #generics
where #( #were ),*
{
- type Err = #krate::private::serde_json::Error;
-
+ type Err = #krate::export::serde_json::Error;
+
fn into_response_error(self) -> Result<#krate::Response, Self::Err>
{
match self {
@@ -325,7 +314,7 @@ pub fn expand_resource_error(input: DeriveInput) -> Result {
}
}
}
-
+
#( #from_impls )*
})
}
diff --git a/derive/src/util.rs b/derive/src/util.rs
index ef55659..d82dc31 100644
--- a/derive/src/util.rs
+++ b/derive/src/util.rs
@@ -1,70 +1,41 @@
use proc_macro2::{Delimiter, TokenStream, TokenTree};
use std::iter;
-use syn::{Error, Lit, LitBool, LitStr, Path, Result};
+use syn::Error;
-pub(crate) trait CollectToResult {
+pub trait CollectToResult
+{
type Item;
-
- fn collect_to_result(self) -> Result>;
+
+ fn collect_to_result(self) -> Result, Error>;
}
impl
- CollectToResult for I
where
- I: Iterator
- >
+ I : Iterator
- >
{
type Item = Item;
-
- fn collect_to_result(self) -> Result> {
- self.fold(Ok(Vec::new()), |res, code| match (code, res) {
- (Ok(code), Ok(mut codes)) => {
- codes.push(code);
- Ok(codes)
- },
- (Ok(_), Err(errors)) => Err(errors),
- (Err(err), Ok(_)) => Err(err),
- (Err(err), Err(mut errors)) => {
- errors.combine(err);
- Err(errors)
- }
+
+ fn collect_to_result(self) -> Result, Error>
+ {
+ self.fold(, Error>>::Ok(Vec::new()), |res, code| {
+ match (code, res) {
+ (Ok(code), Ok(mut codes)) => { codes.push(code); Ok(codes) },
+ (Ok(_), Err(errors)) => Err(errors),
+ (Err(err), Ok(_)) => Err(err),
+ (Err(err), Err(mut errors)) => { errors.combine(err); Err(errors) }
+ }
})
}
}
-pub(crate) trait ExpectLit {
- fn expect_bool(self) -> Result;
- fn expect_str(self) -> Result;
-}
-impl ExpectLit for Lit {
- fn expect_bool(self) -> Result {
- match self {
- Self::Bool(bool) => Ok(bool),
- _ => Err(Error::new(self.span(), "Expected boolean literal"))
- }
- }
-
- fn expect_str(self) -> Result {
- match self {
- Self::Str(str) => Ok(str),
- _ => Err(Error::new(self.span(), "Expected string literal"))
- }
- }
-}
-
-pub(crate) trait PathEndsWith {
- fn ends_with(&self, s: &str) -> bool;
-}
-
-impl PathEndsWith for Path {
- fn ends_with(&self, s: &str) -> bool {
- self.segments.last().map(|segment| segment.ident.to_string()).as_deref() == Some(s)
- }
-}
-
-pub(crate) fn remove_parens(input: TokenStream) -> TokenStream {
+pub fn remove_parens(input : TokenStream) -> TokenStream
+{
let iter = input.into_iter().flat_map(|tt| {
- if let TokenTree::Group(group) = &tt {
- if group.delimiter() == Delimiter::Parenthesis {
+ if let TokenTree::Group(group) = &tt
+ {
+ if group.delimiter() == Delimiter::Parenthesis
+ {
return Box::new(group.stream().into_iter()) as Box>;
}
}
diff --git a/example/Cargo.toml b/example/Cargo.toml
index 398a5ef..de21cba 100644
--- a/example/Cargo.toml
+++ b/example/Cargo.toml
@@ -2,23 +2,22 @@
[package]
name = "example"
-version = "0.0.0"
+version = "0.0.1"
authors = ["Dominic Meiser "]
edition = "2018"
license = "Unlicense"
readme = "README.md"
+include = ["src/**/*", "Cargo.toml", "LICENSE"]
repository = "https://gitlab.com/msrd0/gotham-restful"
-publish = false
-workspace = ".."
[badges]
gitlab = { repository = "msrd0/gotham-restful", branch = "master" }
[dependencies]
fake = "2.2.2"
-gotham = { version = "0.5.0", default-features = false }
-gotham_derive = "0.5.0"
-gotham_restful = { version = "0.2.0", features = ["auth", "cors", "openapi"], default-features = false }
+gotham = { version = "0.5.0-rc.1", default-features = false }
+gotham_derive = "0.5.0-rc.1"
+gotham_restful = { version = "0.1.0-rc0", features = ["auth", "openapi"] }
log = "0.4.8"
-pretty_env_logger = "0.4"
+log4rs = { version = "0.12.0", features = ["console_appender"], default-features = false }
serde = "1.0.110"
diff --git a/example/src/main.rs b/example/src/main.rs
index e85f911..f953e45 100644
--- a/example/src/main.rs
+++ b/example/src/main.rs
@@ -1,7 +1,5 @@
-#[macro_use]
-extern crate gotham_derive;
-#[macro_use]
-extern crate log;
+#[macro_use] extern crate gotham_derive;
+#[macro_use] extern crate log;
use fake::{faker::internet::en::Username, Fake};
use gotham::{
@@ -11,24 +9,36 @@ use gotham::{
router::builder::*,
state::State
};
-use gotham_restful::{cors::*, *};
+use gotham_restful::*;
+use log::LevelFilter;
+use log4rs::{
+ append::console::ConsoleAppender,
+ config::{Appender, Config, Root},
+ encode::pattern::PatternEncoder
+};
use serde::{Deserialize, Serialize};
#[derive(Resource)]
-#[resource(read_all, read, search, create, update_all, update, remove, remove_all)]
-struct Users {}
-
-#[derive(Resource)]
-#[resource(auth_read_all)]
-struct Auth {}
-
-#[derive(Deserialize, OpenapiType, Serialize, StateData, StaticResponseExtender)]
-struct User {
- username: String
+#[resource(read_all, read, search, create, change_all, change, remove, remove_all)]
+struct Users
+{
}
-#[read_all]
-fn read_all() -> Success>> {
+#[derive(Resource)]
+#[resource(ReadAll)]
+struct Auth
+{
+}
+
+#[derive(Deserialize, OpenapiType, Serialize, StateData, StaticResponseExtender)]
+struct User
+{
+ username : String
+}
+
+#[read_all(Users)]
+fn read_all() -> Success>>
+{
vec![Username().fake(), Username().fake()]
.into_iter()
.map(|username| Some(User { username }))
@@ -36,95 +46,114 @@ fn read_all() -> Success>> {
.into()
}
-#[read]
-fn read(id: u64) -> Success {
- let username: String = Username().fake();
- User {
- username: format!("{}{}", username, id)
- }
- .into()
+#[read(Users)]
+fn read(id : u64) -> Success
+{
+ let username : String = Username().fake();
+ User { username: format!("{}{}", username, id) }.into()
}
-#[search]
-fn search(query: User) -> Success {
+#[search(Users)]
+fn search(query : User) -> Success
+{
query.into()
}
-#[create]
-fn create(body: User) {
+#[create(Users)]
+fn create(body : User)
+{
info!("Created User: {}", body.username);
}
-#[change_all]
-fn update_all(body: Vec) {
- info!(
- "Changing all Users to {:?}",
- body.into_iter().map(|u| u.username).collect::>()
- );
+#[change_all(Users)]
+fn update_all(body : Vec)
+{
+ info!("Changing all Users to {:?}", body.into_iter().map(|u| u.username).collect::>());
}
-#[change]
-fn update(id: u64, body: User) {
+#[change(Users)]
+fn update(id : u64, body : User)
+{
info!("Change User {} to {}", id, body.username);
}
-#[remove_all]
-fn remove_all() {
+#[remove_all(Users)]
+fn remove_all()
+{
info!("Delete all Users");
}
-#[remove]
-fn remove(id: u64) {
+#[remove(Users)]
+fn remove(id : u64)
+{
info!("Delete User {}", id);
}
-#[read_all]
-fn auth_read_all(auth: AuthStatus<()>) -> AuthSuccess {
+#[read_all(Auth)]
+fn auth_read_all(auth : AuthStatus<()>) -> AuthSuccess
+{
match auth {
AuthStatus::Authenticated(data) => Ok(format!("{:?}", data)),
_ => Err(Forbidden)
}
}
-const ADDR: &str = "127.0.0.1:18080";
+const ADDR : &str = "127.0.0.1:18080";
#[derive(Clone, Default)]
struct Handler;
-impl AuthHandler for Handler {
- fn jwt_secret Option>(&self, _state: &mut State, _decode_data: F) -> Option> {
+impl AuthHandler for Handler
+{
+ fn jwt_secret Option>(&self, _state : &mut State, _decode_data : F) -> Option>
+ {
None
}
}
-fn main() {
- pretty_env_logger::init_timed();
-
+fn main()
+{
+ let encoder = PatternEncoder::new("{d(%Y-%m-%d %H:%M:%S%.3f %Z)} [{l}] {M} - {m}\n");
+ let config = Config::builder()
+ .appender(
+ Appender::builder()
+ .build("stdout", Box::new(
+ ConsoleAppender::builder()
+ .encoder(Box::new(encoder))
+ .build()
+ )))
+ .build(Root::builder().appender("stdout").build(LevelFilter::Info))
+ .unwrap();
+ log4rs::init_config(config).unwrap();
+
let cors = CorsConfig {
origin: Origin::Copy,
- headers: Headers::List(vec![CONTENT_TYPE]),
+ headers: vec![CONTENT_TYPE],
credentials: true,
..Default::default()
};
-
+
let auth = >::from_source(AuthSource::AuthorizationHeader);
let logging = RequestLogger::new(log::Level::Info);
- let (chain, pipelines) = single_pipeline(new_pipeline().add(auth).add(logging).add(cors).build());
-
- gotham::start(
- ADDR,
- build_router(chain, pipelines, |route| {
- let info = OpenapiInfo {
- title: "Users Example".to_owned(),
- version: "0.0.1".to_owned(),
- urls: vec![format!("http://{}", ADDR)]
- };
- route.with_openapi(info, |mut route| {
- route.resource::("users");
- route.resource::("auth");
- route.get_openapi("openapi");
- route.swagger_ui("");
- });
- })
+ let (chain, pipelines) = single_pipeline(
+ new_pipeline()
+ .add(auth)
+ .add(logging)
+ .add(cors)
+ .build()
);
+
+ gotham::start(ADDR, build_router(chain, pipelines, |route| {
+ let info = OpenapiInfo {
+ title: "Users Example".to_owned(),
+ version: "0.0.1".to_owned(),
+ urls: vec![format!("http://{}", ADDR)]
+ };
+ route.with_openapi(info, |mut route| {
+ route.resource::("users");
+ route.resource::("auth");
+ route.get_openapi("openapi");
+ });
+ }));
println!("Gotham started on {} for testing", ADDR);
}
+
diff --git a/openapi_type/Cargo.toml b/openapi_type/Cargo.toml
deleted file mode 100644
index 782f798..0000000
--- a/openapi_type/Cargo.toml
+++ /dev/null
@@ -1,27 +0,0 @@
-# -*- eval: (cargo-minor-mode 1) -*-
-
-[package]
-workspace = ".."
-name = "openapi_type"
-version = "0.1.0-dev"
-authors = ["Dominic Meiser "]
-edition = "2018"
-description = "OpenAPI type information for Rust structs and enums"
-keywords = ["openapi", "type"]
-license = "Apache-2.0"
-repository = "https://gitlab.com/msrd0/gotham-restful/-/tree/master/openapi_type"
-
-[dependencies]
-indexmap = "1.6"
-openapi_type_derive = "0.1.0-dev"
-openapiv3 = "=0.3.2"
-serde_json = "1.0"
-
-# optional dependencies / features
-chrono = { version = "0.4.19", optional = true }
-uuid = { version = "0.8.2" , optional = true }
-
-[dev-dependencies]
-paste = "1.0"
-serde = "1.0"
-trybuild = "1.0"
diff --git a/openapi_type/src/impls.rs b/openapi_type/src/impls.rs
deleted file mode 100644
index d46a922..0000000
--- a/openapi_type/src/impls.rs
+++ /dev/null
@@ -1,224 +0,0 @@
-use crate::{OpenapiSchema, OpenapiType};
-#[cfg(feature = "chrono")]
-use chrono::{offset::TimeZone, Date, DateTime, NaiveDate, NaiveDateTime};
-use indexmap::{IndexMap, IndexSet};
-use openapiv3::{
- AdditionalProperties, ArrayType, IntegerType, NumberFormat, NumberType, ObjectType, ReferenceOr, SchemaKind,
- StringFormat, StringType, Type, VariantOrUnknownOrEmpty
-};
-use serde_json::Value;
-use std::{
- collections::{BTreeMap, BTreeSet, HashMap, HashSet},
- hash::BuildHasher,
- num::{NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize}
-};
-#[cfg(feature = "uuid")]
-use uuid::Uuid;
-
-macro_rules! impl_openapi_type {
- ($($ty:ident $(<$($generic:ident : $bound:path),+>)*),* => $schema:expr) => {
- $(
- impl $(<$($generic : $bound),+>)* OpenapiType for $ty $(<$($generic),+>)* {
- fn schema() -> OpenapiSchema {
- $schema
- }
- }
- )*
- };
-}
-
-type Unit = ();
-impl_openapi_type!(Unit => {
- OpenapiSchema::new(SchemaKind::Type(Type::Object(ObjectType {
- additional_properties: Some(AdditionalProperties::Any(false)),
- ..Default::default()
- })))
-});
-
-impl_openapi_type!(Value => {
- OpenapiSchema {
- nullable: true,
- name: None,
- schema: SchemaKind::Any(Default::default()),
- dependencies: Default::default()
- }
-});
-
-impl_openapi_type!(bool => OpenapiSchema::new(SchemaKind::Type(Type::Boolean {})));
-
-#[inline]
-fn int_schema(minimum: Option, bits: Option) -> OpenapiSchema {
- OpenapiSchema::new(SchemaKind::Type(Type::Integer(IntegerType {
- minimum,
- format: bits
- .map(|bits| VariantOrUnknownOrEmpty::Unknown(format!("int{}", bits)))
- .unwrap_or(VariantOrUnknownOrEmpty::Empty),
- ..Default::default()
- })))
-}
-
-impl_openapi_type!(isize => int_schema(None, None));
-impl_openapi_type!(i8 => int_schema(None, Some(8)));
-impl_openapi_type!(i16 => int_schema(None, Some(16)));
-impl_openapi_type!(i32 => int_schema(None, Some(32)));
-impl_openapi_type!(i64 => int_schema(None, Some(64)));
-impl_openapi_type!(i128 => int_schema(None, Some(128)));
-
-impl_openapi_type!(usize => int_schema(Some(0), None));
-impl_openapi_type!(u8 => int_schema(Some(0), Some(8)));
-impl_openapi_type!(u16 => int_schema(Some(0), Some(16)));
-impl_openapi_type!(u32 => int_schema(Some(0), Some(32)));
-impl_openapi_type!(u64 => int_schema(Some(0), Some(64)));
-impl_openapi_type!(u128 => int_schema(Some(0), Some(128)));
-
-impl_openapi_type!(NonZeroUsize => int_schema(Some(1), None));
-impl_openapi_type!(NonZeroU8 => int_schema(Some(1), Some(8)));
-impl_openapi_type!(NonZeroU16 => int_schema(Some(1), Some(16)));
-impl_openapi_type!(NonZeroU32 => int_schema(Some(1), Some(32)));
-impl_openapi_type!(NonZeroU64 => int_schema(Some(1), Some(64)));
-impl_openapi_type!(NonZeroU128 => int_schema(Some(1), Some(128)));
-
-#[inline]
-fn float_schema(format: NumberFormat) -> OpenapiSchema {
- OpenapiSchema::new(SchemaKind::Type(Type::Number(NumberType {
- format: VariantOrUnknownOrEmpty::Item(format),
- ..Default::default()
- })))
-}
-
-impl_openapi_type!(f32 => float_schema(NumberFormat::Float));
-impl_openapi_type!(f64 => float_schema(NumberFormat::Double));
-
-#[inline]
-fn str_schema(format: VariantOrUnknownOrEmpty) -> OpenapiSchema {
- OpenapiSchema::new(SchemaKind::Type(Type::String(StringType {
- format,
- ..Default::default()
- })))
-}
-
-impl_openapi_type!(String, str => str_schema(VariantOrUnknownOrEmpty::Empty));
-
-#[cfg(feature = "chrono")]
-impl_openapi_type!(Date, NaiveDate => {
- str_schema(VariantOrUnknownOrEmpty::Item(StringFormat::Date))
-});
-
-#[cfg(feature = "chrono")]
-impl_openapi_type!(DateTime, NaiveDateTime => {
- str_schema(VariantOrUnknownOrEmpty::Item(StringFormat::DateTime))
-});
-
-#[cfg(feature = "uuid")]
-impl_openapi_type!(Uuid => {
- str_schema(VariantOrUnknownOrEmpty::Unknown("uuid".to_owned()))
-});
-
-impl_openapi_type!(Option => {
- let schema = T::schema();
- let mut dependencies = schema.dependencies.clone();
- let schema = match schema.name.clone() {
- Some(name) => {
- let reference = ReferenceOr::Reference {
- reference: format!("#/components/schemas/{}", name)
- };
- dependencies.insert(name, schema);
- SchemaKind::AllOf { all_of: vec![reference] }
- },
- None => schema.schema
- };
-
- OpenapiSchema {
- nullable: true,
- name: None,
- schema,
- dependencies
- }
-});
-
-#[inline]
-fn array_schema(unique_items: bool) -> OpenapiSchema {
- let schema = T::schema();
- let mut dependencies = schema.dependencies.clone();
-
- let items = match schema.name.clone() {
- Some(name) => {
- let reference = ReferenceOr::Reference {
- reference: format!("#/components/schemas/{}", name)
- };
- dependencies.insert(name, schema);
- reference
- },
- None => ReferenceOr::Item(Box::new(schema.into_schema()))
- };
-
- OpenapiSchema {
- nullable: false,
- name: None,
- schema: SchemaKind::Type(Type::Array(ArrayType {
- items,
- min_items: None,
- max_items: None,
- unique_items
- })),
- dependencies
- }
-}
-
-impl_openapi_type!(Vec => array_schema::(false));
-impl_openapi_type!(BTreeSet, IndexSet, HashSet => {
- array_schema::(true)
-});
-
-#[inline]
-fn map_schema() -> OpenapiSchema {
- let key_schema = K::schema();
- let mut dependencies = key_schema.dependencies.clone();
-
- let keys = match key_schema.name.clone() {
- Some(name) => {
- let reference = ReferenceOr::Reference {
- reference: format!("#/components/schemas/{}", name)
- };
- dependencies.insert(name, key_schema);
- reference
- },
- None => ReferenceOr::Item(Box::new(key_schema.into_schema()))
- };
-
- let schema = T::schema();
- dependencies.extend(schema.dependencies.iter().map(|(k, v)| (k.clone(), v.clone())));
-
- let items = Box::new(match schema.name.clone() {
- Some(name) => {
- let reference = ReferenceOr::Reference {
- reference: format!("#/components/schemas/{}", name)
- };
- dependencies.insert(name, schema);
- reference
- },
- None => ReferenceOr::Item(schema.into_schema())
- });
-
- let mut properties = IndexMap::new();
- properties.insert("default".to_owned(), keys);
-
- OpenapiSchema {
- nullable: false,
- name: None,
- schema: SchemaKind::Type(Type::Object(ObjectType {
- properties,
- required: vec!["default".to_owned()],
- additional_properties: Some(AdditionalProperties::Schema(items)),
- ..Default::default()
- })),
- dependencies
- }
-}
-
-impl_openapi_type!(
- BTreeMap,
- IndexMap,
- HashMap
- => map_schema::()
-);
diff --git a/openapi_type/src/lib.rs b/openapi_type/src/lib.rs
deleted file mode 100644
index 2933027..0000000
--- a/openapi_type/src/lib.rs
+++ /dev/null
@@ -1,86 +0,0 @@
-#![warn(missing_debug_implementations, rust_2018_idioms)]
-#![forbid(unsafe_code)]
-#![cfg_attr(feature = "cargo-clippy", allow(clippy::tabs_in_doc_comments))]
-/*!
-TODO
-*/
-
-pub use indexmap;
-pub use openapi_type_derive::OpenapiType;
-pub use openapiv3 as openapi;
-
-mod impls;
-#[doc(hidden)]
-pub mod private;
-
-use indexmap::IndexMap;
-use openapi::{Schema, SchemaData, SchemaKind};
-
-// TODO update the documentation
-/**
-This struct needs to be available for every type that can be part of an OpenAPI Spec. It is
-already implemented for primitive types, String, Vec, Option and the like. To have it available
-for your type, simply derive from [OpenapiType].
-*/
-#[derive(Debug, Clone, PartialEq)]
-pub struct OpenapiSchema {
- /// The name of this schema. If it is None, the schema will be inlined.
- pub name: Option,
- /// Whether this particular schema is nullable. Note that there is no guarantee that this will
- /// make it into the final specification, it might just be interpreted as a hint to make it
- /// an optional parameter.
- pub nullable: bool,
- /// The actual OpenAPI schema.
- pub schema: SchemaKind,
- /// Other schemas that this schema depends on. They will be included in the final OpenAPI Spec
- /// along with this schema.
- pub dependencies: IndexMap
-}
-
-impl OpenapiSchema {
- /// Create a new schema that has no name.
- pub fn new(schema: SchemaKind) -> Self {
- Self {
- name: None,
- nullable: false,
- schema,
- dependencies: IndexMap::new()
- }
- }
-
- /// Convert this schema to a [Schema] that can be serialized to the OpenAPI Spec.
- pub fn into_schema(self) -> Schema {
- Schema {
- schema_data: SchemaData {
- nullable: self.nullable,
- title: self.name,
- ..Default::default()
- },
- schema_kind: self.schema
- }
- }
-}
-
-/**
-This trait needs to be implemented by every type that is being used in the OpenAPI Spec. It gives
-access to the [OpenapiSchema] of this type. It is provided for primitive types, String and the
-like. For use on your own types, there is a derive macro:
-
-```
-# #[macro_use] extern crate openapi_type_derive;
-#
-#[derive(OpenapiType)]
-struct MyResponse {
- message: String
-}
-```
-*/
-pub trait OpenapiType {
- fn schema() -> OpenapiSchema;
-}
-
-impl<'a, T: ?Sized + OpenapiType> OpenapiType for &'a T {
- fn schema() -> OpenapiSchema {
- T::schema()
- }
-}
diff --git a/openapi_type/src/private.rs b/openapi_type/src/private.rs
deleted file mode 100644
index 892b8e3..0000000
--- a/openapi_type/src/private.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-use crate::OpenapiSchema;
-use indexmap::IndexMap;
-
-pub type Dependencies = IndexMap;
-
-pub fn add_dependencies(dependencies: &mut Dependencies, other: &mut Dependencies) {
- while let Some((dep_name, dep_schema)) = other.pop() {
- if !dependencies.contains_key(&dep_name) {
- dependencies.insert(dep_name, dep_schema);
- }
- }
-}
diff --git a/openapi_type/tests/custom_types.rs b/openapi_type/tests/custom_types.rs
deleted file mode 100644
index 18cca88..0000000
--- a/openapi_type/tests/custom_types.rs
+++ /dev/null
@@ -1,249 +0,0 @@
-#![allow(dead_code)]
-use openapi_type::OpenapiType;
-
-macro_rules! test_type {
- ($ty:ty = $json:tt) => {
- paste::paste! {
- #[test]
- fn [< $ty:lower >]() {
- let schema = <$ty as OpenapiType>::schema();
- let schema = openapi_type::OpenapiSchema::into_schema(schema);
- let schema_json = serde_json::to_value(&schema).unwrap();
- let expected = serde_json::json!($json);
- assert_eq!(schema_json, expected);
- }
- }
- };
-}
-
-#[derive(OpenapiType)]
-struct UnitStruct;
-test_type!(UnitStruct = {
- "type": "object",
- "title": "UnitStruct",
- "additionalProperties": false
-});
-
-#[derive(OpenapiType)]
-struct SimpleStruct {
- foo: String,
- bar: isize
-}
-test_type!(SimpleStruct = {
- "type": "object",
- "title": "SimpleStruct",
- "properties": {
- "foo": {
- "type": "string"
- },
- "bar": {
- "type": "integer"
- }
- },
- "required": ["foo", "bar"]
-});
-
-#[derive(OpenapiType)]
-#[openapi(rename = "FooBar")]
-struct StructRename;
-test_type!(StructRename = {
- "type": "object",
- "title": "FooBar",
- "additionalProperties": false
-});
-
-#[derive(OpenapiType)]
-enum EnumWithoutFields {
- Success,
- Error
-}
-test_type!(EnumWithoutFields = {
- "type": "string",
- "title": "EnumWithoutFields",
- "enum": [
- "Success",
- "Error"
- ]
-});
-
-#[derive(OpenapiType)]
-enum EnumWithOneField {
- Success { value: isize }
-}
-test_type!(EnumWithOneField = {
- "type": "object",
- "title": "EnumWithOneField",
- "properties": {
- "Success": {
- "type": "object",
- "properties": {
- "value": {
- "type": "integer"
- }
- },
- "required": ["value"]
- }
- },
- "required": ["Success"]
-});
-
-#[derive(OpenapiType)]
-enum EnumWithFields {
- Success { value: isize },
- Error { msg: String }
-}
-test_type!(EnumWithFields = {
- "title": "EnumWithFields",
- "oneOf": [{
- "type": "object",
- "properties": {
- "Success": {
- "type": "object",
- "properties": {
- "value": {
- "type": "integer"
- }
- },
- "required": ["value"]
- }
- },
- "required": ["Success"]
- }, {
- "type": "object",
- "properties": {
- "Error": {
- "type": "object",
- "properties": {
- "msg": {
- "type": "string"
- }
- },
- "required": ["msg"]
- }
- },
- "required": ["Error"]
- }]
-});
-
-#[derive(OpenapiType)]
-enum EnumExternallyTagged {
- Success { value: isize },
- Empty,
- Error
-}
-test_type!(EnumExternallyTagged = {
- "title": "EnumExternallyTagged",
- "oneOf": [{
- "type": "object",
- "properties": {
- "Success": {
- "type": "object",
- "properties": {
- "value": {
- "type": "integer"
- }
- },
- "required": ["value"]
- }
- },
- "required": ["Success"]
- }, {
- "type": "string",
- "enum": ["Empty", "Error"]
- }]
-});
-
-#[derive(OpenapiType)]
-#[openapi(tag = "ty")]
-enum EnumInternallyTagged {
- Success { value: isize },
- Empty,
- Error
-}
-test_type!(EnumInternallyTagged = {
- "title": "EnumInternallyTagged",
- "oneOf": [{
- "type": "object",
- "properties": {
- "value": {
- "type": "integer"
- },
- "ty": {
- "type": "string",
- "enum": ["Success"]
- }
- },
- "required": ["value", "ty"]
- }, {
- "type": "object",
- "properties": {
- "ty": {
- "type": "string",
- "enum": ["Empty", "Error"]
- }
- },
- "required": ["ty"]
- }]
-});
-
-#[derive(OpenapiType)]
-#[openapi(tag = "ty", content = "ct")]
-enum EnumAdjacentlyTagged {
- Success { value: isize },
- Empty,
- Error
-}
-test_type!(EnumAdjacentlyTagged = {
- "title": "EnumAdjacentlyTagged",
- "oneOf": [{
- "type": "object",
- "properties": {
- "ty": {
- "type": "string",
- "enum": ["Success"]
- },
- "ct": {
- "type": "object",
- "properties": {
- "value": {
- "type": "integer"
- }
- },
- "required": ["value"]
- }
- },
- "required": ["ty", "ct"]
- }, {
- "type": "object",
- "properties": {
- "ty": {
- "type": "string",
- "enum": ["Empty", "Error"]
- }
- },
- "required": ["ty"]
- }]
-});
-
-#[derive(OpenapiType)]
-#[openapi(untagged)]
-enum EnumUntagged {
- Success { value: isize },
- Empty,
- Error
-}
-test_type!(EnumUntagged = {
- "title": "EnumUntagged",
- "oneOf": [{
- "type": "object",
- "properties": {
- "value": {
- "type": "integer"
- }
- },
- "required": ["value"]
- }, {
- "type": "object",
- "additionalProperties": false
- }]
-});
diff --git a/openapi_type/tests/fail/enum_with_no_variants.rs b/openapi_type/tests/fail/enum_with_no_variants.rs
deleted file mode 100644
index d08e223..0000000
--- a/openapi_type/tests/fail/enum_with_no_variants.rs
+++ /dev/null
@@ -1,6 +0,0 @@
-use openapi_type::OpenapiType;
-
-#[derive(OpenapiType)]
-enum Foo {}
-
-fn main() {}
diff --git a/openapi_type/tests/fail/enum_with_no_variants.stderr b/openapi_type/tests/fail/enum_with_no_variants.stderr
deleted file mode 100644
index 5c6b1d1..0000000
--- a/openapi_type/tests/fail/enum_with_no_variants.stderr
+++ /dev/null
@@ -1,5 +0,0 @@
-error: #[derive(OpenapiType)] does not support enums with no variants
- --> $DIR/enum_with_no_variants.rs:4:10
- |
-4 | enum Foo {}
- | ^^
diff --git a/openapi_type/tests/fail/not_openapitype.rs b/openapi_type/tests/fail/not_openapitype.rs
deleted file mode 100644
index 2b5b23c..0000000
--- a/openapi_type/tests/fail/not_openapitype.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-use openapi_type::OpenapiType;
-
-#[derive(OpenapiType)]
-struct Foo {
- bar: Bar
-}
-
-struct Bar;
-
-fn main() {
- Foo::schema();
-}
diff --git a/openapi_type/tests/fail/not_openapitype.stderr b/openapi_type/tests/fail/not_openapitype.stderr
deleted file mode 100644
index f089b15..0000000
--- a/openapi_type/tests/fail/not_openapitype.stderr
+++ /dev/null
@@ -1,8 +0,0 @@
-error[E0277]: the trait bound `Bar: OpenapiType` is not satisfied
- --> $DIR/not_openapitype.rs:3:10
- |
-3 | #[derive(OpenapiType)]
- | ^^^^^^^^^^^ the trait `OpenapiType` is not implemented for `Bar`
- |
- = note: required by `schema`
- = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/openapi_type/tests/fail/not_openapitype_generics.rs b/openapi_type/tests/fail/not_openapitype_generics.rs
deleted file mode 100644
index 3d2a09d..0000000
--- a/openapi_type/tests/fail/not_openapitype_generics.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-use openapi_type::OpenapiType;
-
-#[derive(OpenapiType)]
-struct Foo {
- bar: T
-}
-
-struct Bar;
-
-fn main() {
- >::schema();
-}
diff --git a/openapi_type/tests/fail/not_openapitype_generics.stderr b/openapi_type/tests/fail/not_openapitype_generics.stderr
deleted file mode 100644
index d33bafe..0000000
--- a/openapi_type/tests/fail/not_openapitype_generics.stderr
+++ /dev/null
@@ -1,23 +0,0 @@
-error[E0599]: no function or associated item named `schema` found for struct `Foo` in the current scope
- --> $DIR/not_openapitype_generics.rs:11:14
- |
-4 | struct Foo {
- | -------------
- | |
- | function or associated item `schema` not found for this
- | doesn't satisfy `Foo: OpenapiType`
-...
-8 | struct Bar;
- | ----------- doesn't satisfy `Bar: OpenapiType`
-...
-11 | >::schema();
- | ^^^^^^ function or associated item not found in `Foo`
- |
- = note: the method `schema` exists but the following trait bounds were not satisfied:
- `Bar: OpenapiType`
- which is required by `Foo: OpenapiType`
- `Foo: OpenapiType`
- which is required by `&Foo: OpenapiType`
- = help: items from traits can only be used if the trait is implemented and in scope
- = note: the following trait defines an item `schema`, perhaps you need to implement it:
- candidate #1: `OpenapiType`
diff --git a/openapi_type/tests/fail/rustfmt.sh b/openapi_type/tests/fail/rustfmt.sh
deleted file mode 100755
index a93f958..0000000
--- a/openapi_type/tests/fail/rustfmt.sh
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/bin/busybox ash
-set -euo pipefail
-
-rustfmt=${RUSTFMT:-rustfmt}
-version="$($rustfmt -V)"
-case "$version" in
- *nightly*)
- # all good, no additional flags required
- ;;
- *)
- # assume we're using some sort of rustup setup
- rustfmt="$rustfmt +nightly"
- ;;
-esac
-
-return=0
-find "$(dirname "$0")" -name '*.rs' -type f | while read file; do
- $rustfmt --config-path "$(dirname "$0")/../../../rustfmt.toml" "$@" "$file" || return=1
-done
-
-exit $return
diff --git a/openapi_type/tests/fail/tuple_struct.rs b/openapi_type/tests/fail/tuple_struct.rs
deleted file mode 100644
index 146a236..0000000
--- a/openapi_type/tests/fail/tuple_struct.rs
+++ /dev/null
@@ -1,6 +0,0 @@
-use openapi_type::OpenapiType;
-
-#[derive(OpenapiType)]
-struct Foo(i64, i64);
-
-fn main() {}
diff --git a/openapi_type/tests/fail/tuple_struct.stderr b/openapi_type/tests/fail/tuple_struct.stderr
deleted file mode 100644
index b5ceb01..0000000
--- a/openapi_type/tests/fail/tuple_struct.stderr
+++ /dev/null
@@ -1,5 +0,0 @@
-error: #[derive(OpenapiType)] does not support tuple structs
- --> $DIR/tuple_struct.rs:4:11
- |
-4 | struct Foo(i64, i64);
- | ^^^^^^^^^^
diff --git a/openapi_type/tests/fail/tuple_variant.rs b/openapi_type/tests/fail/tuple_variant.rs
deleted file mode 100644
index 92aa8d7..0000000
--- a/openapi_type/tests/fail/tuple_variant.rs
+++ /dev/null
@@ -1,8 +0,0 @@
-use openapi_type::OpenapiType;
-
-#[derive(OpenapiType)]
-enum Foo {
- Pair(i64, i64)
-}
-
-fn main() {}
diff --git a/openapi_type/tests/fail/tuple_variant.stderr b/openapi_type/tests/fail/tuple_variant.stderr
deleted file mode 100644
index 05573cb..0000000
--- a/openapi_type/tests/fail/tuple_variant.stderr
+++ /dev/null
@@ -1,5 +0,0 @@
-error: #[derive(OpenapiType)] does not support tuple variants
- --> $DIR/tuple_variant.rs:5:6
- |
-5 | Pair(i64, i64)
- | ^^^^^^^^^^
diff --git a/openapi_type/tests/fail/union.rs b/openapi_type/tests/fail/union.rs
deleted file mode 100644
index d011109..0000000
--- a/openapi_type/tests/fail/union.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-use openapi_type::OpenapiType;
-
-#[derive(OpenapiType)]
-union Foo {
- signed: i64,
- unsigned: u64
-}
-
-fn main() {}
diff --git a/openapi_type/tests/fail/union.stderr b/openapi_type/tests/fail/union.stderr
deleted file mode 100644
index f0feb48..0000000
--- a/openapi_type/tests/fail/union.stderr
+++ /dev/null
@@ -1,5 +0,0 @@
-error: #[derive(OpenapiType)] cannot be used on unions
- --> $DIR/union.rs:4:1
- |
-4 | union Foo {
- | ^^^^^
diff --git a/openapi_type/tests/fail/unknown_attribute.rs b/openapi_type/tests/fail/unknown_attribute.rs
deleted file mode 100644
index 70a4785..0000000
--- a/openapi_type/tests/fail/unknown_attribute.rs
+++ /dev/null
@@ -1,7 +0,0 @@
-use openapi_type::OpenapiType;
-
-#[derive(OpenapiType)]
-#[openapi(pizza)]
-struct Foo;
-
-fn main() {}
diff --git a/openapi_type/tests/fail/unknown_attribute.stderr b/openapi_type/tests/fail/unknown_attribute.stderr
deleted file mode 100644
index 2558768..0000000
--- a/openapi_type/tests/fail/unknown_attribute.stderr
+++ /dev/null
@@ -1,5 +0,0 @@
-error: Unexpected token
- --> $DIR/unknown_attribute.rs:4:11
- |
-4 | #[openapi(pizza)]
- | ^^^^^
diff --git a/openapi_type/tests/std_types.rs b/openapi_type/tests/std_types.rs
deleted file mode 100644
index e10fb89..0000000
--- a/openapi_type/tests/std_types.rs
+++ /dev/null
@@ -1,216 +0,0 @@
-#[cfg(feature = "chrono")]
-use chrono::{Date, DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, Utc};
-use indexmap::{IndexMap, IndexSet};
-use openapi_type::OpenapiType;
-use serde_json::Value;
-use std::{
- collections::{BTreeMap, BTreeSet, HashMap, HashSet},
- num::{NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize}
-};
-#[cfg(feature = "uuid")]
-use uuid::Uuid;
-
-macro_rules! test_type {
- ($($ty:ident $(<$($generic:ident),+>)*),* = $json:tt) => {
- paste::paste! { $(
- #[test]
- fn [< $ty:lower $($(_ $generic:lower)+)* >]() {
- let schema = <$ty $(<$($generic),+>)* as OpenapiType>::schema();
- let schema = openapi_type::OpenapiSchema::into_schema(schema);
- let schema_json = serde_json::to_value(&schema).unwrap();
- let expected = serde_json::json!($json);
- assert_eq!(schema_json, expected);
- }
- )* }
- };
-}
-
-type Unit = ();
-test_type!(Unit = {
- "type": "object",
- "additionalProperties": false
-});
-
-test_type!(Value = {
- "nullable": true
-});
-
-test_type!(bool = {
- "type": "boolean"
-});
-
-// ### integer types
-
-test_type!(isize = {
- "type": "integer"
-});
-
-test_type!(usize = {
- "type": "integer",
- "minimum": 0
-});
-
-test_type!(i8 = {
- "type": "integer",
- "format": "int8"
-});
-
-test_type!(u8 = {
- "type": "integer",
- "format": "int8",
- "minimum": 0
-});
-
-test_type!(i16 = {
- "type": "integer",
- "format": "int16"
-});
-
-test_type!(u16 = {
- "type": "integer",
- "format": "int16",
- "minimum": 0
-});
-
-test_type!(i32 = {
- "type": "integer",
- "format": "int32"
-});
-
-test_type!(u32 = {
- "type": "integer",
- "format": "int32",
- "minimum": 0
-});
-
-test_type!(i64 = {
- "type": "integer",
- "format": "int64"
-});
-
-test_type!(u64 = {
- "type": "integer",
- "format": "int64",
- "minimum": 0
-});
-
-test_type!(i128 = {
- "type": "integer",
- "format": "int128"
-});
-
-test_type!(u128 = {
- "type": "integer",
- "format": "int128",
- "minimum": 0
-});
-
-// ### non-zero integer types
-
-test_type!(NonZeroUsize = {
- "type": "integer",
- "minimum": 1
-});
-
-test_type!(NonZeroU8 = {
- "type": "integer",
- "format": "int8",
- "minimum": 1
-});
-
-test_type!(NonZeroU16 = {
- "type": "integer",
- "format": "int16",
- "minimum": 1
-});
-
-test_type!(NonZeroU32 = {
- "type": "integer",
- "format": "int32",
- "minimum": 1
-});
-
-test_type!(NonZeroU64 = {
- "type": "integer",
- "format": "int64",
- "minimum": 1
-});
-
-test_type!(NonZeroU128 = {
- "type": "integer",
- "format": "int128",
- "minimum": 1
-});
-
-// ### floats
-
-test_type!(f32 = {
- "type": "number",
- "format": "float"
-});
-
-test_type!(f64 = {
- "type": "number",
- "format": "double"
-});
-
-// ### string
-
-test_type!(String = {
- "type": "string"
-});
-
-#[cfg(feature = "uuid")]
-test_type!(Uuid = {
- "type": "string",
- "format": "uuid"
-});
-
-// ### date/time
-
-#[cfg(feature = "chrono")]
-test_type!(Date, Date, Date, NaiveDate = {
- "type": "string",
- "format": "date"
-});
-
-#[cfg(feature = "chrono")]
-test_type!(DateTime, DateTime, DateTime, NaiveDateTime = {
- "type": "string",
- "format": "date-time"
-});
-
-// ### some std types
-
-test_type!(Option = {
- "type": "string",
- "nullable": true
-});
-
-test_type!(Vec = {
- "type": "array",
- "items": {
- "type": "string"
- }
-});
-
-test_type!(BTreeSet, IndexSet, HashSet = {
- "type": "array",
- "items": {
- "type": "string"
- },
- "uniqueItems": true
-});
-
-test_type!(BTreeMap, IndexMap, HashMap = {
- "type": "object",
- "properties": {
- "default": {
- "type": "integer"
- }
- },
- "required": ["default"],
- "additionalProperties": {
- "type": "string"
- }
-});
diff --git a/openapi_type/tests/trybuild.rs b/openapi_type/tests/trybuild.rs
deleted file mode 100644
index b76b676..0000000
--- a/openapi_type/tests/trybuild.rs
+++ /dev/null
@@ -1,7 +0,0 @@
-use trybuild::TestCases;
-
-#[test]
-fn trybuild() {
- let t = TestCases::new();
- t.compile_fail("tests/fail/*.rs");
-}
diff --git a/openapi_type_derive/Cargo.toml b/openapi_type_derive/Cargo.toml
deleted file mode 100644
index ab8e932..0000000
--- a/openapi_type_derive/Cargo.toml
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- eval: (cargo-minor-mode 1) -*-
-
-[package]
-workspace = ".."
-name = "openapi_type_derive"
-version = "0.1.0-dev"
-authors = ["Dominic Meiser "]
-edition = "2018"
-description = "Implementation detail of the openapi_type crate"
-license = "Apache-2.0"
-repository = "https://gitlab.com/msrd0/gotham-restful/-/tree/master/openapi_type_derive"
-
-[lib]
-proc-macro = true
-
-[dependencies]
-proc-macro2 = "1.0"
-quote = "1.0"
-syn = "1.0"
diff --git a/openapi_type_derive/src/codegen.rs b/openapi_type_derive/src/codegen.rs
deleted file mode 100644
index a56c97c..0000000
--- a/openapi_type_derive/src/codegen.rs
+++ /dev/null
@@ -1,143 +0,0 @@
-use crate::parser::{ParseData, ParseDataType};
-use proc_macro2::TokenStream;
-use quote::quote;
-use syn::LitStr;
-
-impl ParseData {
- pub(super) fn gen_schema(&self) -> TokenStream {
- match self {
- Self::Struct(fields) => gen_struct(fields),
- Self::Enum(variants) => gen_enum(variants),
- Self::Alternatives(alt) => gen_alt(alt),
- Self::Unit => gen_unit()
- }
- }
-}
-
-fn gen_struct(fields: &[(LitStr, ParseDataType)]) -> TokenStream {
- let field_name = fields.iter().map(|(name, _)| name);
- let field_schema = fields.iter().map(|(_, ty)| match ty {
- ParseDataType::Type(ty) => {
- quote!(<#ty as ::openapi_type::OpenapiType>::schema())
- },
- ParseDataType::Inline(data) => {
- let code = data.gen_schema();
- quote!(::openapi_type::OpenapiSchema::new(#code))
- }
- });
-
- let openapi = path!(::openapi_type::openapi);
- quote! {
- {
- let mut properties = <::openapi_type::indexmap::IndexMap<
- ::std::string::String,
- #openapi::ReferenceOr<::std::boxed::Box<#openapi::Schema>>
- >>::new();
- let mut required = <::std::vec::Vec<::std::string::String>>::new();
-
- #({
- const FIELD_NAME: &::core::primitive::str = #field_name;
- let mut field_schema = #field_schema;
- ::openapi_type::private::add_dependencies(
- &mut dependencies,
- &mut field_schema.dependencies
- );
-
- // fields in OpenAPI are nullable by default
- match field_schema.nullable {
- true => field_schema.nullable = false,
- false => required.push(::std::string::String::from(FIELD_NAME))
- };
-
- match field_schema.name.as_ref() {
- // include the field schema as reference
- ::std::option::Option::Some(schema_name) => {
- let mut reference = ::std::string::String::from("#/components/schemas/");
- reference.push_str(schema_name);
- properties.insert(
- ::std::string::String::from(FIELD_NAME),
- #openapi::ReferenceOr::Reference { reference }
- );
- dependencies.insert(
- ::std::string::String::from(schema_name),
- field_schema
- );
- },
- // inline the field schema
- ::std::option::Option::None => {
- properties.insert(
- ::std::string::String::from(FIELD_NAME),
- #openapi::ReferenceOr::Item(
- ::std::boxed::Box::new(
- field_schema.into_schema()
- )
- )
- );
- }
- }
- })*
-
- #openapi::SchemaKind::Type(
- #openapi::Type::Object(
- #openapi::ObjectType {
- properties,
- required,
- .. ::std::default::Default::default()
- }
- )
- )
- }
- }
-}
-
-fn gen_enum(variants: &[LitStr]) -> TokenStream {
- let openapi = path!(::openapi_type::openapi);
- quote! {
- {
- let mut enumeration = <::std::vec::Vec<::std::string::String>>::new();
- #(enumeration.push(::std::string::String::from(#variants));)*
- #openapi::SchemaKind::Type(
- #openapi::Type::String(
- #openapi::StringType {
- enumeration,
- .. ::std::default::Default::default()
- }
- )
- )
- }
- }
-}
-
-fn gen_alt(alt: &[ParseData]) -> TokenStream {
- let openapi = path!(::openapi_type::openapi);
- let schema = alt.iter().map(|data| data.gen_schema());
- quote! {
- {
- let mut alternatives = <::std::vec::Vec<
- #openapi::ReferenceOr<#openapi::Schema>
- >>::new();
- #(alternatives.push(#openapi::ReferenceOr::Item(
- ::openapi_type::OpenapiSchema::new(#schema).into_schema()
- ));)*
- #openapi::SchemaKind::OneOf {
- one_of: alternatives
- }
- }
- }
-}
-
-fn gen_unit() -> TokenStream {
- let openapi = path!(::openapi_type::openapi);
- quote! {
- #openapi::SchemaKind::Type(
- #openapi::Type::Object(
- #openapi::ObjectType {
- additional_properties: ::std::option::Option::Some(
- #openapi::AdditionalProperties::Any(false)
- ),
- .. ::std::default::Default::default()
- }
- )
- )
- }
-}
diff --git a/openapi_type_derive/src/lib.rs b/openapi_type_derive/src/lib.rs
deleted file mode 100644
index 0a81bec..0000000
--- a/openapi_type_derive/src/lib.rs
+++ /dev/null
@@ -1,95 +0,0 @@
-#![warn(missing_debug_implementations, rust_2018_idioms)]
-#![deny(broken_intra_doc_links)]
-#![forbid(unsafe_code)]
-//! This crate defines the macros for `#[derive(OpenapiType)]`.
-
-use proc_macro::TokenStream;
-use proc_macro2::TokenStream as TokenStream2;
-use quote::quote;
-use syn::{parse_macro_input, Data, DeriveInput, LitStr, TraitBound, TraitBoundModifier, TypeParamBound};
-
-#[macro_use]
-mod util;
-//use util::*;
-
-mod codegen;
-mod parser;
-use parser::*;
-
-/// The derive macro for [OpenapiType](https://docs.rs/openapi_type/*/openapi_type/trait.OpenapiType.html).
-#[proc_macro_derive(OpenapiType, attributes(openapi))]
-pub fn derive_openapi_type(input: TokenStream) -> TokenStream {
- let input = parse_macro_input!(input);
- expand_openapi_type(input).unwrap_or_else(|err| err.to_compile_error()).into()
-}
-
-fn expand_openapi_type(mut input: DeriveInput) -> syn::Result {
- // parse #[serde] and #[openapi] attributes
- let mut attrs = ContainerAttributes::default();
- for attr in &input.attrs {
- if attr.path.is_ident("serde") {
- parse_container_attrs(attr, &mut attrs, false)?;
- }
- }
- for attr in &input.attrs {
- if attr.path.is_ident("openapi") {
- parse_container_attrs(attr, &mut attrs, true)?;
- }
- }
-
- // prepare impl block for codegen
- let ident = &input.ident;
- let name = ident.to_string();
- let mut name = LitStr::new(&name, ident.span());
- if let Some(rename) = &attrs.rename {
- name = rename.clone();
- }
-
- // prepare the generics - all impl generics will get `OpenapiType` requirement
- let (impl_generics, ty_generics, where_clause) = {
- let generics = &mut input.generics;
- generics.type_params_mut().for_each(|param| {
- param.colon_token.get_or_insert_with(Default::default);
- param.bounds.push(TypeParamBound::Trait(TraitBound {
- paren_token: None,
- modifier: TraitBoundModifier::None,
- lifetimes: None,
- path: path!(::openapi_type::OpenapiType)
- }));
- });
- generics.split_for_impl()
- };
-
- // parse the input data
- let parsed = match &input.data {
- Data::Struct(strukt) => parse_struct(strukt)?,
- Data::Enum(inum) => parse_enum(inum, &attrs)?,
- Data::Union(union) => parse_union(union)?
- };
-
- // run the codegen
- let schema_code = parsed.gen_schema();
-
- // put the code together
- Ok(quote! {
- #[allow(unused_mut)]
- impl #impl_generics ::openapi_type::OpenapiType for #ident #ty_generics #where_clause {
- fn schema() -> ::openapi_type::OpenapiSchema {
- // prepare the dependencies
- let mut dependencies = ::openapi_type::private::Dependencies::new();
-
- // create the schema
- let schema = #schema_code;
-
- // return everything
- const NAME: &::core::primitive::str = #name;
- ::openapi_type::OpenapiSchema {
- name: ::std::option::Option::Some(::std::string::String::from(NAME)),
- nullable: false,
- schema,
- dependencies
- }
- }
- }
- })
-}
diff --git a/openapi_type_derive/src/parser.rs b/openapi_type_derive/src/parser.rs
deleted file mode 100644
index 350fee2..0000000
--- a/openapi_type_derive/src/parser.rs
+++ /dev/null
@@ -1,198 +0,0 @@
-use crate::util::{ExpectLit, ToLitStr};
-use proc_macro2::Span;
-use syn::{
- punctuated::Punctuated, spanned::Spanned as _, Attribute, DataEnum, DataStruct, DataUnion, Fields, FieldsNamed, LitStr,
- Meta, Token, Type
-};
-
-pub(super) enum ParseDataType {
- Type(Type),
- Inline(ParseData)
-}
-
-#[allow(dead_code)]
-pub(super) enum ParseData {
- Struct(Vec<(LitStr, ParseDataType)>),
- Enum(Vec),
- Alternatives(Vec),
- Unit
-}
-
-fn parse_named_fields(named_fields: &FieldsNamed) -> syn::Result {
- let mut fields: Vec<(LitStr, ParseDataType)> = Vec::new();
- for f in &named_fields.named {
- let ident = f
- .ident
- .as_ref()
- .ok_or_else(|| syn::Error::new(f.span(), "#[derive(OpenapiType)] does not support fields without an ident"))?;
- let name = ident.to_lit_str();
- let ty = f.ty.to_owned();
- fields.push((name, ParseDataType::Type(ty)));
- }
- Ok(ParseData::Struct(fields))
-}
-
-pub(super) fn parse_struct(strukt: &DataStruct) -> syn::Result {
- match &strukt.fields {
- Fields::Named(named_fields) => parse_named_fields(named_fields),
- Fields::Unnamed(unnamed_fields) => {
- return Err(syn::Error::new(
- unnamed_fields.span(),
- "#[derive(OpenapiType)] does not support tuple structs"
- ))
- },
- Fields::Unit => Ok(ParseData::Unit)
- }
-}
-
-pub(super) fn parse_enum(inum: &DataEnum, attrs: &ContainerAttributes) -> syn::Result {
- let mut strings: Vec = Vec::new();
- let mut types: Vec<(LitStr, ParseData)> = Vec::new();
-
- for v in &inum.variants {
- let name = v.ident.to_lit_str();
- match &v.fields {
- Fields::Named(named_fields) => {
- types.push((name, parse_named_fields(named_fields)?));
- },
- Fields::Unnamed(unnamed_fields) => {
- return Err(syn::Error::new(
- unnamed_fields.span(),
- "#[derive(OpenapiType)] does not support tuple variants"
- ))
- },
- Fields::Unit => strings.push(name)
- }
- }
-
- let data_strings = if strings.is_empty() {
- None
- } else {
- match (&attrs.tag, &attrs.content, attrs.untagged) {
- // externally tagged (default)
- (None, None, false) => Some(ParseData::Enum(strings)),
- // internally tagged or adjacently tagged
- (Some(tag), _, false) => Some(ParseData::Struct(vec![(
- tag.clone(),
- ParseDataType::Inline(ParseData::Enum(strings))
- )])),
- // untagged
- (None, None, true) => Some(ParseData::Unit),
- // unknown
- _ => return Err(syn::Error::new(Span::call_site(), "Unknown enum representation"))
- }
- };
-
- let data_types =
- if types.is_empty() {
- None
- } else {
- Some(ParseData::Alternatives(
- types
- .into_iter()
- .map(|(name, mut data)| {
- Ok(match (&attrs.tag, &attrs.content, attrs.untagged) {
- // externally tagged (default)
- (None, None, false) => ParseData::Struct(vec![(name, ParseDataType::Inline(data))]),
- // internally tagged
- (Some(tag), None, false) => {
- match &mut data {
- ParseData::Struct(fields) => {
- fields.push((tag.clone(), ParseDataType::Inline(ParseData::Enum(vec![name]))))
- },
- _ => return Err(syn::Error::new(
- tag.span(),
- "#[derive(OpenapiType)] does not support tuple variants on internally tagged enums"
- ))
- };
- data
- },
- // adjacently tagged
- (Some(tag), Some(content), false) => ParseData::Struct(vec![
- (tag.clone(), ParseDataType::Inline(ParseData::Enum(vec![name]))),
- (content.clone(), ParseDataType::Inline(data)),
- ]),
- // untagged
- (None, None, true) => data,
- // unknown
- _ => return Err(syn::Error::new(Span::call_site(), "Unknown enum representation"))
- })
- })
- .collect::>>()?
- ))
- };
-
- match (data_strings, data_types) {
- // only variants without fields
- (Some(data), None) => Ok(data),
- // only one variant with fields
- (None, Some(ParseData::Alternatives(mut alt))) if alt.len() == 1 => Ok(alt.remove(0)),
- // only variants with fields
- (None, Some(data)) => Ok(data),
- // variants with and without fields
- (Some(data), Some(ParseData::Alternatives(mut alt))) => {
- alt.push(data);
- Ok(ParseData::Alternatives(alt))
- },
- // no variants
- (None, None) => Err(syn::Error::new(
- inum.brace_token.span,
- "#[derive(OpenapiType)] does not support enums with no variants"
- )),
- // data_types always produces Alternatives
- _ => unreachable!()
- }
-}
-
-pub(super) fn parse_union(union: &DataUnion) -> syn::Result {
- Err(syn::Error::new(
- union.union_token.span(),
- "#[derive(OpenapiType)] cannot be used on unions"
- ))
-}
-
-#[derive(Default)]
-pub(super) struct ContainerAttributes {
- pub(super) rename: Option,
- pub(super) rename_all: Option,
- pub(super) tag: Option,
- pub(super) content: Option,
- pub(super) untagged: bool
-}
-
-pub(super) fn parse_container_attrs(
- input: &Attribute,
- attrs: &mut ContainerAttributes,
- error_on_unknown: bool
-) -> syn::Result<()> {
- let tokens: Punctuated = input.parse_args_with(Punctuated::parse_terminated)?;
- for token in tokens {
- match token {
- Meta::NameValue(kv) if kv.path.is_ident("rename") => {
- attrs.rename = Some(kv.lit.expect_str()?);
- },
-
- Meta::NameValue(kv) if kv.path.is_ident("rename_all") => {
- attrs.rename_all = Some(kv.lit.expect_str()?);
- },
-
- Meta::NameValue(kv) if kv.path.is_ident("tag") => {
- attrs.tag = Some(kv.lit.expect_str()?);
- },
-
- Meta::NameValue(kv) if kv.path.is_ident("content") => {
- attrs.content = Some(kv.lit.expect_str()?);
- },
-
- Meta::Path(path) if path.is_ident("untagged") => {
- attrs.untagged = true;
- },
-
- Meta::Path(path) if error_on_unknown => return Err(syn::Error::new(path.span(), "Unexpected token")),
- Meta::List(list) if error_on_unknown => return Err(syn::Error::new(list.span(), "Unexpected token")),
- Meta::NameValue(kv) if error_on_unknown => return Err(syn::Error::new(kv.path.span(), "Unexpected token")),
- _ => {}
- }
- }
- Ok(())
-}
diff --git a/openapi_type_derive/src/util.rs b/openapi_type_derive/src/util.rs
deleted file mode 100644
index 2a752e0..0000000
--- a/openapi_type_derive/src/util.rs
+++ /dev/null
@@ -1,52 +0,0 @@
-use proc_macro2::Ident;
-use syn::{Lit, LitStr};
-
-/// Convert any literal path into a [syn::Path].
-macro_rules! path {
- (:: $($segment:ident)::*) => {
- path!(@private Some(Default::default()), $($segment),*)
- };
- ($($segment:ident)::*) => {
- path!(@private None, $($segment),*)
- };
- (@private $leading_colon:expr, $($segment:ident),*) => {
- {
- #[allow(unused_mut)]
- let mut segments: ::syn::punctuated::Punctuated<::syn::PathSegment, _> = Default::default();
- $(
- segments.push(::syn::PathSegment {
- ident: ::proc_macro2::Ident::new(stringify!($segment), ::proc_macro2::Span::call_site()),
- arguments: Default::default()
- });
- )*
- ::syn::Path {
- leading_colon: $leading_colon,
- segments
- }
- }
- };
-}
-
-/// Convert any [Ident] into a [LitStr]. Basically `stringify!`.
-pub(super) trait ToLitStr {
- fn to_lit_str(&self) -> LitStr;
-}
-impl ToLitStr for Ident {
- fn to_lit_str(&self) -> LitStr {
- LitStr::new(&self.to_string(), self.span())
- }
-}
-
-/// Convert a [Lit] to one specific literal type.
-pub(crate) trait ExpectLit {
- fn expect_str(self) -> syn::Result;
-}
-
-impl ExpectLit for Lit {
- fn expect_str(self) -> syn::Result {
- match self {
- Self::Str(str) => Ok(str),
- _ => Err(syn::Error::new(self.span(), "Expected string literal"))
- }
- }
-}
diff --git a/rustfmt.toml b/rustfmt.toml
deleted file mode 100644
index 1cd0ec8..0000000
--- a/rustfmt.toml
+++ /dev/null
@@ -1,19 +0,0 @@
-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
-imports_granularity = "Crate"
-overflow_delimited_expr = true
-use_field_init_shorthand = true
-use_try_shorthand = true
diff --git a/src/auth.rs b/src/auth.rs
index fdbab63..0888ac3 100644
--- a/src/auth.rs
+++ b/src/auth.rs
@@ -1,27 +1,29 @@
-use crate::{AuthError, Forbidden};
-
+use crate::{AuthError, Forbidden, HeaderName};
use cookie::CookieJar;
-use futures_util::{
- future,
- future::{FutureExt, TryFutureExt}
-};
+use futures_util::{future, future::{FutureExt, TryFutureExt}};
use gotham::{
- anyhow,
handler::HandlerFuture,
- hyper::header::{HeaderMap, HeaderName, AUTHORIZATION},
- middleware::{cookie::CookieParser, Middleware, NewMiddleware},
+ hyper::header::{AUTHORIZATION, HeaderMap},
+ middleware::{Middleware, NewMiddleware},
state::{FromState, State}
};
-use jsonwebtoken::{errors::ErrorKind, DecodingKey};
+use jsonwebtoken::{
+ errors::ErrorKind,
+ DecodingKey
+};
use serde::de::DeserializeOwned;
-use std::{marker::PhantomData, panic::RefUnwindSafe, pin::Pin};
+use std::{
+ marker::PhantomData,
+ panic::RefUnwindSafe,
+ pin::Pin
+};
-#[doc(no_inline)]
pub use jsonwebtoken::Validation as AuthValidation;
/// The authentication status returned by the auth middleware for each request.
#[derive(Debug, StateData)]
-pub enum AuthStatus {
+pub enum AuthStatus
+{
/// The auth status is unknown.
Unknown,
/// The request has been performed without any kind of authentication.
@@ -36,9 +38,10 @@ pub enum AuthStatus {
impl Clone for AuthStatus
where
- T: Clone + Send + 'static
+ T : Clone + Send + 'static
{
- fn clone(&self) -> Self {
+ fn clone(&self) -> Self
+ {
match self {
Self::Unknown => Self::Unknown,
Self::Unauthenticated => Self::Unauthenticated,
@@ -49,10 +52,16 @@ where
}
}
-impl Copy for AuthStatus