Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions utoipa-gen/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1287,11 +1287,7 @@ impl ComponentSchema {
quote! { <#rewritten_path as utoipa::ToSchema>::schemas(schemas) };

let description_tokens = description_stream.to_token_stream();
let schema = if default.is_some()
|| nullable
|| title.is_some()
|| !description_tokens.is_empty()
{
let schema = if nullable {
quote_spanned! {type_path.span()=>
utoipa::openapi::schema::OneOfBuilder::new()
#nullable_item
Expand All @@ -1300,6 +1296,20 @@ impl ComponentSchema {
#default_tokens
#description_stream
}
} else if default.is_some()
|| title.is_some()
|| !description_tokens.is_empty()
{
quote_spanned! {type_path.span()=>
utoipa::openapi::schema::AllOfBuilder::new()
.item(#items_tokens)
.item(
utoipa::openapi::schema::ObjectBuilder::new()
#title_tokens
#default_tokens
#description_stream
)
}
} else {
items_tokens
};
Expand Down Expand Up @@ -1358,7 +1368,7 @@ impl ComponentSchema {
// TODO: refs support `summary` field but currently there is no such field
// on schemas more over there is no way to distinct the `summary` from
// `description` of the ref. Should we consider supporting the summary?
let schema = if default.is_some() || nullable || title.is_some() {
let schema = if nullable {
composed_or_ref(quote_spanned! {type_path.span()=>
utoipa::openapi::schema::OneOfBuilder::new()
#nullable_item
Expand All @@ -1374,6 +1384,8 @@ impl ComponentSchema {
utoipa::openapi::schema::RefBuilder::new()
#description_stream
.ref_location_from_schema_name(#name_tokens)
#title_tokens
#default_tokens
})
};

Expand Down
69 changes: 69 additions & 0 deletions utoipa-gen/tests/schema_derive_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3273,3 +3273,72 @@ fn test_new_type_struct_pattern() {

assert_json_snapshot!(value);
}

#[test]
fn derive_option_ref_with_nullable_false() {
#[derive(ToSchema)]
#[allow(unused)]
struct RefType {
value: String,
}

let schema = api_doc! {
struct TestStruct {
// Should generate a direct $ref without oneOf
#[schema(nullable = false)]
optional_ref: Option<RefType>,

// For comparison - default Option behavior with implicit nullable = true
default_optional_ref: Option<RefType>,
}
};

assert_json_snapshot!(schema);
}

#[test]
fn derive_option_ref_with_nullable_false_and_default() {
#[derive(ToSchema)]
#[allow(unused)]
struct RefType {
value: String,
}

let schema = api_doc! {
struct TestStruct {
// Should generate a direct $ref without oneOf
#[schema(nullable = false)]
#[schema(default = json!({"value": "foo"}))]
optional_ref: Option<RefType>,

// For comparison - default Option behavior with implicit nullable = true
default_optional_ref: Option<RefType>,
}
};

assert_json_snapshot!(schema);
}

#[test]
fn derive_inline_option_ref_with_nullable_false_and_default() {
#[derive(ToSchema)]
#[allow(unused)]
struct RefType {
value: String,
}

let schema = api_doc! {
struct TestStruct {
// Should generate a direct object without oneOf
#[schema(nullable = false)]
#[schema(default = json!({"value": "foo"}))]
#[schema(inline = true)]
optional_ref: Option<RefType>,

// For comparison - default Option behavior with implicit nullable = true
default_optional_ref: Option<RefType>,
}
};

assert_json_snapshot!(schema);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
source: utoipa-gen/tests/schema_derive_test.rs
expression: schema
---
{
"properties": {
"default_optional_ref": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/components/schemas/RefType"
}
]
},
"optional_ref": {
"allOf": [
{
"properties": {
"value": {
"type": "string"
}
},
"required": [
"value"
],
"type": "object"
},
{
"default": {
"value": "foo"
},
"type": "object"
}
]
}
},
"type": "object"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
source: utoipa-gen/tests/schema_derive_test.rs
expression: schema
---
{
"properties": {
"default_optional_ref": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/components/schemas/RefType"
}
]
},
"optional_ref": {
"$ref": "#/components/schemas/RefType"
}
},
"type": "object"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
source: utoipa-gen/tests/schema_derive_test.rs
expression: schema
---
{
"properties": {
"default_optional_ref": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/components/schemas/RefType"
}
]
},
"optional_ref": {
"$ref": "#/components/schemas/RefType",
"default": {
"value": "foo"
}
}
},
"type": "object"
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ expression: enum_value
snapshot_kind: text
---
{
"oneOf": [
{
"$ref": "#/components/schemas/UnnamedEnum"
}
],
"$ref": "#/components/schemas/UnnamedEnum",
"title": "This is enum ref title"
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
---
source: utoipa-gen/tests/schema_derive_test.rs
expression: "&value"
snapshot_kind: text
---
{
"properties": {
Expand All @@ -17,8 +16,7 @@ snapshot_kind: text
"type": "object"
},
"with_description": {
"description": "This is description",
"oneOf": [
"allOf": [
{
"properties": {
"name": {
Expand All @@ -29,6 +27,10 @@ snapshot_kind: text
"name"
],
"type": "object"
},
{
"description": "This is description",
"type": "object"
}
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,10 @@ snapshot_kind: text
"type": "array"
},
"favorite_book": {
"$ref": "#/components/schemas/Book",
"default": {
"name": "Dune"
},
"oneOf": [
{
"$ref": "#/components/schemas/Book"
}
]
}
},
"leases": {
"additionalProperties": {
Expand Down
18 changes: 18 additions & 0 deletions utoipa/src/openapi/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1397,6 +1397,14 @@ builder! {
/// referenced component does not support summary field this does not have effect.
#[serde(skip_serializing_if = "String::is_empty", default)]
pub summary: String,

/// A default value which by default should override that of the referenced component.
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<Value>,

/// A title which by default should override that of the referenced component..
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
}
}

Expand Down Expand Up @@ -1451,6 +1459,16 @@ impl RefBuilder {
pub fn summary<S: Into<String>>(mut self, summary: S) -> Self {
set_value!(self summary summary.into())
}

/// Add or change default value for the object which by default should override that of the referenced component.
pub fn default(mut self, default: Option<Value>) -> Self {
set_value!(self default default)
}

/// Add or change the title for the object which by default should override that of the referenced component.
pub fn title<I: Into<String>>(mut self, title: Option<I>) -> Self {
set_value!(self title title.map(|title| title.into()))
}
}

impl From<RefBuilder> for RefOr<Schema> {
Expand Down
Loading