Skip to content

Commit fe229e2

Browse files
authored
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
1 parent d437919 commit fe229e2

File tree

5 files changed

+81
-45
lines changed

5 files changed

+81
-45
lines changed

utoipa-gen/src/lib.rs

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2793,6 +2793,28 @@ mod parse_utils {
27932793
Expr(Expr),
27942794
}
27952795

2796+
impl Value {
2797+
pub(crate) fn is_empty(&self) -> bool {
2798+
matches!(self, Self::LitStr(s) if s.value().is_empty())
2799+
}
2800+
}
2801+
2802+
impl Default for Value {
2803+
fn default() -> Self {
2804+
Self::LitStr(LitStr::new("", proc_macro2::Span::call_site()))
2805+
}
2806+
}
2807+
2808+
impl Parse for Value {
2809+
fn parse(input: ParseStream) -> syn::Result<Self> {
2810+
if input.peek(LitStr) {
2811+
Ok::<Value, Error>(Value::LitStr(input.parse::<LitStr>()?))
2812+
} else {
2813+
Ok(Value::Expr(input.parse::<Expr>()?))
2814+
}
2815+
}
2816+
}
2817+
27962818
impl ToTokens for Value {
27972819
fn to_tokens(&self, tokens: &mut TokenStream) {
27982820
match self {
@@ -2823,14 +2845,7 @@ mod parse_utils {
28232845
}
28242846

28252847
pub fn parse_next_literal_str_or_expr(input: ParseStream) -> syn::Result<Value> {
2826-
parse_next(input, || {
2827-
if input.peek(LitStr) {
2828-
Ok::<Value, Error>(Value::LitStr(input.parse::<LitStr>()?))
2829-
} else {
2830-
Ok(Value::Expr(input.parse::<Expr>()?))
2831-
}
2832-
})
2833-
.map_err(|error| {
2848+
parse_next(input, || Value::parse(input)).map_err(|error| {
28342849
syn::Error::new(
28352850
error.span(),
28362851
format!("expected literal string or expression argument: {error}"),

utoipa-gen/src/path/request_body.rs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ impl ToTokens for RequestBody<'_> {
7777
#[cfg_attr(feature = "debug", derive(Debug))]
7878
pub struct RequestBodyAttr<'r> {
7979
content: Option<PathType<'r>>,
80-
content_type: Option<String>,
81-
description: Option<String>,
80+
content_type: Option<parse_utils::Value>,
81+
description: Option<parse_utils::Value>,
8282
example: Option<AnyValue>,
8383
examples: Option<Punctuated<Example, Comma>>,
8484
}
@@ -115,15 +115,15 @@ impl Parse for RequestBodyAttr<'_> {
115115
}
116116
"content_type" => {
117117
request_body_attr.content_type =
118-
Some(parse_utils::parse_next_literal_str(&group)?)
118+
Some(parse_utils::parse_next_literal_str_or_expr(&group)?)
119119
}
120120
"description" => {
121121
request_body_attr.description =
122-
Some(parse_utils::parse_next_literal_str(&group)?)
122+
Some(parse_utils::parse_next_literal_str_or_expr(&group)?)
123123
}
124124
"example" => {
125125
request_body_attr.example = Some(parse_utils::parse_next(&group, || {
126-
AnyValue::parse_json(&group)
126+
AnyValue::parse_any(&group)
127127
})?)
128128
}
129129
"examples" => {
@@ -210,10 +210,13 @@ impl ToTokens for RequestBodyAttr<'_> {
210210
PathType::MediaType(body_type) => {
211211
let type_tree = body_type.as_type_tree();
212212
let required: Required = (!type_tree.is_option()).into();
213-
let content_type = self
214-
.content_type
215-
.as_deref()
216-
.unwrap_or_else(|| type_tree.get_default_content_type());
213+
let content_type = match &self.content_type {
214+
Some(content_type) => content_type.to_token_stream(),
215+
None => {
216+
let content_type = type_tree.get_default_content_type();
217+
quote!(#content_type)
218+
}
219+
};
217220
tokens.extend(quote! {
218221
utoipa::openapi::request_body::RequestBodyBuilder::new()
219222
.content(#content_type, #content.build())

utoipa-gen/src/path/response.rs

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ impl<'r> From<(ResponseStatus, ResponseValue<'r>)> for ResponseTuple<'r> {
168168

169169
pub struct DeriveResponsesAttributes<T> {
170170
derive_value: T,
171-
description: String,
171+
description: parse_utils::Value,
172172
}
173173

174174
impl<'r> From<DeriveResponsesAttributes<DeriveIntoResponsesValue>> for ResponseValue<'r> {
@@ -198,9 +198,9 @@ impl<'r> From<DeriveResponsesAttributes<Option<DeriveToResponseValue>>> for Resp
198198
#[derive(Default)]
199199
#[cfg_attr(feature = "debug", derive(Debug))]
200200
pub struct ResponseValue<'r> {
201-
description: String,
201+
description: parse_utils::Value,
202202
response_type: Option<PathType<'r>>,
203-
content_type: Option<Vec<String>>,
203+
content_type: Option<Vec<parse_utils::Value>>,
204204
headers: Vec<Header>,
205205
example: Option<AnyValue>,
206206
examples: Option<Punctuated<Example, Comma>>,
@@ -210,7 +210,7 @@ pub struct ResponseValue<'r> {
210210
impl<'r> ResponseValue<'r> {
211211
fn from_derive_to_response_value(
212212
derive_value: DeriveToResponseValue,
213-
description: String,
213+
description: parse_utils::Value,
214214
) -> Self {
215215
Self {
216216
description: if derive_value.description.is_empty() && !description.is_empty() {
@@ -228,7 +228,7 @@ impl<'r> ResponseValue<'r> {
228228

229229
fn from_derive_into_responses_value(
230230
response_value: DeriveIntoResponsesValue,
231-
description: String,
231+
description: parse_utils::Value,
232232
) -> Self {
233233
ResponseValue {
234234
description: if response_value.description.is_empty() && !description.is_empty() {
@@ -394,9 +394,9 @@ trait DeriveResponseValue: Parse {
394394
#[derive(Default)]
395395
#[cfg_attr(feature = "debug", derive(Debug))]
396396
struct DeriveToResponseValue {
397-
content_type: Option<Vec<String>>,
397+
content_type: Option<Vec<parse_utils::Value>>,
398398
headers: Vec<Header>,
399-
description: String,
399+
description: parse_utils::Value,
400400
example: Option<(AnyValue, Ident)>,
401401
examples: Option<(Punctuated<Example, Comma>, Ident)>,
402402
}
@@ -467,9 +467,9 @@ impl Parse for DeriveToResponseValue {
467467
#[derive(Default)]
468468
struct DeriveIntoResponsesValue {
469469
status: ResponseStatus,
470-
content_type: Option<Vec<String>>,
470+
content_type: Option<Vec<parse_utils::Value>>,
471471
headers: Vec<Header>,
472-
description: String,
472+
description: parse_utils::Value,
473473
example: Option<(AnyValue, Ident)>,
474474
examples: Option<(Punctuated<Example, Comma>, Ident)>,
475475
}
@@ -870,35 +870,32 @@ mod parse {
870870
use syn::parse::ParseStream;
871871
use syn::punctuated::Punctuated;
872872
use syn::token::{Bracket, Comma};
873-
use syn::{bracketed, parenthesized, LitStr, Result};
873+
use syn::{bracketed, parenthesized, Result};
874874

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

878878
use super::Header;
879879

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

885885
#[inline]
886-
pub(super) fn content_type(input: ParseStream) -> Result<Vec<String>> {
886+
pub(super) fn content_type(input: ParseStream) -> Result<Vec<parse_utils::Value>> {
887887
parse_utils::parse_next(input, || {
888888
let look_content_type = input.lookahead1();
889-
if look_content_type.peek(LitStr) {
890-
Ok(vec![input.parse::<LitStr>()?.value()])
891-
} else if look_content_type.peek(Bracket) {
889+
if look_content_type.peek(Bracket) {
892890
let content_types;
893891
bracketed!(content_types in input);
894892
Ok(
895-
Punctuated::<LitStr, Comma>::parse_terminated(&content_types)?
893+
Punctuated::<parse_utils::Value, Comma>::parse_terminated(&content_types)?
896894
.into_iter()
897-
.map(|lit| lit.value())
898895
.collect(),
899896
)
900897
} else {
901-
Err(look_content_type.error())
898+
Ok(vec![input.parse::<parse_utils::Value>()?])
902899
}
903900
})
904901
}

utoipa-gen/src/path/response/derive.rs

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use syn::{
1616
use crate::component::schema::{EnumSchema, NamedStructSchema};
1717
use crate::doc_comment::CommentAttributes;
1818
use crate::path::{InlineType, PathType};
19-
use crate::{Array, ResultExt};
19+
use crate::{parse_utils, Array, ResultExt};
2020

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

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

295298
let mut derive_value = DeriveIntoResponsesValue::from_attributes(attributes)
296299
.expect("`IntoResponses` must have `#[response(...)]` attribute");
297-
let description = CommentAttributes::from_attributes(attributes).as_formatted_string();
300+
let description = {
301+
let s = CommentAttributes::from_attributes(attributes).as_formatted_string();
302+
parse_utils::Value::LitStr(LitStr::new(&s, Span::call_site()))
303+
};
298304
let status_code = mem::take(&mut derive_value.status);
299305

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

340349
Self(
341350
(
@@ -360,7 +369,10 @@ impl<'p> ToResponseNamedStructResponse<'p> {
360369
);
361370

362371
let derive_value = DeriveToResponseValue::from_attributes(attributes);
363-
let description = CommentAttributes::from_attributes(attributes).as_formatted_string();
372+
let description = {
373+
let s = CommentAttributes::from_attributes(attributes).as_formatted_string();
374+
parse_utils::Value::LitStr(LitStr::new(&s, Span::call_site()))
375+
};
364376
let ty = Self::to_type(ident);
365377

366378
let inline_schema = NamedStructSchema {
@@ -402,7 +414,10 @@ impl<'u> ToResponseUnnamedStructResponse<'u> {
402414
}
403415
});
404416
let derive_value = DeriveToResponseValue::from_attributes(attributes);
405-
let description = CommentAttributes::from_attributes(attributes).as_formatted_string();
417+
let description = {
418+
let s = CommentAttributes::from_attributes(attributes).as_formatted_string();
419+
parse_utils::Value::LitStr(LitStr::new(&s, Span::call_site()))
420+
};
406421

407422
let is_inline = inner_attributes
408423
.iter()
@@ -444,7 +459,10 @@ impl<'r> EnumResponse<'r> {
444459
);
445460

446461
let ty = Self::to_type(ident);
447-
let description = CommentAttributes::from_attributes(attributes).as_formatted_string();
462+
let description = {
463+
let s = CommentAttributes::from_attributes(attributes).as_formatted_string();
464+
parse_utils::Value::LitStr(LitStr::new(&s, Span::call_site()))
465+
};
448466

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

574592
let derive_value = DeriveToResponseValue::from_attributes(attributes);
575-
let description = CommentAttributes::from_attributes(attributes).as_formatted_string();
593+
let description = {
594+
let s = CommentAttributes::from_attributes(attributes).as_formatted_string();
595+
parse_utils::Value::LitStr(LitStr::new(&s, Span::call_site()))
596+
};
576597
let response_value: ResponseValue = ResponseValue::from(DeriveResponsesAttributes {
577598
derive_value,
578599
description,

utoipa-swagger-ui/build.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ fn write_embed_code(target_dir: &str, swagger_version: &str) {
102102
r#"
103103
// This file is auto-generated during compilation, do not modify
104104
#[derive(RustEmbed)]
105-
#[folder = "{}/{}/dist/"]
105+
#[folder = r"{}/{}/dist/"]
106106
struct SwaggerUiDist;
107107
"#,
108108
target_dir, swagger_version

0 commit comments

Comments
 (0)