Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stackoverflow in toExampleValue() for python client #8326

Merged
merged 1 commit into from
Jan 5, 2021
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package org.openapitools.codegen.languages;

import com.google.common.collect.Sets;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.oas.models.media.*;
import io.swagger.v3.oas.models.media.ArraySchema;
Expand Down Expand Up @@ -879,7 +880,7 @@ private String ensureQuotes(String in) {

public String toExampleValue(Schema schema, Object objExample) {
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) {
Expand Down Expand Up @@ -925,9 +926,12 @@ private MappedModel getDiscriminatorMappedModel(CodegenDiscriminator disc) {
* ModelName( line 0
* some_property='some_property_example' line 1
* ) 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
*/
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 = " ";
String currentIndentation = "";
String closingIndentation = "";
Expand All @@ -951,6 +955,27 @@ private String toExampleValueRecursive(String modelName, Schema schema, Object o
if (objExample != null) {
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()) {
Map<String, Schema> allDefinitions = ModelUtils.getSchemas(this.openAPI);
String ref = ModelUtils.getSimpleRef(schema.get$ref());
Expand All @@ -960,7 +985,7 @@ private String toExampleValueRecursive(String modelName, Schema schema, Object o
return fullPrefix + "None" + closeChars;
}
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)) {
// 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.
Expand Down Expand Up @@ -1058,7 +1083,8 @@ private String toExampleValueRecursive(String modelName, Schema schema, Object o
ArraySchema arrayschema = (ArraySchema) schema;
Schema itemSchema = arrayschema.getItems();
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;
} else if (ModelUtils.isMapSchema(schema)) {
if (modelName == null) {
Expand All @@ -1080,7 +1106,8 @@ private String toExampleValueRecursive(String modelName, Schema schema, Object o
addPropPrefix = ensureQuotes(key) + ": ";
}
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 {
example = fullPrefix + closeChars;
}
Expand All @@ -1103,7 +1130,12 @@ private String toExampleValueRecursive(String modelName, Schema schema, Object o
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)) {
// TODO add examples for composed schema models without discriminators

Expand All @@ -1117,7 +1149,12 @@ private String toExampleValueRecursive(String modelName, Schema schema, Object o
CodegenProperty cp = new CodegenProperty();
cp.setName(disc.getPropertyName());
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 {
return fullPrefix + closeChars;
}
Expand All @@ -1130,7 +1167,7 @@ private String toExampleValueRecursive(String modelName, Schema schema, Object o
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();
if (requiredAndOptionalProps == null || requiredAndOptionalProps.isEmpty()) {
return fullPrefix + closeChars;
Expand All @@ -1150,7 +1187,7 @@ private String exampleForObjectModel(Schema schema, String fullPrefix, String cl
propModelName = getModelName(propSchema);
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
example += closingIndentation + closeChars;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@
*/

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 com.google.common.collect.Sets;
Expand All @@ -33,6 +45,7 @@
import org.openapitools.codegen.languages.PythonClientCodegen;
import org.openapitools.codegen.utils.ModelUtils;
import org.testng.Assert;
import org.testng.TestNGAntTask.Mode;
import org.testng.annotations.Test;

@SuppressWarnings("static-method")
Expand Down Expand Up @@ -425,4 +438,31 @@ public void testObjectWithValidations() {
final CodegenModel model = codegen.fromModel(modelName, modelSchema);
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);
expectedValue = expectedValue.replaceAll("\\r\\n", "\n");
spacether marked this conversation as resolved.
Show resolved Hide resolved


Assert.assertEquals(expectedValue.trim(), exampleValue.trim());

}

}
Original file line number Diff line number Diff line change
@@ -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'
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
GeoJsonGeometry(
type="GeometryCollection",
geometries=[
GeoJsonGeometry(
type="GeometryCollection",
geometries=[],
),
],
)