api-macros: Allow ruma_api! invocation without request / response
This commit is contained in:
parent
95fef0b0ec
commit
345d0cf990
@ -19,10 +19,10 @@ pub struct Api {
|
|||||||
metadata: Metadata,
|
metadata: Metadata,
|
||||||
|
|
||||||
/// The `request` section of the macro.
|
/// The `request` section of the macro.
|
||||||
request: Request,
|
request: Option<Request>,
|
||||||
|
|
||||||
/// The `response` section of the macro.
|
/// The `response` section of the macro.
|
||||||
response: Response,
|
response: Option<Response>,
|
||||||
|
|
||||||
/// The `error` section of the macro.
|
/// The `error` section of the macro.
|
||||||
error_ty: Option<Type>,
|
error_ty: Option<Type>,
|
||||||
@ -32,12 +32,12 @@ pub fn expand_all(api: Api) -> syn::Result<TokenStream> {
|
|||||||
let ruma_api = util::import_ruma_api();
|
let ruma_api = util::import_ruma_api();
|
||||||
let http = quote! { #ruma_api::exports::http };
|
let http = quote! { #ruma_api::exports::http };
|
||||||
|
|
||||||
let description = &api.metadata.description;
|
let metadata = &api.metadata;
|
||||||
let method = &api.metadata.method;
|
let description = &metadata.description;
|
||||||
let name = &api.metadata.name;
|
let method = &metadata.method;
|
||||||
let path = &api.metadata.path;
|
let name = &metadata.name;
|
||||||
let rate_limited: TokenStream = api
|
let path = &metadata.path;
|
||||||
.metadata
|
let rate_limited: TokenStream = metadata
|
||||||
.rate_limited
|
.rate_limited
|
||||||
.iter()
|
.iter()
|
||||||
.map(|r| {
|
.map(|r| {
|
||||||
@ -66,8 +66,8 @@ pub fn expand_all(api: Api) -> syn::Result<TokenStream> {
|
|||||||
let error_ty =
|
let error_ty =
|
||||||
api.error_ty.map_or_else(|| quote! { #ruma_api::error::Void }, |err_ty| quote! { #err_ty });
|
api.error_ty.map_or_else(|| quote! { #ruma_api::error::Void }, |err_ty| quote! { #err_ty });
|
||||||
|
|
||||||
let request = api.request.expand(&api.metadata, &error_ty, &ruma_api);
|
let request = api.request.map(|req| req.expand(metadata, &error_ty, &ruma_api));
|
||||||
let response = api.response.expand(&api.metadata, &error_ty, &ruma_api);
|
let response = api.response.map(|res| res.expand(metadata, &error_ty, &ruma_api));
|
||||||
|
|
||||||
let metadata_doc = format!("Metadata for the `{}` API endpoint.", name.value());
|
let metadata_doc = format!("Metadata for the `{}` API endpoint.", name.value());
|
||||||
|
|
||||||
|
@ -26,8 +26,27 @@ mod kw {
|
|||||||
impl Parse for Api {
|
impl Parse for Api {
|
||||||
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||||
let metadata: Metadata = input.parse()?;
|
let metadata: Metadata = input.parse()?;
|
||||||
let request: Request = input.parse()?;
|
|
||||||
let response: Response = input.parse()?;
|
let attributes = input.call(Attribute::parse_outer)?;
|
||||||
|
let (request, attributes) = if input.peek(kw::request) {
|
||||||
|
let request = parse_request(input, attributes)?;
|
||||||
|
let attributes = input.call(Attribute::parse_outer)?;
|
||||||
|
|
||||||
|
(Some(request), attributes)
|
||||||
|
} else {
|
||||||
|
(None, attributes)
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = if input.peek(kw::response) {
|
||||||
|
Some(parse_response(input, attributes)?)
|
||||||
|
} else if !attributes.is_empty() {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
&attributes[0],
|
||||||
|
"attributes are not supported on the error type",
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: Use `bool::then` when MSRV >= 1.50
|
// TODO: Use `bool::then` when MSRV >= 1.50
|
||||||
let error_ty = if input.peek(kw::error) {
|
let error_ty = if input.peek(kw::error) {
|
||||||
@ -39,248 +58,239 @@ impl Parse for Api {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let newtype_body_field = request.newtype_body_field();
|
if let Some(req) = &request {
|
||||||
if metadata.method == "GET" && (request.has_body_fields() || newtype_body_field.is_some()) {
|
let newtype_body_field = req.newtype_body_field();
|
||||||
let mut combined_error: Option<syn::Error> = None;
|
if metadata.method == "GET" && (req.has_body_fields() || newtype_body_field.is_some()) {
|
||||||
let mut add_error = |field| {
|
let mut combined_error: Option<syn::Error> = None;
|
||||||
let error = syn::Error::new_spanned(field, "GET endpoints can't have body fields");
|
let mut add_error = |field| {
|
||||||
if let Some(combined_error_ref) = &mut combined_error {
|
let error =
|
||||||
combined_error_ref.combine(error);
|
syn::Error::new_spanned(field, "GET endpoints can't have body fields");
|
||||||
} else {
|
if let Some(combined_error_ref) = &mut combined_error {
|
||||||
combined_error = Some(error);
|
combined_error_ref.combine(error);
|
||||||
|
} else {
|
||||||
|
combined_error = Some(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for field in req.body_fields() {
|
||||||
|
add_error(field);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
for field in request.body_fields() {
|
if let Some(field) = newtype_body_field {
|
||||||
add_error(field);
|
add_error(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Err(combined_error.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(field) = newtype_body_field {
|
|
||||||
add_error(field);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Err(combined_error.unwrap());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self { metadata, request, response, error_ty })
|
Ok(Self { metadata, request, response, error_ty })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for Request {
|
fn parse_request(input: ParseStream<'_>, attributes: Vec<Attribute>) -> syn::Result<Request> {
|
||||||
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
let request_kw: kw::request = input.parse()?;
|
||||||
let attributes = input.call(Attribute::parse_outer)?;
|
let _: Token![:] = input.parse()?;
|
||||||
let request_kw: kw::request = input.parse()?;
|
let fields;
|
||||||
let _: Token![:] = input.parse()?;
|
braced!(fields in input);
|
||||||
let fields;
|
|
||||||
braced!(fields in input);
|
|
||||||
|
|
||||||
let mut newtype_body_field = None;
|
let mut newtype_body_field = None;
|
||||||
let mut query_map_field = None;
|
let mut query_map_field = None;
|
||||||
let mut lifetimes = RequestLifetimes::default();
|
let mut lifetimes = RequestLifetimes::default();
|
||||||
|
|
||||||
let fields: Vec<_> = fields
|
let fields: Vec<_> = fields
|
||||||
.parse_terminated::<Field, Token![,]>(Field::parse_named)?
|
.parse_terminated::<Field, Token![,]>(Field::parse_named)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|mut field| {
|
.map(|mut field| {
|
||||||
let mut field_kind = None;
|
let mut field_kind = None;
|
||||||
let mut header = None;
|
let mut header = None;
|
||||||
|
|
||||||
for attr in mem::take(&mut field.attrs) {
|
for attr in mem::take(&mut field.attrs) {
|
||||||
let meta = match Meta::from_attribute(&attr)? {
|
let meta = match Meta::from_attribute(&attr)? {
|
||||||
Some(m) => m,
|
Some(m) => m,
|
||||||
None => {
|
None => {
|
||||||
field.attrs.push(attr);
|
field.attrs.push(attr);
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if field_kind.is_some() {
|
|
||||||
return Err(syn::Error::new_spanned(
|
|
||||||
attr,
|
|
||||||
"There can only be one field kind attribute",
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
field_kind = Some(match meta {
|
if field_kind.is_some() {
|
||||||
Meta::Word(ident) => match &ident.to_string()[..] {
|
return Err(syn::Error::new_spanned(
|
||||||
attr @ "body" | attr @ "raw_body" => req_res_meta_word(
|
attr,
|
||||||
attr,
|
"There can only be one field kind attribute",
|
||||||
&field,
|
|
||||||
&mut newtype_body_field,
|
|
||||||
RequestFieldKind::NewtypeBody,
|
|
||||||
RequestFieldKind::NewtypeRawBody,
|
|
||||||
)?,
|
|
||||||
"path" => RequestFieldKind::Path,
|
|
||||||
"query" => RequestFieldKind::Query,
|
|
||||||
"query_map" => {
|
|
||||||
if let Some(f) = &query_map_field {
|
|
||||||
let mut error = syn::Error::new_spanned(
|
|
||||||
field,
|
|
||||||
"There can only be one query map field",
|
|
||||||
);
|
|
||||||
error.combine(syn::Error::new_spanned(
|
|
||||||
f,
|
|
||||||
"Previous query map field",
|
|
||||||
));
|
|
||||||
return Err(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
query_map_field = Some(field.clone());
|
|
||||||
RequestFieldKind::QueryMap
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(syn::Error::new_spanned(
|
|
||||||
ident,
|
|
||||||
"Invalid #[ruma_api] argument, expected one of \
|
|
||||||
`body`, `path`, `query`, `query_map`",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Meta::NameValue(MetaNameValue { name, value }) => {
|
|
||||||
req_res_name_value(name, value, &mut header, RequestFieldKind::Header)?
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
match field_kind.unwrap_or(RequestFieldKind::Body) {
|
|
||||||
RequestFieldKind::Header => {
|
|
||||||
collect_lifetime_idents(&mut lifetimes.header, &field.ty)
|
|
||||||
}
|
|
||||||
RequestFieldKind::Body => {
|
|
||||||
collect_lifetime_idents(&mut lifetimes.body, &field.ty)
|
|
||||||
}
|
|
||||||
RequestFieldKind::NewtypeBody => {
|
|
||||||
collect_lifetime_idents(&mut lifetimes.body, &field.ty)
|
|
||||||
}
|
|
||||||
RequestFieldKind::NewtypeRawBody => {
|
|
||||||
collect_lifetime_idents(&mut lifetimes.body, &field.ty)
|
|
||||||
}
|
|
||||||
RequestFieldKind::Path => {
|
|
||||||
collect_lifetime_idents(&mut lifetimes.path, &field.ty)
|
|
||||||
}
|
|
||||||
RequestFieldKind::Query => {
|
|
||||||
collect_lifetime_idents(&mut lifetimes.query, &field.ty)
|
|
||||||
}
|
|
||||||
RequestFieldKind::QueryMap => {
|
|
||||||
collect_lifetime_idents(&mut lifetimes.query, &field.ty)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(RequestField::new(field_kind.unwrap_or(RequestFieldKind::Body), field, header))
|
|
||||||
})
|
|
||||||
.collect::<syn::Result<_>>()?;
|
|
||||||
|
|
||||||
if newtype_body_field.is_some() && fields.iter().any(|f| f.is_body()) {
|
|
||||||
// TODO: highlight conflicting fields,
|
|
||||||
return Err(syn::Error::new_spanned(
|
|
||||||
request_kw,
|
|
||||||
"Can't have both a newtype body field and regular body fields",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if query_map_field.is_some() && fields.iter().any(|f| f.is_query()) {
|
|
||||||
return Err(syn::Error::new_spanned(
|
|
||||||
// TODO: raw,
|
|
||||||
request_kw,
|
|
||||||
"Can't have both a query map field and regular query fields",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO when/if `&[(&str, &str)]` is supported remove this
|
|
||||||
if query_map_field.is_some() && !lifetimes.query.is_empty() {
|
|
||||||
return Err(syn::Error::new_spanned(
|
|
||||||
request_kw,
|
|
||||||
"Lifetimes are not allowed for query_map fields",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self { attributes, fields, lifetimes })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for Response {
|
|
||||||
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
|
||||||
let attributes = input.call(Attribute::parse_outer)?;
|
|
||||||
let response_kw: kw::response = input.parse()?;
|
|
||||||
let _: Token![:] = input.parse()?;
|
|
||||||
let fields;
|
|
||||||
braced!(fields in input);
|
|
||||||
|
|
||||||
let mut newtype_body_field = None;
|
|
||||||
|
|
||||||
let fields: Vec<_> = fields
|
|
||||||
.parse_terminated::<Field, Token![,]>(Field::parse_named)?
|
|
||||||
.into_iter()
|
|
||||||
.map(|mut field| {
|
|
||||||
if has_lifetime(&field.ty) {
|
|
||||||
return Err(syn::Error::new(
|
|
||||||
field.ident.span(),
|
|
||||||
"Lifetimes on Response fields cannot be supported until GAT are stable",
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut field_kind = None;
|
field_kind = Some(match meta {
|
||||||
let mut header = None;
|
Meta::Word(ident) => match &ident.to_string()[..] {
|
||||||
|
attr @ "body" | attr @ "raw_body" => req_res_meta_word(
|
||||||
for attr in mem::take(&mut field.attrs) {
|
|
||||||
let meta = match Meta::from_attribute(&attr)? {
|
|
||||||
Some(m) => m,
|
|
||||||
None => {
|
|
||||||
field.attrs.push(attr);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if field_kind.is_some() {
|
|
||||||
return Err(syn::Error::new_spanned(
|
|
||||||
attr,
|
attr,
|
||||||
"There can only be one field kind attribute",
|
&field,
|
||||||
));
|
&mut newtype_body_field,
|
||||||
}
|
RequestFieldKind::NewtypeBody,
|
||||||
|
RequestFieldKind::NewtypeRawBody,
|
||||||
field_kind = Some(match meta {
|
)?,
|
||||||
Meta::Word(ident) => match &ident.to_string()[..] {
|
"path" => RequestFieldKind::Path,
|
||||||
s @ "body" | s @ "raw_body" => req_res_meta_word(
|
"query" => RequestFieldKind::Query,
|
||||||
s,
|
"query_map" => {
|
||||||
&field,
|
if let Some(f) = &query_map_field {
|
||||||
&mut newtype_body_field,
|
let mut error = syn::Error::new_spanned(
|
||||||
ResponseFieldKind::NewtypeBody,
|
field,
|
||||||
ResponseFieldKind::NewtypeRawBody,
|
"There can only be one query map field",
|
||||||
)?,
|
);
|
||||||
_ => {
|
error.combine(syn::Error::new_spanned(
|
||||||
return Err(syn::Error::new_spanned(
|
f,
|
||||||
ident,
|
"Previous query map field",
|
||||||
"Invalid #[ruma_api] argument with value, expected `body`",
|
|
||||||
));
|
));
|
||||||
|
return Err(error);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
Meta::NameValue(MetaNameValue { name, value }) => {
|
query_map_field = Some(field.clone());
|
||||||
req_res_name_value(name, value, &mut header, ResponseFieldKind::Header)?
|
RequestFieldKind::QueryMap
|
||||||
}
|
}
|
||||||
});
|
_ => {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
ident,
|
||||||
|
"Invalid #[ruma_api] argument, expected one of \
|
||||||
|
`body`, `path`, `query`, `query_map`",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Meta::NameValue(MetaNameValue { name, value }) => {
|
||||||
|
req_res_name_value(name, value, &mut header, RequestFieldKind::Header)?
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
match field_kind.unwrap_or(RequestFieldKind::Body) {
|
||||||
|
RequestFieldKind::Header => {
|
||||||
|
collect_lifetime_idents(&mut lifetimes.header, &field.ty)
|
||||||
|
}
|
||||||
|
RequestFieldKind::Body => collect_lifetime_idents(&mut lifetimes.body, &field.ty),
|
||||||
|
RequestFieldKind::NewtypeBody => {
|
||||||
|
collect_lifetime_idents(&mut lifetimes.body, &field.ty)
|
||||||
|
}
|
||||||
|
RequestFieldKind::NewtypeRawBody => {
|
||||||
|
collect_lifetime_idents(&mut lifetimes.body, &field.ty)
|
||||||
|
}
|
||||||
|
RequestFieldKind::Path => collect_lifetime_idents(&mut lifetimes.path, &field.ty),
|
||||||
|
RequestFieldKind::Query => collect_lifetime_idents(&mut lifetimes.query, &field.ty),
|
||||||
|
RequestFieldKind::QueryMap => {
|
||||||
|
collect_lifetime_idents(&mut lifetimes.query, &field.ty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(RequestField::new(field_kind.unwrap_or(RequestFieldKind::Body), field, header))
|
||||||
|
})
|
||||||
|
.collect::<syn::Result<_>>()?;
|
||||||
|
|
||||||
|
if newtype_body_field.is_some() && fields.iter().any(|f| f.is_body()) {
|
||||||
|
// TODO: highlight conflicting fields,
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
request_kw,
|
||||||
|
"Can't have both a newtype body field and regular body fields",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if query_map_field.is_some() && fields.iter().any(|f| f.is_query()) {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
// TODO: raw,
|
||||||
|
request_kw,
|
||||||
|
"Can't have both a query map field and regular query fields",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO when/if `&[(&str, &str)]` is supported remove this
|
||||||
|
if query_map_field.is_some() && !lifetimes.query.is_empty() {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
request_kw,
|
||||||
|
"Lifetimes are not allowed for query_map fields",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Request { attributes, fields, lifetimes })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_response(input: ParseStream<'_>, attributes: Vec<Attribute>) -> syn::Result<Response> {
|
||||||
|
let response_kw: kw::response = input.parse()?;
|
||||||
|
let _: Token![:] = input.parse()?;
|
||||||
|
let fields;
|
||||||
|
braced!(fields in input);
|
||||||
|
|
||||||
|
let mut newtype_body_field = None;
|
||||||
|
|
||||||
|
let fields: Vec<_> = fields
|
||||||
|
.parse_terminated::<Field, Token![,]>(Field::parse_named)?
|
||||||
|
.into_iter()
|
||||||
|
.map(|mut field| {
|
||||||
|
if has_lifetime(&field.ty) {
|
||||||
|
return Err(syn::Error::new(
|
||||||
|
field.ident.span(),
|
||||||
|
"Lifetimes on Response fields cannot be supported until GAT are stable",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut field_kind = None;
|
||||||
|
let mut header = None;
|
||||||
|
|
||||||
|
for attr in mem::take(&mut field.attrs) {
|
||||||
|
let meta = match Meta::from_attribute(&attr)? {
|
||||||
|
Some(m) => m,
|
||||||
|
None => {
|
||||||
|
field.attrs.push(attr);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if field_kind.is_some() {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
attr,
|
||||||
|
"There can only be one field kind attribute",
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(match field_kind.unwrap_or(ResponseFieldKind::Body) {
|
field_kind = Some(match meta {
|
||||||
ResponseFieldKind::Body => ResponseField::Body(field),
|
Meta::Word(ident) => match &ident.to_string()[..] {
|
||||||
ResponseFieldKind::Header => {
|
s @ "body" | s @ "raw_body" => req_res_meta_word(
|
||||||
ResponseField::Header(field, header.expect("missing header name"))
|
s,
|
||||||
|
&field,
|
||||||
|
&mut newtype_body_field,
|
||||||
|
ResponseFieldKind::NewtypeBody,
|
||||||
|
ResponseFieldKind::NewtypeRawBody,
|
||||||
|
)?,
|
||||||
|
_ => {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
ident,
|
||||||
|
"Invalid #[ruma_api] argument with value, expected `body`",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Meta::NameValue(MetaNameValue { name, value }) => {
|
||||||
|
req_res_name_value(name, value, &mut header, ResponseFieldKind::Header)?
|
||||||
}
|
}
|
||||||
ResponseFieldKind::NewtypeBody => ResponseField::NewtypeBody(field),
|
});
|
||||||
ResponseFieldKind::NewtypeRawBody => ResponseField::NewtypeRawBody(field),
|
}
|
||||||
})
|
|
||||||
|
Ok(match field_kind.unwrap_or(ResponseFieldKind::Body) {
|
||||||
|
ResponseFieldKind::Body => ResponseField::Body(field),
|
||||||
|
ResponseFieldKind::Header => {
|
||||||
|
ResponseField::Header(field, header.expect("missing header name"))
|
||||||
|
}
|
||||||
|
ResponseFieldKind::NewtypeBody => ResponseField::NewtypeBody(field),
|
||||||
|
ResponseFieldKind::NewtypeRawBody => ResponseField::NewtypeRawBody(field),
|
||||||
})
|
})
|
||||||
.collect::<syn::Result<_>>()?;
|
})
|
||||||
|
.collect::<syn::Result<_>>()?;
|
||||||
|
|
||||||
if newtype_body_field.is_some() && fields.iter().any(|f| f.is_body()) {
|
if newtype_body_field.is_some() && fields.iter().any(|f| f.is_body()) {
|
||||||
// TODO: highlight conflicting fields,
|
// TODO: highlight conflicting fields,
|
||||||
return Err(syn::Error::new_spanned(
|
return Err(syn::Error::new_spanned(
|
||||||
response_kw,
|
response_kw,
|
||||||
"Can't have both a newtype body field and regular body fields",
|
"Can't have both a newtype body field and regular body fields",
|
||||||
));
|
));
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self { attributes, fields })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(Response { attributes, fields })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_lifetime(ty: &Type) -> bool {
|
fn has_lifetime(ty: &Type) -> bool {
|
||||||
|
@ -5,4 +5,7 @@ fn ui() {
|
|||||||
t.compile_fail("tests/ui/02-invalid-path.rs");
|
t.compile_fail("tests/ui/02-invalid-path.rs");
|
||||||
t.pass("tests/ui/03-move-value.rs");
|
t.pass("tests/ui/03-move-value.rs");
|
||||||
t.compile_fail("tests/ui/04-attributes.rs");
|
t.compile_fail("tests/ui/04-attributes.rs");
|
||||||
|
t.pass("tests/ui/05-request-only.rs");
|
||||||
|
t.pass("tests/ui/06-response-only.rs");
|
||||||
|
t.compile_fail("tests/ui/07-error-type-attribute.rs");
|
||||||
}
|
}
|
||||||
|
50
ruma-api/tests/ui/05-request-only.rs
Normal file
50
ruma-api/tests/ui/05-request-only.rs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
use ruma_api::{
|
||||||
|
error::{FromHttpResponseError, IntoHttpError, Void},
|
||||||
|
ruma_api,
|
||||||
|
};
|
||||||
|
use ruma_serde::Outgoing;
|
||||||
|
|
||||||
|
ruma_api! {
|
||||||
|
metadata: {
|
||||||
|
description: "Does something.",
|
||||||
|
method: POST, // An `http::Method` constant. No imports required.
|
||||||
|
name: "some_endpoint",
|
||||||
|
path: "/_matrix/some/endpoint/:baz",
|
||||||
|
rate_limited: false,
|
||||||
|
authentication: None,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq)] // Make sure attributes work
|
||||||
|
request: {
|
||||||
|
// With no attribute on the field, it will be put into the body of the request.
|
||||||
|
pub foo: String,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Outgoing)]
|
||||||
|
pub struct Response;
|
||||||
|
|
||||||
|
impl TryFrom<http::Response<Vec<u8>>> for Response {
|
||||||
|
type Error = FromHttpResponseError<Void>;
|
||||||
|
|
||||||
|
fn try_from(_: http::Response<Vec<u8>>) -> Result<Self, Self::Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Response> for http::Response<Vec<u8>> {
|
||||||
|
type Error = IntoHttpError;
|
||||||
|
|
||||||
|
fn try_from(_: Response) -> Result<Self, Self::Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let req1 = Request { foo: "foo".into() };
|
||||||
|
let req2 = req1.clone();
|
||||||
|
|
||||||
|
assert_eq!(req1, req2);
|
||||||
|
}
|
24
ruma-api/tests/ui/06-response-only.rs
Normal file
24
ruma-api/tests/ui/06-response-only.rs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
use ruma_api::ruma_api;
|
||||||
|
|
||||||
|
ruma_api! {
|
||||||
|
metadata: {
|
||||||
|
description: "Does something.",
|
||||||
|
method: POST, // An `http::Method` constant. No imports required.
|
||||||
|
name: "some_endpoint",
|
||||||
|
path: "/_matrix/some/endpoint/:baz",
|
||||||
|
rate_limited: false,
|
||||||
|
authentication: None,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq)] // Make sure attributes work
|
||||||
|
response: {
|
||||||
|
pub flag: bool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let res1 = Response { flag: false };
|
||||||
|
let res2 = res1.clone();
|
||||||
|
|
||||||
|
assert_eq!(res1, res2);
|
||||||
|
}
|
21
ruma-api/tests/ui/07-error-type-attribute.rs
Normal file
21
ruma-api/tests/ui/07-error-type-attribute.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
use ruma_api::ruma_api;
|
||||||
|
|
||||||
|
ruma_api! {
|
||||||
|
metadata: {
|
||||||
|
description: "Does something.",
|
||||||
|
method: POST, // An `http::Method` constant. No imports required.
|
||||||
|
name: "some_endpoint",
|
||||||
|
path: "/_matrix/some/endpoint/:baz",
|
||||||
|
rate_limited: false,
|
||||||
|
authentication: None,
|
||||||
|
}
|
||||||
|
|
||||||
|
request: {}
|
||||||
|
|
||||||
|
response: {}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
error: ruma_api::error::Void
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
5
ruma-api/tests/ui/07-error-type-attribute.stderr
Normal file
5
ruma-api/tests/ui/07-error-type-attribute.stderr
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
error: unexpected token
|
||||||
|
--> $DIR/07-error-type-attribute.rs:17:5
|
||||||
|
|
|
||||||
|
17 | #[derive(Default)]
|
||||||
|
| ^
|
Loading…
x
Reference in New Issue
Block a user