From fe229e2daa439ef26e10921f2bf68b37e3e58c55 Mon Sep 17 00:00:00 2001 From: Dmitrii - Demenev Date: Tue, 2 Jan 2024 13:27:34 -0700 Subject: [PATCH] Allowing utoipa/utoipa-swagger-ui successfully build on Windows and made 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 --- utoipa-gen/src/lib.rs | 31 +++++++++++++++------ utoipa-gen/src/path/request_body.rs | 21 ++++++++------- utoipa-gen/src/path/response.rs | 35 +++++++++++------------- utoipa-gen/src/path/response/derive.rs | 37 ++++++++++++++++++++------ utoipa-swagger-ui/build.rs | 2 +- 5 files changed, 81 insertions(+), 45 deletions(-) diff --git a/utoipa-gen/src/lib.rs b/utoipa-gen/src/lib.rs index d396f981..85f110b4 100644 --- a/utoipa-gen/src/lib.rs +++ b/utoipa-gen/src/lib.rs @@ -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 { + if input.peek(LitStr) { + Ok::(Value::LitStr(input.parse::()?)) + } else { + Ok(Value::Expr(input.parse::()?)) + } + } + } + impl ToTokens for Value { fn to_tokens(&self, tokens: &mut TokenStream) { match self { @@ -2823,14 +2845,7 @@ mod parse_utils { } pub fn parse_next_literal_str_or_expr(input: ParseStream) -> syn::Result { - parse_next(input, || { - if input.peek(LitStr) { - Ok::(Value::LitStr(input.parse::()?)) - } else { - Ok(Value::Expr(input.parse::()?)) - } - }) - .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}"), diff --git a/utoipa-gen/src/path/request_body.rs b/utoipa-gen/src/path/request_body.rs index 3ace6942..910cefd5 100644 --- a/utoipa-gen/src/path/request_body.rs +++ b/utoipa-gen/src/path/request_body.rs @@ -77,8 +77,8 @@ impl ToTokens for RequestBody<'_> { #[cfg_attr(feature = "debug", derive(Debug))] pub struct RequestBodyAttr<'r> { content: Option>, - content_type: Option, - description: Option, + content_type: Option, + description: Option, example: Option, examples: Option>, } @@ -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" => { @@ -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()) diff --git a/utoipa-gen/src/path/response.rs b/utoipa-gen/src/path/response.rs index 02a93b04..a4dc8489 100644 --- a/utoipa-gen/src/path/response.rs +++ b/utoipa-gen/src/path/response.rs @@ -168,7 +168,7 @@ impl<'r> From<(ResponseStatus, ResponseValue<'r>)> for ResponseTuple<'r> { pub struct DeriveResponsesAttributes { derive_value: T, - description: String, + description: parse_utils::Value, } impl<'r> From> for ResponseValue<'r> { @@ -198,9 +198,9 @@ impl<'r> From>> for Resp #[derive(Default)] #[cfg_attr(feature = "debug", derive(Debug))] pub struct ResponseValue<'r> { - description: String, + description: parse_utils::Value, response_type: Option>, - content_type: Option>, + content_type: Option>, headers: Vec
, example: Option, examples: Option>, @@ -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() { @@ -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() { @@ -394,9 +394,9 @@ trait DeriveResponseValue: Parse { #[derive(Default)] #[cfg_attr(feature = "debug", derive(Debug))] struct DeriveToResponseValue { - content_type: Option>, + content_type: Option>, headers: Vec
, - description: String, + description: parse_utils::Value, example: Option<(AnyValue, Ident)>, examples: Option<(Punctuated, Ident)>, } @@ -467,9 +467,9 @@ impl Parse for DeriveToResponseValue { #[derive(Default)] struct DeriveIntoResponsesValue { status: ResponseStatus, - content_type: Option>, + content_type: Option>, headers: Vec
, - description: String, + description: parse_utils::Value, example: Option<(AnyValue, Ident)>, examples: Option<(Punctuated, Ident)>, } @@ -870,7 +870,7 @@ 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}; @@ -878,27 +878,24 @@ mod parse { use super::Header; #[inline] - pub(super) fn description(input: ParseStream) -> Result { - parse_utils::parse_next_literal_str(input) + pub(super) fn description(input: ParseStream) -> Result { + parse_utils::parse_next_literal_str_or_expr(input) } #[inline] - pub(super) fn content_type(input: ParseStream) -> Result> { + pub(super) fn content_type(input: ParseStream) -> Result> { parse_utils::parse_next(input, || { let look_content_type = input.lookahead1(); - if look_content_type.peek(LitStr) { - Ok(vec![input.parse::()?.value()]) - } else if look_content_type.peek(Bracket) { + if look_content_type.peek(Bracket) { let content_types; bracketed!(content_types in input); Ok( - Punctuated::::parse_terminated(&content_types)? + Punctuated::::parse_terminated(&content_types)? .into_iter() - .map(|lit| lit.value()) .collect(), ) } else { - Err(look_content_type.error()) + Ok(vec![input.parse::()?]) } }) } diff --git a/utoipa-gen/src/path/response/derive.rs b/utoipa-gen/src/path/response/derive.rs index 43adb486..55769b1a 100644 --- a/utoipa-gen/src/path/response/derive.rs +++ b/utoipa-gen/src/path/response/derive.rs @@ -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, @@ -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) { @@ -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 { @@ -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( ( @@ -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 { @@ -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() @@ -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() @@ -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, diff --git a/utoipa-swagger-ui/build.rs b/utoipa-swagger-ui/build.rs index eb65cd81..19af11f1 100644 --- a/utoipa-swagger-ui/build.rs +++ b/utoipa-swagger-ui/build.rs @@ -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