forked from loafle/openapi-generator-original
Fix map and free form object detection issue in 3.1 spec (#17624)
* fix map issue in 3.1 spec * fix, add tests * update samples * update * manully fix spec * revert * fix rust model
This commit is contained in:
parent
c2ec0bafdf
commit
b2f622cd98
@ -329,6 +329,8 @@ public interface IJsonSchemaValidationProperties {
|
|||||||
setIsFreeFormObject(true);
|
setIsFreeFormObject(true);
|
||||||
// TODO: remove below later after updating generators to properly use isFreeFormObject
|
// TODO: remove below later after updating generators to properly use isFreeFormObject
|
||||||
setIsMap(true);
|
setIsMap(true);
|
||||||
|
} else if (ModelUtils.isMapSchema(p)) {
|
||||||
|
setIsMap(true);
|
||||||
} else if (ModelUtils.isTypeObjectSchema(p)) {
|
} else if (ModelUtils.isTypeObjectSchema(p)) {
|
||||||
setIsMap(true);
|
setIsMap(true);
|
||||||
}
|
}
|
||||||
|
@ -417,7 +417,16 @@ public class ModelUtils {
|
|||||||
* @return true if the specified schema is an Object schema.
|
* @return true if the specified schema is an Object schema.
|
||||||
*/
|
*/
|
||||||
public static boolean isTypeObjectSchema(Schema schema) {
|
public static boolean isTypeObjectSchema(Schema schema) {
|
||||||
return SchemaTypeUtil.OBJECT_TYPE.equals(schema.getType());
|
if (schema instanceof JsonSchema) { // 3.1 spec
|
||||||
|
if (schema.getTypes() != null && schema.getTypes().size() == 1) {
|
||||||
|
return SchemaTypeUtil.OBJECT_TYPE.equals(schema.getTypes().iterator().next());
|
||||||
|
} else {
|
||||||
|
// null type or multiple types, e.g. [string, integer]
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else { // 3.0.x or 2.0 spec
|
||||||
|
return SchemaTypeUtil.OBJECT_TYPE.equals(schema.getType());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -567,9 +576,14 @@ public class ModelUtils {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (schema instanceof MapSchema) ||
|
if (schema instanceof JsonSchema) { // 3.1 spec
|
||||||
(schema.getAdditionalProperties() instanceof Schema) ||
|
return ((schema.getAdditionalProperties() instanceof JsonSchema) ||
|
||||||
(schema.getAdditionalProperties() instanceof Boolean && (Boolean) schema.getAdditionalProperties());
|
(schema.getAdditionalProperties() instanceof Boolean && (Boolean) schema.getAdditionalProperties()));
|
||||||
|
} else { // 3.0 or 2.x spec
|
||||||
|
return (schema instanceof MapSchema) ||
|
||||||
|
(schema.getAdditionalProperties() instanceof Schema) ||
|
||||||
|
(schema.getAdditionalProperties() instanceof Boolean && (Boolean) schema.getAdditionalProperties());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -790,6 +804,31 @@ public class ModelUtils {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (schema instanceof JsonSchema) { // 3.1 spec
|
||||||
|
if (isComposedSchema(schema)) { // composed schema, e.g. allOf, oneOf, anyOf
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema.getProperties() != null && !schema.getProperties().isEmpty()) { // has properties
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema.getAdditionalProperties() instanceof Boolean && (Boolean) schema.getAdditionalProperties()) {
|
||||||
|
return true;
|
||||||
|
} else if (schema.getAdditionalProperties() instanceof JsonSchema) {
|
||||||
|
return true;
|
||||||
|
} else if (schema.getTypes() != null) {
|
||||||
|
if (schema.getTypes().size() == 1) { // types = [object]
|
||||||
|
return SchemaTypeUtil.OBJECT_TYPE.equals(schema.getTypes().iterator().next());
|
||||||
|
} else { // has more than 1 type, e.g. types = [integer, string]
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3.0.x spec or 2.x spec
|
||||||
// not free-form if allOf, anyOf, oneOf is not empty
|
// not free-form if allOf, anyOf, oneOf is not empty
|
||||||
if (isComposedSchema(schema)) {
|
if (isComposedSchema(schema)) {
|
||||||
List<Schema> interfaces = ModelUtils.getInterfaces(schema);
|
List<Schema> interfaces = ModelUtils.getInterfaces(schema);
|
||||||
|
@ -82,7 +82,7 @@ impl {{{classname}}} {
|
|||||||
pub fn new({{#requiredVars}}{{{name}}}: {{#isNullable}}Option<{{/isNullable}}{{#isEnum}}{{#isArray}}{{#uniqueItems}}std::collections::HashSet<{{/uniqueItems}}{{^uniqueItems}}Vec<{{/uniqueItems}}{{/isArray}}{{{enumName}}}{{#isArray}}>{{/isArray}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}}>{{/isNullable}}{{^-last}}, {{/-last}}{{/requiredVars}}) -> {{{classname}}} {
|
pub fn new({{#requiredVars}}{{{name}}}: {{#isNullable}}Option<{{/isNullable}}{{#isEnum}}{{#isArray}}{{#uniqueItems}}std::collections::HashSet<{{/uniqueItems}}{{^uniqueItems}}Vec<{{/uniqueItems}}{{/isArray}}{{{enumName}}}{{#isArray}}>{{/isArray}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}}>{{/isNullable}}{{^-last}}, {{/-last}}{{/requiredVars}}) -> {{{classname}}} {
|
||||||
{{{classname}}} {
|
{{{classname}}} {
|
||||||
{{#vars}}
|
{{#vars}}
|
||||||
{{{name}}}{{^required}}{{#isContainer}}{{#isArray}}: None{{/isArray}}{{#isMap}}: None{{/isMap}}{{^isArray}}{{^isMap}}{{#isNullable}}: None{{/isNullable}}{{/isMap}}{{/isArray}}{{/isContainer}}{{^isContainer}}: None{{/isContainer}}{{/required}}{{#required}}{{#isModel}}: {{^isNullable}}Box::new({{{name}}}){{/isNullable}}{{#isNullable}}if let Some(x) = {{{name}}} {Some(Box::new(x))} else {None}{{/isNullable}}{{/isModel}}{{/required}},
|
{{{name}}}{{^required}}{{#isFreeFormObject}}: None{{/isFreeFormObject}}{{#isContainer}}{{#isArray}}: None{{/isArray}}{{#isMap}}: None{{/isMap}}{{^isArray}}{{^isMap}}{{#isNullable}}: None{{/isNullable}}{{/isMap}}{{/isArray}}{{/isContainer}}{{^isContainer}}: None{{/isContainer}}{{/required}}{{#required}}{{#isModel}}: {{^isNullable}}Box::new({{{name}}}){{/isNullable}}{{#isNullable}}if let Some(x) = {{{name}}} {Some(Box::new(x))} else {None}{{/isNullable}}{{/isModel}}{{/required}},
|
||||||
{{/vars}}
|
{{/vars}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -294,4 +294,19 @@ public class ModelUtilsTest {
|
|||||||
String decoded = ModelUtils.getSimpleRef("#/components/~01%20Hallo~1Welt");
|
String decoded = ModelUtils.getSimpleRef("#/components/~01%20Hallo~1Welt");
|
||||||
Assert.assertEquals(decoded, "~1 Hallo/Welt");
|
Assert.assertEquals(decoded, "~1 Hallo/Welt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3.1 spec test
|
||||||
|
@Test
|
||||||
|
public void testIsMapSchema() {
|
||||||
|
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_1/schema.yaml");
|
||||||
|
Schema misc = ModelUtils.getSchema(openAPI, "Misc");
|
||||||
|
|
||||||
|
// test map
|
||||||
|
Assert.assertTrue(ModelUtils.isMapSchema((Schema) misc.getProperties().get("map1")));
|
||||||
|
|
||||||
|
// test free form object
|
||||||
|
Assert.assertTrue(ModelUtils.isFreeFormObject((Schema) misc.getProperties().get("free_form_object_1")));
|
||||||
|
Assert.assertTrue(ModelUtils.isFreeFormObject((Schema) misc.getProperties().get("free_form_object_2")));
|
||||||
|
Assert.assertTrue(ModelUtils.isFreeFormObject((Schema) misc.getProperties().get("free_form_object_3")));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,6 @@ openapi: 3.1.0
|
|||||||
info:
|
info:
|
||||||
title: double-option-hashmap
|
title: double-option-hashmap
|
||||||
version: 0.1.0
|
version: 0.1.0
|
||||||
license:
|
|
||||||
name: MIT
|
|
||||||
identifier: MIT
|
|
||||||
servers:
|
servers:
|
||||||
- url: http://api.example.xyz/v1
|
- url: http://api.example.xyz/v1
|
||||||
paths:
|
paths:
|
||||||
|
53
modules/openapi-generator/src/test/resources/3_1/schema.yaml
Normal file
53
modules/openapi-generator/src/test/resources/3_1/schema.yaml
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
openapi: 3.1.0
|
||||||
|
servers:
|
||||||
|
- url: 'http://petstore.swagger.io/v2'
|
||||||
|
info:
|
||||||
|
description: >-
|
||||||
|
This is a sample server Petstore server. For this sample, you can use the api key
|
||||||
|
`special-key` to test the authorization filters.
|
||||||
|
version: 1.0.0
|
||||||
|
title: OpenAPI Petstore
|
||||||
|
license:
|
||||||
|
name: Apache-2.0
|
||||||
|
url: 'https://www.apache.org/licenses/LICENSE-2.0.html'
|
||||||
|
tags:
|
||||||
|
- name: pet
|
||||||
|
description: Everything about your Pets
|
||||||
|
- name: store
|
||||||
|
description: Access to Petstore orders
|
||||||
|
- name: user
|
||||||
|
description: Operations about user
|
||||||
|
paths:
|
||||||
|
/dummy:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- dummy
|
||||||
|
summary: dummy operation
|
||||||
|
description: ''
|
||||||
|
operationId: dummy_operation
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: successful operation
|
||||||
|
'405':
|
||||||
|
description: Invalid input
|
||||||
|
externalDocs:
|
||||||
|
description: Find out more about Swagger
|
||||||
|
url: 'http://swagger.io'
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Misc:
|
||||||
|
description: Schema tests
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
free_form_object_1:
|
||||||
|
type: object
|
||||||
|
free_form_object_2:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
free_form_object_3:
|
||||||
|
type: object
|
||||||
|
additionalProperties: {}
|
||||||
|
map1:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
@ -3,13 +3,13 @@ name = "regression-16119-reqwest"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["OpenAPI Generator team and contributors"]
|
authors = ["OpenAPI Generator team and contributors"]
|
||||||
description = "No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)"
|
description = "No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)"
|
||||||
license = "MIT"
|
# Override this license by providing a License Object in the OpenAPI.
|
||||||
|
license = "Unlicense"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = "^1.0"
|
serde = "^1.0"
|
||||||
serde_derive = "^1.0"
|
serde_derive = "^1.0"
|
||||||
serde_with = "^2.0"
|
|
||||||
serde_json = "^1.0"
|
serde_json = "^1.0"
|
||||||
url = "^2.2"
|
url = "^2.2"
|
||||||
uuid = { version = "^1.0", features = ["serde", "v4"] }
|
uuid = { version = "^1.0", features = ["serde", "v4"] }
|
||||||
|
@ -13,8 +13,8 @@
|
|||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Parent {
|
pub struct Parent {
|
||||||
#[serde(rename = "child", default, with = "::serde_with::rust::double_option", skip_serializing_if = "Option::is_none")]
|
#[serde(rename = "child", skip_serializing_if = "Option::is_none")]
|
||||||
pub child: Option<Option<::std::collections::HashMap<String, serde_json::Value>>>,
|
pub child: Option<::std::collections::HashMap<String, serde_json::Value>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parent {
|
impl Parent {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user