Skip to content

Commit

Permalink
Add support for addtional tags via tags
Browse files Browse the repository at this point in the history
Add support for additonal tags via `tags` attribute on `utoipa::path`
macro. All the tags will be appended to the tags will be appended to the
tags of the operation. This means that the default tag (the
handler function name by default) will be added if not overridden with
the `tag` attribute.

```rust
 #[utoipa::path(
    get,
    tag = "some_tag",
    tags = ["addtional tag 1", "another one"]
 )]
 fn handler() {}
```
  • Loading branch information
juhaku committed May 7, 2024
1 parent 4168ff1 commit 1abf482
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 5 deletions.
5 changes: 4 additions & 1 deletion utoipa-gen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,9 @@ pub fn derive_to_schema(input: TokenStream) -> TokenStream {
/// this is derived from the handler that is given to [`OpenApi`][openapi]. If derive results empty str
/// then default value _`crate`_ is used instead.
///
/// * `tags = ["tag1", ...]` Can be used to group operations. Operations with same tag are grouped
/// toghether. Tags attribute can be used to add addtional _tags_ for the operation.
///
/// * `request_body = ... | request_body(...)` Defining request body indicates that the request is expecting request body within
/// the performed request.
///
Expand Down Expand Up @@ -2845,7 +2848,7 @@ mod parse_utils {
}

pub fn parse_next_literal_str_or_include_str(input: ParseStream) -> syn::Result<Str> {
Ok(parse_next(input, || input.parse::<Str>())?)
parse_next(input, || input.parse::<Str>())
}

pub fn parse_next_literal_str_or_expr(input: ParseStream) -> syn::Result<Value> {
Expand Down
25 changes: 23 additions & 2 deletions utoipa-gen/src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub struct PathAttr<'p> {
pub(super) path: Option<parse_utils::Value>,
operation_id: Option<Expr>,
tag: Option<parse_utils::Value>,
tags: Vec<parse_utils::Value>,
params: Vec<Parameter<'p>>,
security: Option<Array<'p, SecurityRequirementsAttr>>,
context_path: Option<parse_utils::Value>,
Expand Down Expand Up @@ -136,6 +137,15 @@ impl Parse for PathAttr<'_> {
"tag" => {
path_attr.tag = Some(parse_utils::parse_next_literal_str_or_expr(input)?);
}
"tags" => {
path_attr.tags = parse_utils::parse_next(input, || {
let tags;
syn::bracketed!(tags in input);
Punctuated::<parse_utils::Value, Token![,]>::parse_terminated(&tags)
})?
.into_iter()
.collect::<Vec<_>>();
}
"security" => {
let security;
parenthesized!(security in input);
Expand Down Expand Up @@ -309,6 +319,14 @@ impl<'p> ToTokens for Path<'p> {
help = "Did you define the #[utoipa::path(...)] over function?"
}
});
let tags = if !self.path_attr.tags.is_empty() {
let tags = self.path_attr.tags.as_slice().iter().collect::<Array<_>>();
Some(quote! {
.tags(Some(#tags))
})
} else {
None::<TokenStream2>
};
let tag = self
.path_attr
.tag
Expand Down Expand Up @@ -414,8 +432,8 @@ impl<'p> ToTokens for Path<'p> {
});
path_struct
};
tokens.extend(quote! {

tokens.extend(quote! {
impl utoipa::Path for #impl_for {
fn path() -> String {
#path_with_context_path
Expand All @@ -426,7 +444,10 @@ impl<'p> ToTokens for Path<'p> {
use std::iter::FromIterator;
utoipa::openapi::PathItem::new(
#path_operation,
#operation.tag(*[Some(#tag), default_tag, Some("crate")].iter()
#operation
#tags
.tag(
*[Some(#tag), default_tag, Some("crate")].iter()
.flatten()
.find(|t| !t.is_empty()).unwrap()
)
Expand Down
40 changes: 40 additions & 0 deletions utoipa-gen/tests/path_derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2038,3 +2038,43 @@ fn derive_path_with_tag_constant() {
})
);
}

#[test]
fn derive_path_with_multiple_tags() {
const TAG: &str = "mytag";
const ANOTHER: &str = "another";

#[utoipa::path(
get,
tag = TAG,
tags = ["one", "two", ANOTHER],
path = "/items",
responses(
(status = 200, description = "success response")
),
)]
#[allow(unused)]
fn get_items() -> String {
"".to_string()
}

let operation = test_api_fn_doc! {
get_items,
operation: get,
path: "/items"
};

assert_ne!(operation, Value::Null);
assert_json_eq!(
&operation,
json!({
"operationId": "get_items",
"responses": {
"200": {
"description": "success response",
},
},
"tags": ["one", "two","another","mytag"]
})
);
}
4 changes: 2 additions & 2 deletions utoipa/src/openapi/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,8 +336,8 @@ impl Operation {

impl OperationBuilder {
/// Add or change tags of the [`Operation`].
pub fn tags<I: IntoIterator<Item = String>>(mut self, tags: Option<I>) -> Self {
set_value!(self tags tags.map(|tags| tags.into_iter().collect()))
pub fn tags<I: IntoIterator<Item = V>, V: Into<String>>(mut self, tags: Option<I>) -> Self {
set_value!(self tags tags.map(|tags| tags.into_iter().map(Into::into).collect()))
}

/// Append tag to [`Operation`] tags.
Expand Down

0 comments on commit 1abf482

Please sign in to comment.