1
0
Fork 0
mirror of https://gitlab.com/msrd0/gotham-restful.git synced 2025-02-22 12:42:28 +00:00

Custom Endpoints

This commit is contained in:
msrd0 2021-01-18 16:56:16 +00:00
parent 002cfb1b4d
commit 5261aa9931
28 changed files with 524 additions and 46 deletions

View file

@ -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

View file

@ -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<Foo> {
}
```
### 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<String> {
path.name.into()
}
```
## Arguments
Some endpoints require arguments. Those should be

View file

@ -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]

View file

@ -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<Expr>,
uri: Option<LitStr>,
params: Option<LitBool>,
body: Option<LitBool>
}
}
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 [<set_ $name>](&mut self, span: Span, [<new_ $name>]: $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([<new_ $name>]);
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<Regex> = Lazy::new(|| Regex::new(r#"(^|/):(?P<name>[^/]+)(/|$)"#).unwrap());
impl EndpointType {
fn http_method(&self) -> TokenStream {
fn http_method(&self) -> Option<TokenStream> {
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<TokenStream> {
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<HandlerArg> {
}
#[cfg(feature = "openapi")]
fn expand_operation_id(operation_id: Option<Lit>) -> Option<TokenStream> {
fn expand_operation_id(operation_id: Option<LitStr>) -> Option<TokenStream> {
match operation_id {
Some(operation_id) => Some(quote! {
fn operation_id() -> Option<String> {
@ -231,16 +318,14 @@ fn expand_operation_id(operation_id: Option<Lit>) -> Option<TokenStream> {
}
#[cfg(not(feature = "openapi"))]
fn expand_operation_id(_: Option<Lit>) -> Option<TokenStream> {
fn expand_operation_id(_: Option<LitStr>) -> Option<TokenStream> {
None
}
fn expand_wants_auth(wants_auth: Option<Lit>, 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<LitBool>, 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<TokenStream> {
fn expand_endpoint_type(mut ty: EndpointType, attrs: AttributeArgs, fun: &ItemFn) -> Result<TokenStream> {
// 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<Lit> = None;
let mut wants_auth: Option<Lit> = None;
let mut operation_id: Option<LitStr> = None;
let mut wants_auth: Option<LitBool> = 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<TokenStream> = 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));

View file

@ -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))

View file

@ -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<Vec<Self::Item>, Error>;
fn collect_to_result(self) -> Result<Vec<Self::Item>>;
}
impl<Item, I> CollectToResult for I
where
I: Iterator<Item = Result<Item, Error>>
I: Iterator<Item = Result<Item>>
{
type Item = Item;
fn collect_to_result(self) -> Result<Vec<Item>, Error> {
self.fold(<Result<Vec<Item>, Error>>::Ok(Vec::new()), |res, code| match (code, res) {
fn collect_to_result(self) -> Result<Vec<Item>> {
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<LitBool>;
fn expect_str(self) -> Result<LitStr>;
}
impl ExpectLit for Lit {
fn expect_bool(self) -> Result<LitBool> {
match self {
Self::Bool(bool) => Ok(bool),
_ => Err(Error::new(self.span(), "Expected boolean literal"))
}
}
fn expect_str(self) -> Result<LitStr> {
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 {

View file

@ -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<Foo> {
# }
```
## 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<String> {
path.name.into()
}
# fn main() {
# gotham::start("127.0.0.1:8080", build_simple_router(|route| {
# route.resource::<CustomResource>("custom");
# }));
# }
```
# Arguments
Some endpoints require arguments. Those should be

View file

@ -83,10 +83,14 @@ macro_rules! implOpenapiRouter {
}
static URI_PLACEHOLDER_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r#"(^|/):(?P<name>[^/]+)(/|$)"#).unwrap());
Lazy::new(|| Regex::new(r#"(?P<prefix>^|/):(?P<name>[^/]+)(?P<suffix>/|$)"#).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 {

View file

@ -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",

View file

@ -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<Secrets>
})
}
#[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::<ImageResource>("img");
router.get_openapi("openapi");
router.resource::<SecretResource>("secret");
router.resource::<CustomResource>("custom");
router.get_openapi("openapi");
});
}))
.unwrap();

View file

@ -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() {}

View file

@ -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

View file

@ -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() {}

View file

@ -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

View file

@ -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() {}

View file

@ -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

View file

@ -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() {}

View file

@ -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

View file

@ -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() {}

View file

@ -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

View file

@ -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() {}

View file

@ -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

View file

@ -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() {}

View file

@ -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

View file

@ -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() {}

View file

@ -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

View file

@ -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() {}

View file

@ -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