Skip to content

Commit

Permalink
Allowing utoipa/utoipa-swagger-ui successfully build on Windows and m…
Browse files Browse the repository at this point in the history
…ade path proc macro attribute more permissive (#830)

This PR makes the utoipa `path` attribute macro more premissive by allowing expressions in certain fields like `content-type`, `description` etc where only string was supported previously. This also makes the `utoipa-swagger-ui` build on Windows by way of using raw string in build code that was recently introduced. Detailed list of changes as follows:
* Made the literal raw to allow build.rs work properly on Windows
* Allow any AnyValue for example
* Allow arbitrary string expressions for description
* Made content_type a utoipa-gen::parse_util::Value
* Implemented Parse for utoipa-gen::parse_utils::Value and made content_type of reponse(s) of this type
* Provided more impls for utoipa-gen::parse_utils::Value and allowed response description be of this type
  • Loading branch information
JohnScience authored Jan 2, 2024
1 parent d437919 commit fe229e2
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 45 deletions.
31 changes: 23 additions & 8 deletions utoipa-gen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2793,6 +2793,28 @@ mod parse_utils {
Expr(Expr),
}

impl Value {
pub(crate) fn is_empty(&self) -> bool {
matches!(self, Self::LitStr(s) if s.value().is_empty())
}
}

impl Default for Value {
fn default() -> Self {
Self::LitStr(LitStr::new("", proc_macro2::Span::call_site()))
}
}

impl Parse for Value {
fn parse(input: ParseStream) -> syn::Result<Self> {
if input.peek(LitStr) {
Ok::<Value, Error>(Value::LitStr(input.parse::<LitStr>()?))
} else {
Ok(Value::Expr(input.parse::<Expr>()?))
}
}
}

impl ToTokens for Value {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Expand Down Expand Up @@ -2823,14 +2845,7 @@ mod parse_utils {
}

pub fn parse_next_literal_str_or_expr(input: ParseStream) -> syn::Result<Value> {
parse_next(input, || {
if input.peek(LitStr) {
Ok::<Value, Error>(Value::LitStr(input.parse::<LitStr>()?))
} else {
Ok(Value::Expr(input.parse::<Expr>()?))
}
})
.map_err(|error| {
parse_next(input, || Value::parse(input)).map_err(|error| {
syn::Error::new(
error.span(),
format!("expected literal string or expression argument: {error}"),
Expand Down
21 changes: 12 additions & 9 deletions utoipa-gen/src/path/request_body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ impl ToTokens for RequestBody<'_> {
#[cfg_attr(feature = "debug", derive(Debug))]
pub struct RequestBodyAttr<'r> {
content: Option<PathType<'r>>,
content_type: Option<String>,
description: Option<String>,
content_type: Option<parse_utils::Value>,
description: Option<parse_utils::Value>,
example: Option<AnyValue>,
examples: Option<Punctuated<Example, Comma>>,
}
Expand Down Expand Up @@ -115,15 +115,15 @@ impl Parse for RequestBodyAttr<'_> {
}
"content_type" => {
request_body_attr.content_type =
Some(parse_utils::parse_next_literal_str(&group)?)
Some(parse_utils::parse_next_literal_str_or_expr(&group)?)
}
"description" => {
request_body_attr.description =
Some(parse_utils::parse_next_literal_str(&group)?)
Some(parse_utils::parse_next_literal_str_or_expr(&group)?)
}
"example" => {
request_body_attr.example = Some(parse_utils::parse_next(&group, || {
AnyValue::parse_json(&group)
AnyValue::parse_any(&group)
})?)
}
"examples" => {
Expand Down Expand Up @@ -210,10 +210,13 @@ impl ToTokens for RequestBodyAttr<'_> {
PathType::MediaType(body_type) => {
let type_tree = body_type.as_type_tree();
let required: Required = (!type_tree.is_option()).into();
let content_type = self
.content_type
.as_deref()
.unwrap_or_else(|| type_tree.get_default_content_type());
let content_type = match &self.content_type {
Some(content_type) => content_type.to_token_stream(),
None => {
let content_type = type_tree.get_default_content_type();
quote!(#content_type)
}
};
tokens.extend(quote! {
utoipa::openapi::request_body::RequestBodyBuilder::new()
.content(#content_type, #content.build())
Expand Down
35 changes: 16 additions & 19 deletions utoipa-gen/src/path/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ impl<'r> From<(ResponseStatus, ResponseValue<'r>)> for ResponseTuple<'r> {

pub struct DeriveResponsesAttributes<T> {
derive_value: T,
description: String,
description: parse_utils::Value,
}

impl<'r> From<DeriveResponsesAttributes<DeriveIntoResponsesValue>> for ResponseValue<'r> {
Expand Down Expand Up @@ -198,9 +198,9 @@ impl<'r> From<DeriveResponsesAttributes<Option<DeriveToResponseValue>>> for Resp
#[derive(Default)]
#[cfg_attr(feature = "debug", derive(Debug))]
pub struct ResponseValue<'r> {
description: String,
description: parse_utils::Value,
response_type: Option<PathType<'r>>,
content_type: Option<Vec<String>>,
content_type: Option<Vec<parse_utils::Value>>,
headers: Vec<Header>,
example: Option<AnyValue>,
examples: Option<Punctuated<Example, Comma>>,
Expand All @@ -210,7 +210,7 @@ pub struct ResponseValue<'r> {
impl<'r> ResponseValue<'r> {
fn from_derive_to_response_value(
derive_value: DeriveToResponseValue,
description: String,
description: parse_utils::Value,
) -> Self {
Self {
description: if derive_value.description.is_empty() && !description.is_empty() {
Expand All @@ -228,7 +228,7 @@ impl<'r> ResponseValue<'r> {

fn from_derive_into_responses_value(
response_value: DeriveIntoResponsesValue,
description: String,
description: parse_utils::Value,
) -> Self {
ResponseValue {
description: if response_value.description.is_empty() && !description.is_empty() {
Expand Down Expand Up @@ -394,9 +394,9 @@ trait DeriveResponseValue: Parse {
#[derive(Default)]
#[cfg_attr(feature = "debug", derive(Debug))]
struct DeriveToResponseValue {
content_type: Option<Vec<String>>,
content_type: Option<Vec<parse_utils::Value>>,
headers: Vec<Header>,
description: String,
description: parse_utils::Value,
example: Option<(AnyValue, Ident)>,
examples: Option<(Punctuated<Example, Comma>, Ident)>,
}
Expand Down Expand Up @@ -467,9 +467,9 @@ impl Parse for DeriveToResponseValue {
#[derive(Default)]
struct DeriveIntoResponsesValue {
status: ResponseStatus,
content_type: Option<Vec<String>>,
content_type: Option<Vec<parse_utils::Value>>,
headers: Vec<Header>,
description: String,
description: parse_utils::Value,
example: Option<(AnyValue, Ident)>,
examples: Option<(Punctuated<Example, Comma>, Ident)>,
}
Expand Down Expand Up @@ -870,35 +870,32 @@ mod parse {
use syn::parse::ParseStream;
use syn::punctuated::Punctuated;
use syn::token::{Bracket, Comma};
use syn::{bracketed, parenthesized, LitStr, Result};
use syn::{bracketed, parenthesized, Result};

use crate::path::example::Example;
use crate::{parse_utils, AnyValue};

use super::Header;

#[inline]
pub(super) fn description(input: ParseStream) -> Result<String> {
parse_utils::parse_next_literal_str(input)
pub(super) fn description(input: ParseStream) -> Result<parse_utils::Value> {
parse_utils::parse_next_literal_str_or_expr(input)
}

#[inline]
pub(super) fn content_type(input: ParseStream) -> Result<Vec<String>> {
pub(super) fn content_type(input: ParseStream) -> Result<Vec<parse_utils::Value>> {
parse_utils::parse_next(input, || {
let look_content_type = input.lookahead1();
if look_content_type.peek(LitStr) {
Ok(vec![input.parse::<LitStr>()?.value()])
} else if look_content_type.peek(Bracket) {
if look_content_type.peek(Bracket) {
let content_types;
bracketed!(content_types in input);
Ok(
Punctuated::<LitStr, Comma>::parse_terminated(&content_types)?
Punctuated::<parse_utils::Value, Comma>::parse_terminated(&content_types)?
.into_iter()
.map(|lit| lit.value())
.collect(),
)
} else {
Err(look_content_type.error())
Ok(vec![input.parse::<parse_utils::Value>()?])
}
})
}
Expand Down
37 changes: 29 additions & 8 deletions utoipa-gen/src/path/response/derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use syn::{
use crate::component::schema::{EnumSchema, NamedStructSchema};
use crate::doc_comment::CommentAttributes;
use crate::path::{InlineType, PathType};
use crate::{Array, ResultExt};
use crate::{parse_utils, Array, ResultExt};

use super::{
Content, DeriveIntoResponsesValue, DeriveResponseValue, DeriveResponsesAttributes,
Expand Down Expand Up @@ -241,7 +241,10 @@ impl<'u> UnnamedStructResponse<'u> {
}
let mut derive_value = DeriveIntoResponsesValue::from_attributes(attributes)
.expect("`IntoResponses` must have `#[response(...)]` attribute");
let description = CommentAttributes::from_attributes(attributes).as_formatted_string();
let description = {
let s = CommentAttributes::from_attributes(attributes).as_formatted_string();
parse_utils::Value::LitStr(LitStr::new(&s, Span::call_site()))
};
let status_code = mem::take(&mut derive_value.status);

match (ref_response, to_response) {
Expand Down Expand Up @@ -294,7 +297,10 @@ impl NamedStructResponse<'_> {

let mut derive_value = DeriveIntoResponsesValue::from_attributes(attributes)
.expect("`IntoResponses` must have `#[response(...)]` attribute");
let description = CommentAttributes::from_attributes(attributes).as_formatted_string();
let description = {
let s = CommentAttributes::from_attributes(attributes).as_formatted_string();
parse_utils::Value::LitStr(LitStr::new(&s, Span::call_site()))
};
let status_code = mem::take(&mut derive_value.status);

let inline_schema = NamedStructSchema {
Expand Down Expand Up @@ -335,7 +341,10 @@ impl UnitStructResponse<'_> {
let mut derive_value = DeriveIntoResponsesValue::from_attributes(attributes)
.expect("`IntoResponses` must have `#[response(...)]` attribute");
let status_code = mem::take(&mut derive_value.status);
let description = CommentAttributes::from_attributes(attributes).as_formatted_string();
let description = {
let s = CommentAttributes::from_attributes(attributes).as_formatted_string();
parse_utils::Value::LitStr(LitStr::new(&s, Span::call_site()))
};

Self(
(
Expand All @@ -360,7 +369,10 @@ impl<'p> ToResponseNamedStructResponse<'p> {
);

let derive_value = DeriveToResponseValue::from_attributes(attributes);
let description = CommentAttributes::from_attributes(attributes).as_formatted_string();
let description = {
let s = CommentAttributes::from_attributes(attributes).as_formatted_string();
parse_utils::Value::LitStr(LitStr::new(&s, Span::call_site()))
};
let ty = Self::to_type(ident);

let inline_schema = NamedStructSchema {
Expand Down Expand Up @@ -402,7 +414,10 @@ impl<'u> ToResponseUnnamedStructResponse<'u> {
}
});
let derive_value = DeriveToResponseValue::from_attributes(attributes);
let description = CommentAttributes::from_attributes(attributes).as_formatted_string();
let description = {
let s = CommentAttributes::from_attributes(attributes).as_formatted_string();
parse_utils::Value::LitStr(LitStr::new(&s, Span::call_site()))
};

let is_inline = inner_attributes
.iter()
Expand Down Expand Up @@ -444,7 +459,10 @@ impl<'r> EnumResponse<'r> {
);

let ty = Self::to_type(ident);
let description = CommentAttributes::from_attributes(attributes).as_formatted_string();
let description = {
let s = CommentAttributes::from_attributes(attributes).as_formatted_string();
parse_utils::Value::LitStr(LitStr::new(&s, Span::call_site()))
};

let variants_content = variants
.into_iter()
Expand Down Expand Up @@ -572,7 +590,10 @@ impl ToResponseUnitStructResponse<'_> {
Self::validate_attributes(attributes, Self::has_no_field_attributes);

let derive_value = DeriveToResponseValue::from_attributes(attributes);
let description = CommentAttributes::from_attributes(attributes).as_formatted_string();
let description = {
let s = CommentAttributes::from_attributes(attributes).as_formatted_string();
parse_utils::Value::LitStr(LitStr::new(&s, Span::call_site()))
};
let response_value: ResponseValue = ResponseValue::from(DeriveResponsesAttributes {
derive_value,
description,
Expand Down
2 changes: 1 addition & 1 deletion utoipa-swagger-ui/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ fn write_embed_code(target_dir: &str, swagger_version: &str) {
r#"
// This file is auto-generated during compilation, do not modify
#[derive(RustEmbed)]
#[folder = "{}/{}/dist/"]
#[folder = r"{}/{}/dist/"]
struct SwaggerUiDist;
"#,
target_dir, swagger_version
Expand Down

0 comments on commit fe229e2

Please sign in to comment.