Skip to content

Commit

Permalink
Improve proc macro errors
Browse files Browse the repository at this point in the history
  • Loading branch information
juhaku committed Jan 20, 2022
1 parent dd744de commit b544e7b
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 112 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ mod pet_api {
(status = 404, description = "Pet was not found")
],
params = [
("id" = u64, path, description = "Pet database id to get Per for"),
("id" = u64, path, description = "Pet database id to get Pet for"),
]
)]
async fn get_pet_by_id(pet_id: u64) -> Pet {
Expand Down Expand Up @@ -97,7 +97,7 @@ This would produce api doc something similar to:
{
"name": "id",
"in": "path",
"description": "Pet database id to get Per for",
"description": "Pet database id to get Pet for",
"required": true,
"deprecated": false,
"schema": {
Expand Down
2 changes: 1 addition & 1 deletion tests/utoipa_gen_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ mod pet_api {
(status = 404, description = "Pet was not found")
],
params = [
("id" = u64, path, description = "Pet database id to get Per for"),
("id" = u64, path, description = "Pet database id to get Pet for"),
]
)]
#[allow(unused)]
Expand Down
61 changes: 38 additions & 23 deletions utoipa-gen/src/component/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,16 @@ impl Parse for ComponentAttr<Enum> {
}
"example" => {
enum_attr.example = Some(parse_utils::parse_next(input, || {
parse_lit_as_string(input, name, "unparseable example, expected Literal")
parse_lit_as_string(input, name, "unparseable example, expected literal")
}))
}
_ => {
return Err(Error::new(
ident.span(),
format!("unexpected attribute: {}, expected: default, example", name),
format!(
"unexpected identifer: {}, expected any of: default, example",
name
),
))
}
}
Expand Down Expand Up @@ -107,9 +110,9 @@ impl Parse for ComponentAttr<Struct> {
input.parse::<Ident>().unwrap();
input.parse::<Token![!]>().unwrap();

Ok(input.parse::<Group>().expect_or_abort(
"unparseable example, expected Parenthesized Group e.g. json!(...)",
))
Ok(input
.parse::<Group>()
.expect_or_abort("unparseable example, expected parenthesis"))
} else {
Err(Error::new(
ident.span(),
Expand All @@ -126,7 +129,7 @@ impl Parse for ComponentAttr<Struct> {
}
_ => Err(Error::new(
ident.span(),
format!("unexpected attribute: {}, expected: example", name),
format!("unexpected identifer: {}, expected: example", name),
)),
}
}
Expand All @@ -137,9 +140,9 @@ impl Parse for ComponentAttr<UnnamedFieldStruct> {
let mut unnamed_struct = UnnamedFieldStruct::default();

loop {
let attribute = input
.parse::<Ident>()
.expect_or_abort("Unparseable ComponentAttr<UnnamedFieldStruct>, expected Ident");
let attribute = input.parse::<Ident>().expect_or_abort(
"Unparseable ComponentAttr<UnnamedFieldStruct>, expected identifier",
);
let name = &*attribute.to_string();

match name {
Expand All @@ -150,13 +153,20 @@ impl Parse for ComponentAttr<UnnamedFieldStruct> {
}
"example" => {
unnamed_struct.example = Some(parse_utils::parse_next(input, || {
parse_lit_as_string(input, name, "unparseable example, expected Literal")
parse_lit_as_string(
input,
name,
"unparseable example, expected literal string",
)
}))
}
_ => {
return Err(Error::new(
attribute.span(),
format!("unexpected attribute: {}, expected default, example", name),
format!(
"unexpected identifier: {}, expected any of: default, example",
name
),
))
}
}
Expand All @@ -183,19 +193,19 @@ impl Parse for ComponentAttr<NamedField> {
loop {
let ident = input
.parse::<Ident>()
.expect_or_abort("Unparseable ComponentAttr<NamedField>, expected Ident");
.expect_or_abort("Unparseable ComponentAttr<NamedField>, expected identifier");
let name = &*ident.to_string();

match name {
"example" => {
field.example = Some(parse_utils::parse_next(input, || {
parse_lit_as_string(input, name, "unparseable example, expected Literal")
parse_lit_as_string(input, name, "unparseable example, expected literal")
}));
}
"format" => {
let format = parse_utils::parse_next(input, || {
input.parse::<ExprPath>().expect_or_abort(
"unparseable format expected ExprPath e.g. ComponentFormat::String",
"unparseable format expected expression path e.g. ComponentFormat::String",
)
});

Expand All @@ -215,9 +225,9 @@ impl Parse for ComponentAttr<NamedField> {
return Err(Error::new(
ident.span(),
format!(
"unexpected attribute: {}, expected: deprecated, example, format, default",
name
),
"unexpected identifier: {}, expected any of: example, format, default",
name
),
))
}
}
Expand All @@ -242,7 +252,7 @@ fn parse_lit_as_string(input: &ParseBuffer, field: &str, error_msg: &str) -> Str
Lit::ByteStr(byte_str) => String::from_utf8(byte_str.value()).unwrap_or_else(|_| {
abort!(
input.span(),
format!("Unparseable utf8 content in: {}", &field)
format!("unparseable utf8 content in: {}", &field)
)
}),
Lit::Char(char) => char.value().to_string(),
Expand All @@ -252,22 +262,27 @@ fn parse_lit_as_string(input: &ParseBuffer, field: &str, error_msg: &str) -> Str
Lit::Verbatim(_) => {
abort!(
input.span(),
format!("Unparseable literal in field: {}", &field)
format!("unparseable literal in field: {}", &field)
)
}
}
}

fn parse_default_as_token_stream(input: &ParseBuffer, name: &str) -> TokenStream {
if input.peek(Lit) {
let literal = parse_lit_as_string(input, name, "unparseable default, expected Literal");
let literal = parse_lit_as_string(
input,
name,
&format!("unparseable {}, expected literal", name),
);
quote_spanned! {input.span()=>
#literal
}
} else {
let method = input
.parse::<ExprPath>()
.expect_or_abort("unparseable default, expected Literal, or ExprPath");
let method = input.parse::<ExprPath>().expect_or_abort(&format!(
"unparseable {}, expected literal or expresssion path",
name
));
quote_spanned! {input.span()=>
#method()
}
Expand Down
4 changes: 2 additions & 2 deletions utoipa-gen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ impl Parse for MediaType {
};

let ty = if input.peek(syn::Ident) {
parse_ident(input, "unparseable MediaType, expected Ident")
parse_ident(input, "unparseable MediaType, expected identifer")
} else {
is_array = true;

Expand All @@ -229,7 +229,7 @@ impl Parse for MediaType {

parse_ident(
&group,
"unparseable MediaType, expected Ident within Bracket Group",
"unparseable MediaType, expected identifer within brackets",
)
}?;

Expand Down
69 changes: 33 additions & 36 deletions utoipa-gen/src/path.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
use std::{io::Error, str::FromStr};

use proc_macro2::{Group, Ident, TokenStream as TokenStream2};
use proc_macro_error::{abort_call_site, OptionExt, ResultExt};
use proc_macro_error::{abort, OptionExt, ResultExt};
use quote::{format_ident, quote, ToTokens};
use syn::{
bracketed,
parse::{Parse, ParseStream},
parse2,
punctuated::Punctuated,
LitStr, Token,
Token,
};

use crate::Deprecated;
use crate::{
component_type::ComponentType,
ext::{Argument, ArgumentIn},
};
use crate::{parse_utils, Deprecated};

use self::{
parameter::{Parameter, ParameterIn},
Expand Down Expand Up @@ -133,41 +133,30 @@ impl Parse for PathAttr {
loop {
let ident = input
.parse::<Ident>()
.expect_or_abort("failed to parse first ident");
let ident_name = &*ident.to_string();

let parse_lit_str = |input: &ParseStream, error_message: &str| -> String {
if input.peek(Token![=]) {
input.parse::<Token![=]>().unwrap();
}

input
.parse::<LitStr>()
.expect_or_abort(error_message)
.value()
};
.expect_or_abort("unparseable PatAttr, expected identifer");
let attribute = &*ident.to_string();

let parse_groups = |input: &ParseStream| {
if input.peek(Token![=]) {
input.parse::<Token![=]>().unwrap();
}
parse_utils::parse_next(input, || {
let content;
bracketed!(content in input);

let content;
bracketed!(content in input);

Punctuated::<Group, Token![,]>::parse_terminated(&content)
Punctuated::<Group, Token![,]>::parse_terminated(&content)
})
};

match ident_name {
match attribute {
"operation_id" => {
path_attr.operation_id = Some(parse_lit_str(
&input,
"expected literal string for operation id",
path_attr.operation_id = Some(parse_utils::parse_next_lit_str(
input,
"unparseable operation_id, expected literal string",
));
}
"path" => {
path_attr.path =
Some(parse_lit_str(&input, "expected literal string for path"));
path_attr.path = Some(parse_utils::parse_next_lit_str(
input,
"unparseable path, expected literal string",
));
}
"request_body" => {
if input.peek(Token![=]) {
Expand All @@ -177,17 +166,19 @@ impl Parse for PathAttr {
Some(input.parse::<RequestBodyAttr>().unwrap_or_abort());
}
"responses" => {
let groups = parse_groups(&input)
.expect_or_abort("expected responses to be group separated by comma (,)");
let groups = parse_groups(&input).expect_or_abort(
"unparseable responses, expected group separated by comma (,)",
);

path_attr.responses = groups
.into_iter()
.map(|group| syn::parse2::<Response>(group.stream()).unwrap_or_abort())
.collect::<Vec<_>>();
}
"params" => {
let groups = parse_groups(&input)
.expect_or_abort("expected parameters to be group separated by comma (,)");
let groups = parse_groups(&input).expect_or_abort(
"unparseable params, expected group separated by comma (,)",
);
path_attr.params = Some(
groups
.iter()
Expand All @@ -196,14 +187,20 @@ impl Parse for PathAttr {
)
}
"tag" => {
path_attr.tag = Some(parse_lit_str(&input, "expected literal string for tag"));
path_attr.tag = Some(parse_utils::parse_next_lit_str(
input,
"unparseable tag, expected literal string",
));
}
_ => {
// any other case it is expected to be path operation
if let Some(path_operation) =
ident_name.parse::<PathOperation>().into_iter().next()
attribute.parse::<PathOperation>().into_iter().next()
{
path_attr.path_operation = Some(path_operation)
} else {
let erro_msg = format!("unexpected identifier: {}, expected any of: operation_id, path, get, post, put, delete, options, head, patch, trace, connect, request_body, responses, params, tag", attribute);
return Err(syn::Error::new(ident.span(), erro_msg));
}
}
}
Expand Down Expand Up @@ -259,7 +256,7 @@ impl PathOperation {
pub fn from_ident(ident: &Ident) -> Self {
match ident.to_string().as_str().parse::<PathOperation>() {
Ok(operation) => operation,
Err(error) => abort_call_site!("{}", error),
Err(error) => abort!(ident.span(), format!("{}", error)),
}
}
}
Expand Down
Loading

0 comments on commit b544e7b

Please sign in to comment.