forked from loafle/openapi-generator-original
Issue 13043: Improved handling of recursive schemas (#13109)
* Issue 13043: Improved handling of recursive schemas Converted Exception to a warning when a Schema is doubly added to the includedSchemas list Broke recursion in a more reasonable place when generating example values * Addressed comments from PR
This commit is contained in:
@@ -1679,23 +1679,6 @@ public class PythonExperimentalClientCodegen extends AbstractPythonCodegen {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to isolate the call to add schemas to the includedSchemas list that will detect when
|
||||
* a schema is added multiple times.
|
||||
* @param includedSchemas List of includedSchemas from toExampleValueRecursive
|
||||
* @param schema the schema to add
|
||||
*/
|
||||
private void addSchemaToIncludedSchemaList(List<Schema> includedSchemas, Schema schema) {
|
||||
|
||||
// if the schema is in the list, then throw an exception.
|
||||
// this should never happen
|
||||
if(includedSchemas.contains(schema)) {
|
||||
throw new RuntimeException("Attempted to add a schema to the includedSchemas list twice");
|
||||
}
|
||||
|
||||
includedSchemas.add(schema);
|
||||
}
|
||||
|
||||
/***
|
||||
* Recursively generates string examples for schemas
|
||||
*
|
||||
@@ -1776,7 +1759,10 @@ public class PythonExperimentalClientCodegen extends AbstractPythonCodegen {
|
||||
Boolean hasProperties = (schema.getProperties() != null && !schema.getProperties().isEmpty());
|
||||
CodegenDiscriminator disc = createDiscriminator(modelName, schema, openAPI);
|
||||
if (ModelUtils.isComposedSchema(schema)) {
|
||||
addSchemaToIncludedSchemaList(includedSchemas, schema);
|
||||
if(includedSchemas.contains(schema)) {
|
||||
return "";
|
||||
}
|
||||
includedSchemas.add(schema);
|
||||
// complex composed object type schemas not yet handled and the code returns early
|
||||
if (hasProperties) {
|
||||
// what if this composed schema defined properties + allOf?
|
||||
@@ -1941,7 +1927,10 @@ public class PythonExperimentalClientCodegen extends AbstractPythonCodegen {
|
||||
ArraySchema arrayschema = (ArraySchema) schema;
|
||||
Schema itemSchema = arrayschema.getItems();
|
||||
String itemModelName = getModelName(itemSchema);
|
||||
addSchemaToIncludedSchemaList(includedSchemas, schema);
|
||||
if(includedSchemas.contains(schema)) {
|
||||
return "";
|
||||
}
|
||||
includedSchemas.add(schema);
|
||||
String itemExample = toExampleValueRecursive(itemModelName, itemSchema, objExample, indentationLevel + 1, "", exampleLine + 1, includedSchemas);
|
||||
if (StringUtils.isEmpty(itemExample) || cycleFound) {
|
||||
return fullPrefix + "[]" + closeChars;
|
||||
@@ -2018,7 +2007,11 @@ public class PythonExperimentalClientCodegen extends AbstractPythonCodegen {
|
||||
addPropPrefix = ensureQuotes(key) + ": ";
|
||||
}
|
||||
String addPropsModelName = getModelName(addPropsSchema);
|
||||
addSchemaToIncludedSchemaList(includedSchemas, schema);
|
||||
if(includedSchemas.contains(schema)) {
|
||||
return "";
|
||||
}
|
||||
includedSchemas.add(schema);
|
||||
|
||||
example = fullPrefix + "\n" + toExampleValueRecursive(addPropsModelName, addPropsSchema, addPropsExample, indentationLevel + 1, addPropPrefix, exampleLine + 1, includedSchemas) + ",\n" + closingIndentation + closeChars;
|
||||
} else {
|
||||
example = fullPrefix + closeChars;
|
||||
@@ -2035,14 +2028,18 @@ public class PythonExperimentalClientCodegen extends AbstractPythonCodegen {
|
||||
}
|
||||
|
||||
private String exampleForObjectModel(Schema schema, String fullPrefix, String closeChars, CodegenProperty discProp, int indentationLevel, int exampleLine, String closingIndentation, List<Schema> includedSchemas) {
|
||||
|
||||
Map<String, Schema> requiredAndOptionalProps = schema.getProperties();
|
||||
if (requiredAndOptionalProps == null || requiredAndOptionalProps.isEmpty()) {
|
||||
return fullPrefix + closeChars;
|
||||
}
|
||||
|
||||
String example = fullPrefix + "\n";
|
||||
if(includedSchemas.contains(schema)) {
|
||||
return "";
|
||||
}
|
||||
includedSchemas.add(schema);
|
||||
|
||||
addSchemaToIncludedSchemaList(includedSchemas, schema);
|
||||
String example = fullPrefix + "\n";
|
||||
|
||||
for (Map.Entry<String, Schema> entry : requiredAndOptionalProps.entrySet()) {
|
||||
String propName = entry.getKey();
|
||||
@@ -2055,14 +2052,25 @@ public class PythonExperimentalClientCodegen extends AbstractPythonCodegen {
|
||||
propExample = discProp.example;
|
||||
} else {
|
||||
propModelName = getModelName(propSchema);
|
||||
propExample = exampleFromStringOrArraySchema(propSchema, null, propName);
|
||||
propExample = exampleFromStringOrArraySchema(
|
||||
propSchema,
|
||||
null,
|
||||
propName);
|
||||
}
|
||||
|
||||
example += toExampleValueRecursive(propModelName, propSchema, propExample, indentationLevel + 1, propName + "=", exampleLine + 1, includedSchemas) + ",\n";
|
||||
example += toExampleValueRecursive(propModelName,
|
||||
propSchema,
|
||||
propExample,
|
||||
indentationLevel + 1,
|
||||
propName + "=",
|
||||
exampleLine + 1,
|
||||
includedSchemas) + ",\n";
|
||||
}
|
||||
|
||||
// TODO handle additionalProperties also
|
||||
example += closingIndentation + closeChars;
|
||||
return example;
|
||||
|
||||
}
|
||||
|
||||
private Object exampleFromStringOrArraySchema(Schema sc, Object currentExample, String propName) {
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.google.common.io.Resources;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.Operation;
|
||||
import io.swagger.v3.oas.models.media.*;
|
||||
import java.io.PrintWriter;
|
||||
import org.openapitools.codegen.*;
|
||||
import org.openapitools.codegen.languages.PythonExperimentalClientCodegen;
|
||||
import org.openapitools.codegen.utils.ModelUtils;
|
||||
@@ -28,6 +29,7 @@ import org.testng.annotations.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import org.testng.reporters.jq.Model;
|
||||
|
||||
@SuppressWarnings("static-method")
|
||||
public class PythonExperimentalClientTest {
|
||||
@@ -74,24 +76,42 @@ public class PythonExperimentalClientTest {
|
||||
Assert.assertFalse(openAPI.getExtensions().containsValue("x-original-swagger-version"));
|
||||
}
|
||||
|
||||
@Test(description = "tests GeoJson Examples")
|
||||
public void testRecursiveGeoJsonExample() throws IOException {
|
||||
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/issue_13043_recursive_model.yaml");
|
||||
@Test(description = "tests GeoJson Example for GeoJsonGeometry")
|
||||
public void testRecursiveGeoJsonExampleWhenTypeIsGeoJsonGeometry() throws IOException {
|
||||
|
||||
testEndpointExampleValue("/geojson",
|
||||
"src/test/resources/3_0/issue_13043_recursive_model.yaml",
|
||||
"3_0/issue_13043_recursive_model_expected_value.txt");
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test(description = "tests GeoJson Example for GeometryCollection")
|
||||
public void testRecursiveGeoJsonExampleWhenTypeIsGeometryCollection() throws IOException {
|
||||
|
||||
testEndpointExampleValue("/geojson_geometry_collection",
|
||||
"src/test/resources/3_0/issue_13043_recursive_model.yaml",
|
||||
"3_0/issue_13043_geometry_collection_expected_value.txt");
|
||||
|
||||
}
|
||||
|
||||
private void testEndpointExampleValue(String endpoint, String specFilePath, String expectedAnswerPath) throws IOException {
|
||||
final OpenAPI openAPI = TestUtils.parseFlattenSpec(specFilePath);
|
||||
final PythonExperimentalClientCodegen codegen = new PythonExperimentalClientCodegen();
|
||||
codegen.setOpenAPI(openAPI);
|
||||
|
||||
final Operation operation = openAPI.getPaths().get("/geojson").getPost();
|
||||
final Operation operation = openAPI.getPaths().get(endpoint).getPost();
|
||||
Schema schema = ModelUtils.getSchemaFromRequestBody(operation.getRequestBody());
|
||||
String exampleValue = codegen.toExampleValue(schema, null);
|
||||
|
||||
// uncomment if you need to regenerate the expected value
|
||||
// PrintWriter printWriter = new PrintWriter("src/test/resources/3_0/issue_8052_recursive_model_expected_value.txt");
|
||||
// printWriter.write(exampleValue);
|
||||
// printWriter.close();
|
||||
// org.junit.Assert.assertTrue(false);
|
||||
// uncomment if you need to regenerate the expected value
|
||||
// PrintWriter printWriter = new PrintWriter("src/test/resources/" + expectedAnswerPath);
|
||||
// printWriter.write(exampleValue);
|
||||
// printWriter.close();
|
||||
// org.junit.Assert.assertTrue(false);
|
||||
|
||||
String expectedValue = Resources.toString(
|
||||
Resources.getResource("3_0/issue_13043_recursive_model_expected_value.txt"),
|
||||
Resources.getResource(expectedAnswerPath),
|
||||
StandardCharsets.UTF_8);
|
||||
expectedValue = expectedValue.replaceAll("\\r\\n", "\n");
|
||||
Assert.assertEquals(exampleValue.trim(), expectedValue.trim());
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
GeometryCollection(
|
||||
type="GeometryCollection",
|
||||
geometries=[],
|
||||
)
|
||||
@@ -26,6 +26,26 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeoJsonGeometry'
|
||||
parameters: []
|
||||
/geojson_geometry_collection:
|
||||
post:
|
||||
summary: Add a GeoJson GeometryCollection Object
|
||||
operationId: post-geojson-geometry-collection
|
||||
responses:
|
||||
'201':
|
||||
description: Created
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
description: GeoJson ID
|
||||
'400':
|
||||
description: Bad Request
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GeometryCollection'
|
||||
parameters: []
|
||||
components:
|
||||
schemas:
|
||||
GeoJsonGeometry:
|
||||
|
||||
Reference in New Issue
Block a user