diff --git a/CHANGELOG.md b/CHANGELOG.md index af8a451..8b9fe8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Support custom HTTP response headers - New `endpoint` router extension with associated `Endpoint` trait ([!18]) + - Support for custom endpoints using the `#[endpoint]` macro ([!19]) ### Changed - The cors handler can now copy headers from the request if desired @@ -34,3 +35,4 @@ Previous changes are not tracked by this changelog file. Refer to the [releases] [!18]: https://gitlab.com/msrd0/gotham-restful/-/merge_requests/18 + [!19]: https://gitlab.com/msrd0/gotham-restful/-/merge_requests/19 diff --git a/README.md b/README.md index f683381..083bf20 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,11 @@ This crate is just as safe as you'd expect from anything written in safe Rust - ## Endpoints +There are a set of pre-defined endpoints that should cover the majority of REST APIs. However, +it is also possible to define your own endpoints. + +### Pre-defined Endpoints + Assuming you assign `/foobar` to your resource, the following pre-defined endpoints exist: | Endpoint Name | Required Arguments | HTTP Verb | HTTP Path | @@ -82,6 +87,30 @@ fn read(id: u64) -> Success { } ``` +### Custom Endpoints + +Defining custom endpoints is done with the `#[endpoint]` macro. The syntax is similar to that +of the pre-defined endpoints, but you need to give it more context: + +```rust +use gotham_restful::gotham::hyper::Method; + +#[derive(Resource)] +#[resource(custom_endpoint)] +struct CustomResource; + +/// This type is used to parse path parameters. +#[derive(Deserialize, StateData, StaticResponseExtender)] +struct CustomPath { + name: String +} + +#[endpoint(uri = "custom/:name/read", method = "Method::GET", params = false, body = false)] +fn custom_endpoint(path: CustomPath) -> Success { + path.name.into() +} +``` + ## Arguments Some endpoints require arguments. Those should be diff --git a/derive/Cargo.toml b/derive/Cargo.toml index 8e0fb87..adc9d37 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -19,8 +19,11 @@ gitlab = { repository = "msrd0/gotham-restful", branch = "master" } [dependencies] heck = "0.3.1" +once_cell = "1.5" +paste = "1.0" proc-macro2 = "1.0.13" quote = "1.0.6" +regex = "1.4" syn = { version = "1.0.22", features = ["full"] } [features] diff --git a/derive/src/endpoint.rs b/derive/src/endpoint.rs index 1c8d15d..cbaec3b 100644 --- a/derive/src/endpoint.rs +++ b/derive/src/endpoint.rs @@ -1,10 +1,13 @@ -use crate::util::{CollectToResult, PathEndsWith}; +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}; +use quote::{format_ident, quote, ToTokens}; +use regex::Regex; use std::str::FromStr; use syn::{ - spanned::Spanned, Attribute, AttributeArgs, Error, FnArg, ItemFn, Lit, LitBool, Meta, NestedMeta, PatType, Result, - ReturnType, Type + parse::Parse, spanned::Spanned, Attribute, AttributeArgs, Error, Expr, FnArg, ItemFn, LitBool, LitStr, Meta, NestedMeta, + PatType, Result, ReturnType, Type }; pub enum EndpointType { @@ -15,9 +18,52 @@ pub enum EndpointType { UpdateAll, Update, DeleteAll, - Delete + 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; @@ -36,21 +82,26 @@ impl FromStr for EndpointType { } } +static URI_PLACEHOLDER_REGEX: Lazy = Lazy::new(|| Regex::new(r#"(^|/):(?P[^/]+)(/|$)"#).unwrap()); + impl EndpointType { - fn http_method(&self) -> TokenStream { + fn http_method(&self) -> Option { + let hyper_method = quote!(::gotham_restful::gotham::hyper::Method); match self { - Self::ReadAll | Self::Read | Self::Search => quote!(::gotham_restful::gotham::hyper::Method::GET), - Self::Create => quote!(::gotham_restful::gotham::hyper::Method::POST), - Self::UpdateAll | Self::Update => quote!(::gotham_restful::gotham::hyper::Method::PUT), - Self::DeleteAll | Self::Delete => quote!(::gotham_restful::gotham::hyper::Method::DELETE) + 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) -> TokenStream { + fn uri(&self) -> Option { match self { - Self::ReadAll | Self::Create | Self::UpdateAll | Self::DeleteAll => quote!(""), - Self::Read | Self::Update | Self::Delete => quote!(":id"), - Self::Search => quote!("search") + 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) } } @@ -63,6 +114,13 @@ impl EndpointType { 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() } } } @@ -72,7 +130,14 @@ impl EndpointType { Self::ReadAll | Self::Search | Self::Create | Self::UpdateAll | Self::DeleteAll => { quote!(::gotham_restful::gotham::extractor::NoopPathExtractor) }, - Self::Read | Self::Update | Self::Delete => quote!(::gotham_restful::export::IdPlaceholder::<#arg_ty>) + Self::Read | Self::Update | Self::Delete => quote!(::gotham_restful::export::IdPlaceholder::<#arg_ty>), + Self::Custom { .. } => { + if self.has_placeholders().value { + arg_ty.to_token_stream() + } else { + quote!(::gotham_restful::gotham::extractor::NoopPathExtractor) + } + }, } } @@ -87,7 +152,11 @@ impl EndpointType { Self::Search => LitBool { value: true, span: Span::call_site() - } + }, + Self::Custom { params, .. } => params.clone().unwrap_or_else(|| LitBool { + value: false, + span: Span::call_site() + }) } } @@ -96,7 +165,14 @@ impl EndpointType { Self::ReadAll | Self::Read | Self::Create | Self::UpdateAll | Self::Update | Self::DeleteAll | Self::Delete => { quote!(::gotham_restful::gotham::extractor::NoopQueryStringExtractor) }, - Self::Search => quote!(#arg_ty) + Self::Search => quote!(#arg_ty), + Self::Custom { .. } => { + if self.needs_params().value { + arg_ty.to_token_stream() + } else { + quote!(::gotham_restful::gotham::extractor::NoopQueryStringExtractor) + } + }, } } @@ -109,14 +185,25 @@ impl EndpointType { 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::Create | Self::UpdateAll | Self::Update => quote!(#arg_ty), + Self::Custom { .. } => { + if self.needs_body().value { + arg_ty.to_token_stream() + } else { + quote!(::gotham_restful::gotham::extractor::NoopPathExtractor) + } + }, } } } @@ -219,7 +306,7 @@ fn interpret_arg(_index: usize, arg: &PatType) -> Result { } #[cfg(feature = "openapi")] -fn expand_operation_id(operation_id: Option) -> Option { +fn expand_operation_id(operation_id: Option) -> Option { match operation_id { Some(operation_id) => Some(quote! { fn operation_id() -> Option { @@ -231,16 +318,14 @@ fn expand_operation_id(operation_id: Option) -> Option { } #[cfg(not(feature = "openapi"))] -fn expand_operation_id(_: Option) -> Option { +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(|| { - Lit::Bool(LitBool { - value: default, - span: Span::call_site() - }) +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! { @@ -256,22 +341,30 @@ pub fn endpoint_ident(fn_ident: &Ident) -> 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(ty: EndpointType, attrs: AttributeArgs, fun: &ItemFn) -> Result { +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 operation_id: Option = None; - let mut wants_auth: Option = None; + 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("operation_id") { - operation_id = Some(kv.lit); + operation_id = Some(kv.lit.expect_str()?); } else if kv.path.ends_with("wants_auth") { - wants_auth = Some(kv.lit); + 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")); } @@ -324,8 +417,18 @@ fn expand_endpoint_type(ty: EndpointType, attrs: AttributeArgs, fun: &ItemFn) -> Ok(Some(ty)) }; - let http_method = ty.http_method(); - let uri = ty.uri(); + 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 needs_params = ty.needs_params(); @@ -339,7 +442,11 @@ fn expand_endpoint_type(ty: EndpointType, attrs: AttributeArgs, fun: &ItemFn) -> let mut handle_args: Vec = Vec::new(); if has_placeholders.value { - handle_args.push(quote!(placeholders.id)); + 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)); diff --git a/derive/src/lib.rs b/derive/src/lib.rs index f42e50f..39e2855 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -87,6 +87,11 @@ 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)) +} + #[proc_macro_attribute] pub fn read_all(attr: TokenStream, item: TokenStream) -> TokenStream { expand_macro(attr, item, |attr, item| expand_endpoint(EndpointType::ReadAll, attr, item)) diff --git a/derive/src/util.rs b/derive/src/util.rs index aa94ce5..ef55659 100644 --- a/derive/src/util.rs +++ b/derive/src/util.rs @@ -1,21 +1,21 @@ use proc_macro2::{Delimiter, TokenStream, TokenTree}; use std::iter; -use syn::{Error, Path}; +use syn::{Error, Lit, LitBool, LitStr, Path, Result}; -pub trait CollectToResult { +pub(crate) trait CollectToResult { type Item; - fn collect_to_result(self) -> Result, Error>; + fn collect_to_result(self) -> Result>; } impl CollectToResult for I where - I: Iterator> + I: Iterator> { type Item = Item; - fn collect_to_result(self) -> Result, Error> { - self.fold(, Error>>::Ok(Vec::new()), |res, code| match (code, res) { + 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) @@ -30,6 +30,27 @@ where } } +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; } @@ -40,7 +61,7 @@ impl PathEndsWith for Path { } } -pub fn remove_parens(input: TokenStream) -> TokenStream { +pub(crate) 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 { diff --git a/src/lib.rs b/src/lib.rs index 079f208..780f053 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,11 @@ This crate is just as safe as you'd expect from anything written in safe Rust - # Endpoints +There are a set of pre-defined endpoints that should cover the majority of REST APIs. However, +it is also possible to define your own endpoints. + +## Pre-defined Endpoints + Assuming you assign `/foobar` to your resource, the following pre-defined endpoints exist: | Endpoint Name | Required Arguments | HTTP Verb | HTTP Path | @@ -70,6 +75,41 @@ fn read(id: u64) -> Success { # } ``` +## Custom Endpoints + +Defining custom endpoints is done with the `#[endpoint]` macro. The syntax is similar to that +of the pre-defined endpoints, but you need to give it more context: + +```rust,no_run +# #[macro_use] extern crate gotham_derive; +# #[macro_use] extern crate gotham_restful_derive; +# use gotham::router::builder::*; +# use gotham_restful::*; +# use serde::{Deserialize, Serialize}; +use gotham_restful::gotham::hyper::Method; + +#[derive(Resource)] +#[resource(custom_endpoint)] +struct CustomResource; + +/// This type is used to parse path parameters. +#[derive(Deserialize, StateData, StaticResponseExtender)] +# #[cfg_attr(feature = "openapi", derive(OpenapiType))] +struct CustomPath { + name: String +} + +#[endpoint(uri = "custom/:name/read", method = "Method::GET", params = false, body = false)] +fn custom_endpoint(path: CustomPath) -> Success { + path.name.into() +} +# fn main() { +# gotham::start("127.0.0.1:8080", build_simple_router(|route| { +# route.resource::("custom"); +# })); +# } +``` + # Arguments Some endpoints require arguments. Those should be diff --git a/src/openapi/router.rs b/src/openapi/router.rs index a836a40..7ae71c6 100644 --- a/src/openapi/router.rs +++ b/src/openapi/router.rs @@ -83,10 +83,14 @@ macro_rules! implOpenapiRouter { } static URI_PLACEHOLDER_REGEX: Lazy = - Lazy::new(|| Regex::new(r#"(^|/):(?P[^/]+)(/|$)"#).unwrap()); + Lazy::new(|| Regex::new(r#"(?P^|/):(?P[^/]+)(?P/|$)"#).unwrap()); let uri: &str = &E::uri(); - let uri = - URI_PLACEHOLDER_REGEX.replace_all(uri, |captures: &Captures<'_>| format!("{{{}}}", &captures["name"])); + let uri = URI_PLACEHOLDER_REGEX.replace_all(uri, |captures: &Captures<'_>| { + format!( + "{}{{{}}}{}", + &captures["prefix"], &captures["name"], &captures["suffix"] + ) + }); let path = if uri.is_empty() { format!("{}/{}", self.0.scope.unwrap_or_default(), self.1) } else { diff --git a/tests/openapi_specification.json b/tests/openapi_specification.json index c9e6c53..c3297cd 100644 --- a/tests/openapi_specification.json +++ b/tests/openapi_specification.json @@ -44,6 +44,56 @@ }, "openapi": "3.0.2", "paths": { + "/custom": { + "patch": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "No Content" + } + } + } + }, + "/custom/read/{from}/with/{id}": { + "get": { + "parameters": [ + { + "in": "path", + "name": "from", + "required": true, + "schema": { + "type": "string" + }, + "style": "simple" + }, + { + "in": "path", + "name": "id", + "required": true, + "schema": { + "format": "int64", + "minimum": 0, + "type": "integer" + }, + "style": "simple" + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + } + }, "/img/{id}": { "get": { "operationId": "getImage", diff --git a/tests/openapi_specification.rs b/tests/openapi_specification.rs index faa9fb1..a2d33d4 100644 --- a/tests/openapi_specification.rs +++ b/tests/openapi_specification.rs @@ -5,6 +5,7 @@ extern crate gotham_derive; use chrono::{NaiveDate, NaiveDateTime}; use gotham::{ + hyper::Method, pipeline::{new_pipeline, single::single_pipeline}, router::builder::*, test::TestServer @@ -81,6 +82,22 @@ fn search_secret(auth: AuthStatus, _query: SecretQuery) -> AuthSuccess }) } +#[derive(Resource)] +#[resource(custom_read_with, custom_patch)] +struct CustomResource; + +#[derive(Deserialize, OpenapiType, StateData, StaticResponseExtender)] +struct ReadWithPath { + from: String, + id: u64 +} + +#[endpoint(method = "Method::GET", uri = "read/:from/with/:id")] +fn custom_read_with(_path: ReadWithPath) {} + +#[endpoint(method = "Method::PATCH", uri = "", body = true)] +fn custom_patch(_body: String) {} + #[test] fn openapi_specification() { let info = OpenapiInfo { @@ -97,8 +114,9 @@ fn openapi_specification() { let server = TestServer::new(build_router(chain, pipelines, |router| { router.with_openapi(info, |mut router| { router.resource::("img"); - router.get_openapi("openapi"); router.resource::("secret"); + router.resource::("custom"); + router.get_openapi("openapi"); }); })) .unwrap(); diff --git a/tests/ui/endpoint/custom_method_invalid_expr.rs b/tests/ui/endpoint/custom_method_invalid_expr.rs new file mode 100644 index 0000000..9d6450d --- /dev/null +++ b/tests/ui/endpoint/custom_method_invalid_expr.rs @@ -0,0 +1,11 @@ +#[macro_use] +extern crate gotham_restful; + +#[derive(Resource)] +#[resource(read_all)] +struct FooResource; + +#[endpoint(method = "I like pizza", uri = "custom_read")] +async fn read_all() {} + +fn main() {} diff --git a/tests/ui/endpoint/custom_method_invalid_expr.stderr b/tests/ui/endpoint/custom_method_invalid_expr.stderr new file mode 100644 index 0000000..8375cd1 --- /dev/null +++ b/tests/ui/endpoint/custom_method_invalid_expr.stderr @@ -0,0 +1,11 @@ +error: unexpected token + --> $DIR/custom_method_invalid_expr.rs:8:21 + | +8 | #[endpoint(method = "I like pizza", uri = "custom_read")] + | ^^^^^^^^^^^^^^ + +error[E0412]: cannot find type `read_all___gotham_restful_endpoint` in this scope + --> $DIR/custom_method_invalid_expr.rs:5:12 + | +5 | #[resource(read_all)] + | ^^^^^^^^ not found in this scope diff --git a/tests/ui/endpoint/custom_method_invalid_type.rs b/tests/ui/endpoint/custom_method_invalid_type.rs new file mode 100644 index 0000000..b5bc674 --- /dev/null +++ b/tests/ui/endpoint/custom_method_invalid_type.rs @@ -0,0 +1,11 @@ +#[macro_use] +extern crate gotham_restful; + +#[derive(Resource)] +#[resource(read_all)] +struct FooResource; + +#[endpoint(method = "String::new()", uri = "custom_read")] +async fn read_all() {} + +fn main() {} diff --git a/tests/ui/endpoint/custom_method_invalid_type.stderr b/tests/ui/endpoint/custom_method_invalid_type.stderr new file mode 100644 index 0000000..35ad1c3 --- /dev/null +++ b/tests/ui/endpoint/custom_method_invalid_type.stderr @@ -0,0 +1,8 @@ +error[E0308]: mismatched types + --> $DIR/custom_method_invalid_type.rs:8:21 + | +8 | #[endpoint(method = "String::new()", uri = "custom_read")] + | --------------------^^^^^^^^^^^^^^^----------------------- + | | | + | | expected struct `Method`, found struct `std::string::String` + | expected `Method` because of return type diff --git a/tests/ui/endpoint/custom_method_missing.rs b/tests/ui/endpoint/custom_method_missing.rs new file mode 100644 index 0000000..d0e6855 --- /dev/null +++ b/tests/ui/endpoint/custom_method_missing.rs @@ -0,0 +1,11 @@ +#[macro_use] +extern crate gotham_restful; + +#[derive(Resource)] +#[resource(read_all)] +struct FooResource; + +#[endpoint(uri = "custom_read")] +async fn read_all() {} + +fn main() {} diff --git a/tests/ui/endpoint/custom_method_missing.stderr b/tests/ui/endpoint/custom_method_missing.stderr new file mode 100644 index 0000000..df6da97 --- /dev/null +++ b/tests/ui/endpoint/custom_method_missing.stderr @@ -0,0 +1,13 @@ +error: Missing `method` attribute (e.g. `#[endpoint(method = "gotham_restful::gotham::hyper::Method::GET")]`) + --> $DIR/custom_method_missing.rs:8:1 + | +8 | #[endpoint(uri = "custom_read")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0412]: cannot find type `read_all___gotham_restful_endpoint` in this scope + --> $DIR/custom_method_missing.rs:5:12 + | +5 | #[resource(read_all)] + | ^^^^^^^^ not found in this scope diff --git a/tests/ui/endpoint/custom_uri_missing.rs b/tests/ui/endpoint/custom_uri_missing.rs new file mode 100644 index 0000000..5ec5182 --- /dev/null +++ b/tests/ui/endpoint/custom_uri_missing.rs @@ -0,0 +1,11 @@ +#[macro_use] +extern crate gotham_restful; + +#[derive(Resource)] +#[resource(read_all)] +struct FooResource; + +#[endpoint(method = "gotham_restful::gotham::hyper::Method::GET")] +async fn read_all() {} + +fn main() {} diff --git a/tests/ui/endpoint/custom_uri_missing.stderr b/tests/ui/endpoint/custom_uri_missing.stderr new file mode 100644 index 0000000..b1584b8 --- /dev/null +++ b/tests/ui/endpoint/custom_uri_missing.stderr @@ -0,0 +1,13 @@ +error: Missing `uri` attribute (e.g. `#[endpoint(uri = "custom_endpoint")]`) + --> $DIR/custom_uri_missing.rs:8:1 + | +8 | #[endpoint(method = "gotham_restful::gotham::hyper::Method::GET")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0412]: cannot find type `read_all___gotham_restful_endpoint` in this scope + --> $DIR/custom_uri_missing.rs:5:12 + | +5 | #[resource(read_all)] + | ^^^^^^^^ not found in this scope diff --git a/tests/ui/endpoint/non_custom_body_attribute.rs b/tests/ui/endpoint/non_custom_body_attribute.rs new file mode 100644 index 0000000..e6257d4 --- /dev/null +++ b/tests/ui/endpoint/non_custom_body_attribute.rs @@ -0,0 +1,11 @@ +#[macro_use] +extern crate gotham_restful; + +#[derive(Resource)] +#[resource(read_all)] +struct FooResource; + +#[read_all(body = false)] +async fn read_all() {} + +fn main() {} diff --git a/tests/ui/endpoint/non_custom_body_attribute.stderr b/tests/ui/endpoint/non_custom_body_attribute.stderr new file mode 100644 index 0000000..a30e540 --- /dev/null +++ b/tests/ui/endpoint/non_custom_body_attribute.stderr @@ -0,0 +1,11 @@ +error: `body` can only be used on custom endpoints + --> $DIR/non_custom_body_attribute.rs:8:12 + | +8 | #[read_all(body = false)] + | ^^^^ + +error[E0412]: cannot find type `read_all___gotham_restful_endpoint` in this scope + --> $DIR/non_custom_body_attribute.rs:5:12 + | +5 | #[resource(read_all)] + | ^^^^^^^^ not found in this scope diff --git a/tests/ui/endpoint/non_custom_method_attribute.rs b/tests/ui/endpoint/non_custom_method_attribute.rs new file mode 100644 index 0000000..c23423c --- /dev/null +++ b/tests/ui/endpoint/non_custom_method_attribute.rs @@ -0,0 +1,11 @@ +#[macro_use] +extern crate gotham_restful; + +#[derive(Resource)] +#[resource(read_all)] +struct FooResource; + +#[read_all(method = "gotham_restful::gotham::hyper::Method::GET")] +async fn read_all() {} + +fn main() {} diff --git a/tests/ui/endpoint/non_custom_method_attribute.stderr b/tests/ui/endpoint/non_custom_method_attribute.stderr new file mode 100644 index 0000000..42a541d --- /dev/null +++ b/tests/ui/endpoint/non_custom_method_attribute.stderr @@ -0,0 +1,11 @@ +error: `method` can only be used on custom endpoints + --> $DIR/non_custom_method_attribute.rs:8:12 + | +8 | #[read_all(method = "gotham_restful::gotham::hyper::Method::GET")] + | ^^^^^^ + +error[E0412]: cannot find type `read_all___gotham_restful_endpoint` in this scope + --> $DIR/non_custom_method_attribute.rs:5:12 + | +5 | #[resource(read_all)] + | ^^^^^^^^ not found in this scope diff --git a/tests/ui/endpoint/non_custom_params_attribute.rs b/tests/ui/endpoint/non_custom_params_attribute.rs new file mode 100644 index 0000000..377331b --- /dev/null +++ b/tests/ui/endpoint/non_custom_params_attribute.rs @@ -0,0 +1,11 @@ +#[macro_use] +extern crate gotham_restful; + +#[derive(Resource)] +#[resource(read_all)] +struct FooResource; + +#[read_all(params = true)] +async fn read_all() {} + +fn main() {} diff --git a/tests/ui/endpoint/non_custom_params_attribute.stderr b/tests/ui/endpoint/non_custom_params_attribute.stderr new file mode 100644 index 0000000..9ca336e --- /dev/null +++ b/tests/ui/endpoint/non_custom_params_attribute.stderr @@ -0,0 +1,11 @@ +error: `params` can only be used on custom endpoints + --> $DIR/non_custom_params_attribute.rs:8:12 + | +8 | #[read_all(params = true)] + | ^^^^^^ + +error[E0412]: cannot find type `read_all___gotham_restful_endpoint` in this scope + --> $DIR/non_custom_params_attribute.rs:5:12 + | +5 | #[resource(read_all)] + | ^^^^^^^^ not found in this scope diff --git a/tests/ui/endpoint/non_custom_uri_attribute.rs b/tests/ui/endpoint/non_custom_uri_attribute.rs new file mode 100644 index 0000000..1945fce --- /dev/null +++ b/tests/ui/endpoint/non_custom_uri_attribute.rs @@ -0,0 +1,11 @@ +#[macro_use] +extern crate gotham_restful; + +#[derive(Resource)] +#[resource(read_all)] +struct FooResource; + +#[read_all(uri = "custom_read")] +async fn read_all() {} + +fn main() {} diff --git a/tests/ui/endpoint/non_custom_uri_attribute.stderr b/tests/ui/endpoint/non_custom_uri_attribute.stderr new file mode 100644 index 0000000..61018a7 --- /dev/null +++ b/tests/ui/endpoint/non_custom_uri_attribute.stderr @@ -0,0 +1,11 @@ +error: `uri` can only be used on custom endpoints + --> $DIR/non_custom_uri_attribute.rs:8:12 + | +8 | #[read_all(uri = "custom_read")] + | ^^^ + +error[E0412]: cannot find type `read_all___gotham_restful_endpoint` in this scope + --> $DIR/non_custom_uri_attribute.rs:5:12 + | +5 | #[resource(read_all)] + | ^^^^^^^^ not found in this scope diff --git a/tests/ui/endpoint/wants_auth_non_bool.rs b/tests/ui/endpoint/wants_auth_non_bool.rs new file mode 100644 index 0000000..b341aaf --- /dev/null +++ b/tests/ui/endpoint/wants_auth_non_bool.rs @@ -0,0 +1,11 @@ +#[macro_use] +extern crate gotham_restful; + +#[derive(Resource)] +#[resource(read_all)] +struct FooResource; + +#[read_all(wants_auth = "yes, please")] +async fn read_all() {} + +fn main() {} diff --git a/tests/ui/endpoint/wants_auth_non_bool.stderr b/tests/ui/endpoint/wants_auth_non_bool.stderr new file mode 100644 index 0000000..e752c40 --- /dev/null +++ b/tests/ui/endpoint/wants_auth_non_bool.stderr @@ -0,0 +1,11 @@ +error: Expected boolean literal + --> $DIR/wants_auth_non_bool.rs:8:25 + | +8 | #[read_all(wants_auth = "yes, please")] + | ^^^^^^^^^^^^^ + +error[E0412]: cannot find type `read_all___gotham_restful_endpoint` in this scope + --> $DIR/wants_auth_non_bool.rs:5:12 + | +5 | #[resource(read_all)] + | ^^^^^^^^ not found in this scope