forked from loafle/openapi-generator-original
[core] Add x-is-free-form vendor extension (#6849)
This adds an x-is-free-form vendor extension to allow users to skip our "free-form" logic which would previously prevent object schemas with no properties to be considered "free-form". The previous behavior was due in part to Swagger Parser not exposing `additionalProperties: false` to us (which should be similar behavior to this extension). A free-form object is considered a dynamic object with any number of properties/types. DefaultGenerator does not allow for generation of models considered free-form. However, a base type with no properties and no additional properties is allowed by OpenAPI Specification and is meaningful in many languages (e.g. "marker interfaces" or abstract closed types).
This commit is contained in:
parent
54a6c791f7
commit
a97feaf533
@ -65,6 +65,8 @@ public class ModelUtils {
|
||||
// A vendor extension to track the value of the 'disallowAdditionalPropertiesIfNotPresent' CLI
|
||||
private static final String disallowAdditionalPropertiesIfNotPresent = "x-disallow-additional-properties-if-not-present";
|
||||
|
||||
private static final String freeFormExplicit = "x-is-free-form";
|
||||
|
||||
private static ObjectMapper JSON_MAPPER, YAML_MAPPER;
|
||||
|
||||
static {
|
||||
@ -672,25 +674,23 @@ public class ModelUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if the schema is a model with at least one property.
|
||||
* Check to see if the schema is a model
|
||||
*
|
||||
* @param schema potentially containing a '$ref'
|
||||
* @return true if it's a model with at least one properties
|
||||
*/
|
||||
public static boolean isModel(Schema schema) {
|
||||
if (schema == null) {
|
||||
// TODO: Is this message necessary? A null schema is not a model, so the result is correct.
|
||||
once(LOGGER).error("Schema cannot be null in isModel check");
|
||||
return false;
|
||||
}
|
||||
|
||||
// has at least one property
|
||||
if (schema.getProperties() != null && !schema.getProperties().isEmpty()) {
|
||||
// has properties
|
||||
if (null != schema.getProperties()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// composed schema is a model
|
||||
return schema instanceof ComposedSchema;
|
||||
// composed schema is a model, consider very simple ObjectSchema a model
|
||||
return schema instanceof ComposedSchema || schema instanceof ObjectSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -745,6 +745,16 @@ public class ModelUtils {
|
||||
// no properties
|
||||
if ((schema.getProperties() == null || schema.getProperties().isEmpty())) {
|
||||
Schema addlProps = getAdditionalProperties(openAPI, schema);
|
||||
|
||||
if (schema.getExtensions() != null && schema.getExtensions().containsKey(freeFormExplicit)) {
|
||||
// User has hard-coded vendor extension to handle free-form evaluation.
|
||||
boolean isFreeFormExplicit = Boolean.parseBoolean(String.valueOf(schema.getExtensions().get(freeFormExplicit)));
|
||||
if (!isFreeFormExplicit && addlProps != null && addlProps.getProperties() != null && !addlProps.getProperties().isEmpty()) {
|
||||
once(LOGGER).error(String.format(Locale.ROOT, "Potentially confusing usage of %s within model which defines additional properties", freeFormExplicit));
|
||||
}
|
||||
return isFreeFormExplicit;
|
||||
}
|
||||
|
||||
// additionalProperties not defined
|
||||
if (addlProps == null) {
|
||||
return true;
|
||||
|
@ -552,7 +552,7 @@ public class JavaClientCodegenTest {
|
||||
assertEquals(getCodegenOperation.authMethods.size(), 2);
|
||||
assertTrue(getCodegenOperation.authMethods.get(0).hasMore);
|
||||
Assert.assertFalse(getCodegenOperation.authMethods.get(1).hasMore);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFreeFormObjects() {
|
||||
@ -881,7 +881,7 @@ public class JavaClientCodegenTest {
|
||||
//single file
|
||||
"multipartSingleWithHttpInfo(File file)",
|
||||
"formParams.add(\"file\", new FileSystemResource(file));"
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -927,6 +927,35 @@ public class JavaClientCodegenTest {
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAllowModelWithNoProperties() throws Exception {
|
||||
File output = Files.createTempDirectory("test").toFile();
|
||||
|
||||
final CodegenConfigurator configurator = new CodegenConfigurator()
|
||||
.setGeneratorName("java")
|
||||
.setLibrary(JavaClientCodegen.OKHTTP_GSON)
|
||||
.setInputSpec("src/test/resources/2_0/emptyBaseModel.yaml")
|
||||
.setOutputDir(output.getAbsolutePath().replace("\\", "/"));
|
||||
|
||||
final ClientOptInput clientOptInput = configurator.toClientOptInput();
|
||||
DefaultGenerator generator = new DefaultGenerator();
|
||||
List<File> files = generator.opts(clientOptInput).generate();
|
||||
|
||||
Assert.assertEquals(files.size(), 47);
|
||||
TestUtils.ensureContainsFile(files, output, "src/main/java/org/openapitools/client/model/RealCommand.java");
|
||||
TestUtils.ensureContainsFile(files, output, "src/main/java/org/openapitools/client/model/Command.java");
|
||||
|
||||
validateJavaSourceFiles(files);
|
||||
|
||||
TestUtils.assertFileContains(Paths.get(output + "/src/main/java/org/openapitools/client/model/RealCommand.java"),
|
||||
"class RealCommand extends Command");
|
||||
|
||||
TestUtils.assertFileContains(Paths.get(output + "/src/main/java/org/openapitools/client/model/Command.java"),
|
||||
"class Command");
|
||||
|
||||
output.deleteOnExit();
|
||||
}
|
||||
|
||||
/**
|
||||
* See https://github.com/OpenAPITools/openapi-generator/issues/6715
|
||||
*/
|
||||
|
@ -128,6 +128,15 @@ public class ModelUtilsTest {
|
||||
Assert.assertEquals(unusedSchemas.size(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsModelAllowsEmptyBaseModel() {
|
||||
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/2_0/emptyBaseModel.yaml");
|
||||
Schema commandSchema = ModelUtils.getSchema(openAPI, "Command");
|
||||
|
||||
Assert.assertTrue(ModelUtils.isModel(commandSchema));
|
||||
Assert.assertFalse(ModelUtils.isFreeFormObject(openAPI, commandSchema));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReferencedSchema() {
|
||||
Schema otherObj = new ObjectSchema().addProperties("sprop", new StringSchema()).addProperties("iprop", new IntegerSchema());
|
||||
|
@ -0,0 +1,71 @@
|
||||
swagger: "2.0"
|
||||
info:
|
||||
title: Test Command model generation
|
||||
description: Test Command model generation
|
||||
version: 1.0.0
|
||||
host: localhost:8080
|
||||
schemes:
|
||||
- https
|
||||
definitions:
|
||||
Command:
|
||||
title: Command
|
||||
description: The base object for all command objects.
|
||||
type: object
|
||||
# Explicitly avoid treating as a "free-form" or dynamic object, resulting in classical languages as a class with no properties.
|
||||
x-is-free-form: false
|
||||
RealCommand:
|
||||
title: RealCommand
|
||||
description: The real command.
|
||||
allOf:
|
||||
- $ref: '#/definitions/Command'
|
||||
ApiError:
|
||||
description: The base object for API errors.
|
||||
type: object
|
||||
required:
|
||||
- code
|
||||
- message
|
||||
properties:
|
||||
code:
|
||||
description: The error code. Usually, it is the HTTP error code.
|
||||
type: string
|
||||
readOnly: true
|
||||
message:
|
||||
description: The error message.
|
||||
type: string
|
||||
readOnly: true
|
||||
title: ApiError
|
||||
parameters:
|
||||
b_real_command:
|
||||
name: real_command
|
||||
in: body
|
||||
description: A payload for executing a real command.
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/RealCommand'
|
||||
paths:
|
||||
/execute:
|
||||
post:
|
||||
produces: []
|
||||
operationId: executeRealCommand
|
||||
parameters:
|
||||
- name: real_command
|
||||
in: body
|
||||
description: A payload for executing a real command.
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/RealCommand'
|
||||
responses:
|
||||
'204':
|
||||
description: Successful request. No content returned.
|
||||
'400':
|
||||
description: Bad request.
|
||||
schema:
|
||||
$ref: '#/definitions/ApiError'
|
||||
'404':
|
||||
description: Not found.
|
||||
schema:
|
||||
$ref: '#/definitions/ApiError'
|
||||
default:
|
||||
description: Unknown error.
|
||||
schema:
|
||||
$ref: '#/definitions/ApiError'
|
Loading…
x
Reference in New Issue
Block a user