From 7dbfc631515c3d2dc8cc17ba998f8823cd7562cf Mon Sep 17 00:00:00 2001 From: Juha Kukkonen Date: Thu, 3 Oct 2024 16:31:07 +0300 Subject: [PATCH] Fix `as` attribute path format (#1080) Prior to this commit the `as` attribute produced invalid path with line break within the name and some colons not being replaced. This commit will fix this where now as definition like following will get serialized correctly to JSON as shown below. ```rust #[derive(ToSchema)] #[schema(as = types::calculation::calculation_assembly_cost::v1::CalculationAssemblyCostResponse)] pub struct CalculationAssemblyCostResponse { #[schema(value_type = uuid::Uuid)] pub id: String, } ``` Will serialize to: ```json "schema": { "$ref": "#/components/schemas/types.calculation.calculation_assembly_cost.v1.CalculationAssemblyCostResponse" }, ``` Fixes #1019 --- utoipa-gen/CHANGELOG.md | 3 +- .../src/component/features/attributes.rs | 16 +++++++ utoipa-gen/src/component/schema.rs | 18 +------- utoipa-gen/tests/path_derive.rs | 45 +++++++++++++++++++ 4 files changed, 65 insertions(+), 17 deletions(-) diff --git a/utoipa-gen/CHANGELOG.md b/utoipa-gen/CHANGELOG.md index 4a6eeadf..e0020ed2 100644 --- a/utoipa-gen/CHANGELOG.md +++ b/utoipa-gen/CHANGELOG.md @@ -4,6 +4,7 @@ ### Added +* Add `bound` attribute for customizing generic impl bounds. (https://github.com/juhaku/utoipa/pull/1079) * Add auto collect schemas for utoipa-axum (https://github.com/juhaku/utoipa/pull/1072) * Add global config for `utiopa` (https://github.com/juhaku/utoipa/pull/1048) * Add support for `links` in `#[utoipa::path]` (https://github.com/juhaku/utoipa/pull/1047) @@ -20,10 +21,10 @@ * Add support for description and summary overriding (https://github.com/juhaku/utoipa/pull/948) * Add nest `OpenApi` support (https://github.com/juhaku/utoipa/pull/930) * Add support for additional tags via `tags` (https://github.com/juhaku/utoipa/pull/916) -* Add `bound` attribute for customizing generic impl bounds. (https://github.com/juhaku/utoipa/pull/1079) ### Fixed +* Fix `as` attribute path format (https://github.com/juhaku/utoipa/pull/1080) * Fix allow response `content_type` without schema (https://github.com/juhaku/utoipa/pull/1073) * Fix negative value parsing on schema attributes (https://github.com/juhaku/utoipa/pull/1031) * Fix parameter inline for tuple path params (https://github.com/juhaku/utoipa/pull/1014) diff --git a/utoipa-gen/src/component/features/attributes.rs b/utoipa-gen/src/component/features/attributes.rs index ca5c45d1..ae17aa03 100644 --- a/utoipa-gen/src/component/features/attributes.rs +++ b/utoipa-gen/src/component/features/attributes.rs @@ -663,6 +663,22 @@ impl_feature! { pub struct As(pub TypePath); } +impl As { + /// Returns this `As` attribute type path formatted as string supported by OpenAPI spec whereas + /// double colons (::) are replaced with dot (.). + pub fn to_schema_formatted_string(&self) -> String { + // See: https://github.com/juhaku/utoipa/pull/187#issuecomment-1173101405 + // :: are not officially supported in the spec + self.0 + .path + .segments + .iter() + .map(|segment| segment.ident.to_string()) + .collect::>() + .join(".") + } +} + impl Parse for As { fn parse(input: ParseStream, _: Ident) -> syn::Result where diff --git a/utoipa-gen/src/component/schema.rs b/utoipa-gen/src/component/schema.rs index 07a23e24..a9362935 100644 --- a/utoipa-gen/src/component/schema.rs +++ b/utoipa-gen/src/component/schema.rs @@ -4,7 +4,7 @@ use proc_macro2::{Ident, TokenStream}; use quote::{quote, ToTokens}; use syn::{ parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, Data, Field, - Fields, FieldsNamed, FieldsUnnamed, Generics, Path, PathArguments, Variant, + Fields, FieldsNamed, FieldsUnnamed, Generics, Variant, }; use crate::{ @@ -109,7 +109,7 @@ impl ToTokensDiagnostics for Schema<'_> { ); let name = if let Some(schema_as) = variant.get_schema_as() { - format_path_ref(&schema_as.0.path) + schema_as.to_schema_formatted_string() } else { ident.to_string() }; @@ -866,17 +866,3 @@ impl ToTokensDiagnostics for Property { Ok(()) } } - -/// Reformat a path reference string that was generated using [`quote`] to be used as a nice compact schema reference, -/// by removing spaces between colon punctuation and `::` and the path segments. -pub(crate) fn format_path_ref(path: &Path) -> String { - let mut path = path.clone(); - - // Generics and path arguments are unsupported - if let Some(last_segment) = path.segments.last_mut() { - last_segment.arguments = PathArguments::None; - } - // :: are not officially supported in the spec - // See: https://github.com/juhaku/utoipa/pull/187#issuecomment-1173101405 - path.to_token_stream().to_string().replace(" :: ", ".") -} diff --git a/utoipa-gen/tests/path_derive.rs b/utoipa-gen/tests/path_derive.rs index ebd6491d..99113dc3 100644 --- a/utoipa-gen/tests/path_derive.rs +++ b/utoipa-gen/tests/path_derive.rs @@ -7,6 +7,7 @@ use serde_json::{json, Value}; use std::collections::HashMap; use utoipa::openapi::RefOr; use utoipa::openapi::{Object, ObjectBuilder}; +use utoipa::Path; use utoipa::{ openapi::{Response, ResponseBuilder, ResponsesBuilder}, IntoParams, IntoResponses, OpenApi, ToSchema, @@ -2696,3 +2697,47 @@ fn derive_path_test_collect_generic_request_body() { }) ); } + +#[test] +fn path_derive_with_body_ref_using_as_attribute_schema() { + #![allow(unused)] + + #[derive(Serialize, serde::Deserialize, Debug, Clone, ToSchema)] + #[schema(as = types::calculation::calculation_assembly_cost::v1::CalculationAssemblyCostResponse)] + pub struct CalculationAssemblyCostResponse { + #[schema(value_type = uuid::Uuid)] + pub id: String, + } + + #[utoipa::path( + get, + path = "/calculations/assembly-costs", + responses( + (status = 200, description = "Get calculated cost of an assembly.", + body = CalculationAssemblyCostResponse) + ), + )] + async fn handler() {} + + let operation = __path_handler::operation(); + let operation = serde_json::to_value(&operation).expect("operation is JSON serializable"); + + assert_json_eq!( + operation, + json!({ + "operationId": "handler", + "responses": { + "200": { + "description": "Get calculated cost of an assembly.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/types.calculation.calculation_assembly_cost.v1.CalculationAssemblyCostResponse" + }, + } + } + } + } + }) + ); +}