forked from loafle/openapi-generator-original
Fixes issue 8052: Stackoverflow in toExampleValue() for python client (#8054)
Added a Set<String> in toExampleValueRecursive() to keep track of which models we have generated to avoid an infinite recursion for recursive models. An example of a recursive model would be a GeoJson GeometryCollection. Co-authored-by: Frank Levine <frank.levine@blacklynx.tech>
This commit is contained in:
parent
9889e5dfba
commit
fd02bc3d84
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package org.openapitools.codegen.languages;
|
package org.openapitools.codegen.languages;
|
||||||
|
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
import io.swagger.v3.core.util.Json;
|
import io.swagger.v3.core.util.Json;
|
||||||
import io.swagger.v3.oas.models.media.*;
|
import io.swagger.v3.oas.models.media.*;
|
||||||
import io.swagger.v3.oas.models.media.ArraySchema;
|
import io.swagger.v3.oas.models.media.ArraySchema;
|
||||||
@ -879,7 +880,7 @@ public class PythonClientCodegen extends PythonLegacyClientCodegen {
|
|||||||
|
|
||||||
public String toExampleValue(Schema schema, Object objExample) {
|
public String toExampleValue(Schema schema, Object objExample) {
|
||||||
String modelName = getModelName(schema);
|
String modelName = getModelName(schema);
|
||||||
return toExampleValueRecursive(modelName, schema, objExample, 1, "", 0);
|
return toExampleValueRecursive(modelName, schema, objExample, 1, "", 0, Sets.newHashSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Boolean simpleStringSchema(Schema schema) {
|
private Boolean simpleStringSchema(Schema schema) {
|
||||||
@ -925,9 +926,12 @@ public class PythonClientCodegen extends PythonLegacyClientCodegen {
|
|||||||
* ModelName( line 0
|
* ModelName( line 0
|
||||||
* some_property='some_property_example' line 1
|
* some_property='some_property_example' line 1
|
||||||
* ) line 2
|
* ) line 2
|
||||||
|
* @param seenSchemas This set contains all the schemas passed into the recursive function. It is used to check
|
||||||
|
* if a schema was already passed into the function and breaks the infinite recursive loop. The
|
||||||
|
* only schemas that are not added are ones that contain $ref != null
|
||||||
* @return the string example
|
* @return the string example
|
||||||
*/
|
*/
|
||||||
private String toExampleValueRecursive(String modelName, Schema schema, Object objExample, int indentationLevel, String prefix, Integer exampleLine) {
|
private String toExampleValueRecursive(String modelName, Schema schema, Object objExample, int indentationLevel, String prefix, Integer exampleLine, Set<Schema> seenSchemas) {
|
||||||
final String indentionConst = " ";
|
final String indentionConst = " ";
|
||||||
String currentIndentation = "";
|
String currentIndentation = "";
|
||||||
String closingIndentation = "";
|
String closingIndentation = "";
|
||||||
@ -951,6 +955,27 @@ public class PythonClientCodegen extends PythonLegacyClientCodegen {
|
|||||||
if (objExample != null) {
|
if (objExample != null) {
|
||||||
example = objExample.toString();
|
example = objExample.toString();
|
||||||
}
|
}
|
||||||
|
// checks if the current schema has already been passed in. If so, breaks the current recursive pass
|
||||||
|
if (seenSchemas.contains(schema)){
|
||||||
|
if (modelName != null) {
|
||||||
|
return fullPrefix + modelName + closeChars;
|
||||||
|
} else {
|
||||||
|
// this is a recursive schema
|
||||||
|
// need to add a reasonable example to avoid
|
||||||
|
// infinite recursion
|
||||||
|
if(ModelUtils.isNullable(schema)) {
|
||||||
|
// if the schema is nullable, then 'None' is a valid value
|
||||||
|
return fullPrefix + "None" + closeChars;
|
||||||
|
} else if(ModelUtils.isArraySchema(schema)) {
|
||||||
|
// the schema is an array, add an empty array
|
||||||
|
return fullPrefix + "[]" + closeChars;
|
||||||
|
} else {
|
||||||
|
// the schema is an object, make an empty object
|
||||||
|
return fullPrefix + "{}" + closeChars;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (null != schema.get$ref()) {
|
if (null != schema.get$ref()) {
|
||||||
Map<String, Schema> allDefinitions = ModelUtils.getSchemas(this.openAPI);
|
Map<String, Schema> allDefinitions = ModelUtils.getSchemas(this.openAPI);
|
||||||
String ref = ModelUtils.getSimpleRef(schema.get$ref());
|
String ref = ModelUtils.getSimpleRef(schema.get$ref());
|
||||||
@ -960,7 +985,7 @@ public class PythonClientCodegen extends PythonLegacyClientCodegen {
|
|||||||
return fullPrefix + "None" + closeChars;
|
return fullPrefix + "None" + closeChars;
|
||||||
}
|
}
|
||||||
String refModelName = getModelName(schema);
|
String refModelName = getModelName(schema);
|
||||||
return toExampleValueRecursive(refModelName, refSchema, objExample, indentationLevel, prefix, exampleLine);
|
return toExampleValueRecursive(refModelName, refSchema, objExample, indentationLevel, prefix, exampleLine, seenSchemas);
|
||||||
} else if (ModelUtils.isNullType(schema) || isAnyTypeSchema(schema)) {
|
} else if (ModelUtils.isNullType(schema) || isAnyTypeSchema(schema)) {
|
||||||
// The 'null' type is allowed in OAS 3.1 and above. It is not supported by OAS 3.0.x,
|
// The 'null' type is allowed in OAS 3.1 and above. It is not supported by OAS 3.0.x,
|
||||||
// though this tooling supports it.
|
// though this tooling supports it.
|
||||||
@ -1058,7 +1083,8 @@ public class PythonClientCodegen extends PythonLegacyClientCodegen {
|
|||||||
ArraySchema arrayschema = (ArraySchema) schema;
|
ArraySchema arrayschema = (ArraySchema) schema;
|
||||||
Schema itemSchema = arrayschema.getItems();
|
Schema itemSchema = arrayschema.getItems();
|
||||||
String itemModelName = getModelName(itemSchema);
|
String itemModelName = getModelName(itemSchema);
|
||||||
example = fullPrefix + "[" + "\n" + toExampleValueRecursive(itemModelName, itemSchema, objExample, indentationLevel + 1, "", exampleLine + 1) + ",\n" + closingIndentation + "]" + closeChars;
|
seenSchemas.add(schema);
|
||||||
|
example = fullPrefix + "[" + "\n" + toExampleValueRecursive(itemModelName, itemSchema, objExample, indentationLevel + 1, "", exampleLine + 1, seenSchemas) + ",\n" + closingIndentation + "]" + closeChars;
|
||||||
return example;
|
return example;
|
||||||
} else if (ModelUtils.isMapSchema(schema)) {
|
} else if (ModelUtils.isMapSchema(schema)) {
|
||||||
if (modelName == null) {
|
if (modelName == null) {
|
||||||
@ -1080,7 +1106,8 @@ public class PythonClientCodegen extends PythonLegacyClientCodegen {
|
|||||||
addPropPrefix = ensureQuotes(key) + ": ";
|
addPropPrefix = ensureQuotes(key) + ": ";
|
||||||
}
|
}
|
||||||
String addPropsModelName = getModelName(addPropsSchema);
|
String addPropsModelName = getModelName(addPropsSchema);
|
||||||
example = fullPrefix + "\n" + toExampleValueRecursive(addPropsModelName, addPropsSchema, addPropsExample, indentationLevel + 1, addPropPrefix, exampleLine + 1) + ",\n" + closingIndentation + closeChars;
|
seenSchemas.add(schema);
|
||||||
|
example = fullPrefix + "\n" + toExampleValueRecursive(addPropsModelName, addPropsSchema, addPropsExample, indentationLevel + 1, addPropPrefix, exampleLine + 1, seenSchemas) + ",\n" + closingIndentation + closeChars;
|
||||||
} else {
|
} else {
|
||||||
example = fullPrefix + closeChars;
|
example = fullPrefix + closeChars;
|
||||||
}
|
}
|
||||||
@ -1103,7 +1130,12 @@ public class PythonClientCodegen extends PythonLegacyClientCodegen {
|
|||||||
return fullPrefix + closeChars;
|
return fullPrefix + closeChars;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return exampleForObjectModel(schema, fullPrefix, closeChars, null, indentationLevel, exampleLine, closingIndentation);
|
// Adds schema to seenSchemas before running example model function. romoves schema after running
|
||||||
|
// the function. It also doesnt keep track of any schemas within the ObjectModel.
|
||||||
|
seenSchemas.add(schema);
|
||||||
|
String exampleForObjectModel = exampleForObjectModel(schema, fullPrefix, closeChars, null, indentationLevel, exampleLine, closingIndentation, seenSchemas);
|
||||||
|
seenSchemas.remove(schema);
|
||||||
|
return exampleForObjectModel;
|
||||||
} else if (ModelUtils.isComposedSchema(schema)) {
|
} else if (ModelUtils.isComposedSchema(schema)) {
|
||||||
// TODO add examples for composed schema models without discriminators
|
// TODO add examples for composed schema models without discriminators
|
||||||
|
|
||||||
@ -1117,7 +1149,12 @@ public class PythonClientCodegen extends PythonLegacyClientCodegen {
|
|||||||
CodegenProperty cp = new CodegenProperty();
|
CodegenProperty cp = new CodegenProperty();
|
||||||
cp.setName(disc.getPropertyName());
|
cp.setName(disc.getPropertyName());
|
||||||
cp.setExample(discPropNameValue);
|
cp.setExample(discPropNameValue);
|
||||||
return exampleForObjectModel(modelSchema, fullPrefix, closeChars, cp, indentationLevel, exampleLine, closingIndentation);
|
// Adds schema to seenSchemas before running example model function. romoves schema after running
|
||||||
|
// the function. It also doesnt keep track of any schemas within the ObjectModel.
|
||||||
|
seenSchemas.add(modelSchema);
|
||||||
|
String exampleForObjectModel = exampleForObjectModel(modelSchema, fullPrefix, closeChars, cp, indentationLevel, exampleLine, closingIndentation, seenSchemas);
|
||||||
|
seenSchemas.remove(modelSchema);
|
||||||
|
return exampleForObjectModel;
|
||||||
} else {
|
} else {
|
||||||
return fullPrefix + closeChars;
|
return fullPrefix + closeChars;
|
||||||
}
|
}
|
||||||
@ -1130,7 +1167,7 @@ public class PythonClientCodegen extends PythonLegacyClientCodegen {
|
|||||||
return example;
|
return example;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String exampleForObjectModel(Schema schema, String fullPrefix, String closeChars, CodegenProperty discProp, int indentationLevel, int exampleLine, String closingIndentation) {
|
private String exampleForObjectModel(Schema schema, String fullPrefix, String closeChars, CodegenProperty discProp, int indentationLevel, int exampleLine, String closingIndentation, Set<Schema> seenSchemas) {
|
||||||
Map<String, Schema> requiredAndOptionalProps = schema.getProperties();
|
Map<String, Schema> requiredAndOptionalProps = schema.getProperties();
|
||||||
if (requiredAndOptionalProps == null || requiredAndOptionalProps.isEmpty()) {
|
if (requiredAndOptionalProps == null || requiredAndOptionalProps.isEmpty()) {
|
||||||
return fullPrefix + closeChars;
|
return fullPrefix + closeChars;
|
||||||
@ -1150,7 +1187,7 @@ public class PythonClientCodegen extends PythonLegacyClientCodegen {
|
|||||||
propModelName = getModelName(propSchema);
|
propModelName = getModelName(propSchema);
|
||||||
propExample = exampleFromStringOrArraySchema(propSchema, null, propName);
|
propExample = exampleFromStringOrArraySchema(propSchema, null, propName);
|
||||||
}
|
}
|
||||||
example += toExampleValueRecursive(propModelName, propSchema, propExample, indentationLevel + 1, propName + "=", exampleLine + 1) + ",\n";
|
example += toExampleValueRecursive(propModelName, propSchema, propExample, indentationLevel + 1, propName + "=", exampleLine + 1, seenSchemas) + ",\n";
|
||||||
}
|
}
|
||||||
// TODO handle additionalProperties also
|
// TODO handle additionalProperties also
|
||||||
example += closingIndentation + closeChars;
|
example += closingIndentation + closeChars;
|
||||||
|
@ -15,6 +15,18 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
package org.openapitools.codegen.python;
|
package org.openapitools.codegen.python;
|
||||||
|
import com.google.common.io.Resources;
|
||||||
|
import io.swagger.v3.oas.models.PathItem;
|
||||||
|
import io.swagger.v3.oas.models.Paths;
|
||||||
|
import io.swagger.v3.oas.models.parameters.RequestBody;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import javax.validation.constraints.AssertTrue;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.openapitools.codegen.config.CodegenConfigurator;
|
import org.openapitools.codegen.config.CodegenConfigurator;
|
||||||
|
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
@ -33,6 +45,7 @@ import org.openapitools.codegen.*;
|
|||||||
import org.openapitools.codegen.languages.PythonClientCodegen;
|
import org.openapitools.codegen.languages.PythonClientCodegen;
|
||||||
import org.openapitools.codegen.utils.ModelUtils;
|
import org.openapitools.codegen.utils.ModelUtils;
|
||||||
import org.testng.Assert;
|
import org.testng.Assert;
|
||||||
|
import org.testng.TestNGAntTask.Mode;
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
@SuppressWarnings("static-method")
|
@SuppressWarnings("static-method")
|
||||||
@ -425,4 +438,30 @@ public class PythonClientTest {
|
|||||||
final CodegenModel model = codegen.fromModel(modelName, modelSchema);
|
final CodegenModel model = codegen.fromModel(modelName, modelSchema);
|
||||||
Assert.assertEquals((int) model.getMinProperties(), 1);
|
Assert.assertEquals((int) model.getMinProperties(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(description = "tests RecursiveToExample")
|
||||||
|
public void testRecursiveToExample() throws IOException {
|
||||||
|
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/issue_8052_recursive_model.yaml");
|
||||||
|
final PythonClientCodegen codegen = new PythonClientCodegen();
|
||||||
|
codegen.setOpenAPI(openAPI);
|
||||||
|
|
||||||
|
final Operation operation = openAPI.getPaths().get("/geojson").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);
|
||||||
|
|
||||||
|
String expectedValue = Resources.toString(
|
||||||
|
Resources.getResource("3_0/issue_8052_recursive_model_expected_value.txt"),
|
||||||
|
StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
|
||||||
|
Assert.assertEquals(expectedValue.trim(), exampleValue.trim());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,83 @@
|
|||||||
|
openapi: 3.0.0
|
||||||
|
info:
|
||||||
|
version: 01.01.00
|
||||||
|
title: APITest API documentation.
|
||||||
|
termsOfService: http://api.apitest.com/party/tos/
|
||||||
|
servers:
|
||||||
|
- url: https://api.apitest.com/v1
|
||||||
|
paths:
|
||||||
|
/geojson:
|
||||||
|
post:
|
||||||
|
summary: Add a GeoJson Object
|
||||||
|
operationId: post-geojson
|
||||||
|
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/GeoJsonGeometry'
|
||||||
|
parameters: []
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
GeoJsonGeometry:
|
||||||
|
title: GeoJsonGeometry
|
||||||
|
description: GeoJSON geometry
|
||||||
|
oneOf:
|
||||||
|
- $ref: '#/components/schemas/Point'
|
||||||
|
- $ref: '#/components/schemas/GeometryCollection'
|
||||||
|
discriminator:
|
||||||
|
propertyName: type
|
||||||
|
mapping:
|
||||||
|
Point: '#/components/schemas/Point'
|
||||||
|
GeometryCollection: '#/components/schemas/GeometryCollection'
|
||||||
|
externalDocs:
|
||||||
|
url: http://geojson.org/geojson-spec.html#geometry-objects
|
||||||
|
Point:
|
||||||
|
title: Point
|
||||||
|
type: object
|
||||||
|
description: GeoJSON geometry
|
||||||
|
externalDocs:
|
||||||
|
url: http://geojson.org/geojson-spec.html#id2
|
||||||
|
properties:
|
||||||
|
coordinates:
|
||||||
|
title: Point3D
|
||||||
|
type: array
|
||||||
|
description: Point in 3D space
|
||||||
|
externalDocs:
|
||||||
|
url: http://geojson.org/geojson-spec.html#id2
|
||||||
|
minItems: 2
|
||||||
|
maxItems: 3
|
||||||
|
items:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
default: Point
|
||||||
|
required:
|
||||||
|
- type
|
||||||
|
GeometryCollection:
|
||||||
|
title: GeometryCollection
|
||||||
|
type: object
|
||||||
|
description: GeoJSon geometry collection
|
||||||
|
required:
|
||||||
|
- type
|
||||||
|
- geometries
|
||||||
|
externalDocs:
|
||||||
|
url: http://geojson.org/geojson-spec.html#geometrycollection
|
||||||
|
properties:
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
default: GeometryCollection
|
||||||
|
geometries:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/GeoJsonGeometry'
|
@ -0,0 +1,9 @@
|
|||||||
|
GeoJsonGeometry(
|
||||||
|
type="GeometryCollection",
|
||||||
|
geometries=[
|
||||||
|
GeoJsonGeometry(
|
||||||
|
type="GeometryCollection",
|
||||||
|
geometries=[],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
Loading…
x
Reference in New Issue
Block a user