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

[Codegen] Convert "any type" to oneOf model #6051

Prev Previous commit
Convert any type to anyOf
  • Loading branch information
sebastien-rosset committed Apr 28, 2020
commit af6496f5377e2d7a84423ed1c6293f783d4b6ace
Original file line number Diff line number Diff line change
Expand Up @@ -2106,36 +2106,68 @@ public String toModelName(final String name) {
return camelize(modelNamePrefix + "_" + name + "_" + modelNameSuffix);
}

// Returns a model that encapsulates the JSON schema "any type". Its value
// can be any of null, integer, boolean, number, string, array or map.
// "Any type" is a schema that does not have the "type" attribute
// specified in the OpenAPI schema. That means the value can be any valid
// payload, i.e. the null value, boolean, string, integer, number,
// array or map.
// Numerical payloads may match more than one type, for example "2" may
// match integer and number. Hence the use of 'anyOf'.
public CodegenModel getAnyTypeModel(String name, Schema schema) {
/**
* Returns a composed model that encapsulates the JSON schema "any type".
* Its value can be any of null, integer, boolean, number, string, array or map.
*/
protected Schema getAnyTypeSchema(String name, Schema schema) {
if (!ModelUtils.isAnyTypeSchema(schema)) {
throw new RuntimeException("Schema '" + name + "' is not 'any type'");
}
// TODO: what is a good name for this expanded type? There could be collisions.
String anyTypeName = name + ".AnyType";
ComposedSchema cs = (ComposedSchema) new ComposedSchema()
.addAnyOfItem(new ObjectSchema().type("null"))
.addAnyOfItem(new BooleanSchema())
.addAnyOfItem(new StringSchema())
.addAnyOfItem(new IntegerSchema())
.addAnyOfItem(new NumberSchema())
.name(anyTypeName);
.addAnyOfItem(new StringSchema()
.minLength(schema.getMinLength())
.maxLength(schema.getMaxLength())
.pattern(schema.getPattern())
)
.addAnyOfItem(new IntegerSchema()
.minimum(schema.getMinimum())
.maximum(schema.getMaximum())
.exclusiveMinimum(schema.getExclusiveMinimum())
.exclusiveMaximum(schema.getExclusiveMaximum())
.multipleOf(schema.getMultipleOf())
)
.addAnyOfItem(new NumberSchema()
.minimum(schema.getMinimum())
.maximum(schema.getMaximum())
.exclusiveMinimum(schema.getExclusiveMinimum())
.exclusiveMaximum(schema.getExclusiveMaximum())
.multipleOf(schema.getMultipleOf())
)
.name(name);

// The map keys must be strings and the values can be anything.
cs.addAnyOfItem(new MapSchema().additionalProperties(true));
cs.addAnyOfItem(new MapSchema()
.additionalProperties(true)
.minProperties(schema.getMinProperties())
.maxProperties(schema.getMaxProperties())
.required(schema.getRequired())
);
// The array items can be anything.
cs.addAnyOfItem(new ArraySchema());
cs.addAnyOfItem(new ArraySchema()
.minItems(schema.getMinItems())
.maxItems(schema.getMaxItems())
.uniqueItems(schema.getUniqueItems())
);
if (schema != null) {
cs.setTitle(schema.getTitle());
cs.setDescription(schema.getDescription());
Copy link
Contributor Author

@sebastien-rosset sebastien-rosset Apr 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially I thought I should create a single "AnyType" schema, but in reality there may be corner cases where constraints other than "type" have been specified:
title
pattern
required
enum
minimum
maximum
exclusiveMinimum
exclusiveMaximum
multipleOf
minLength
maxLength
minItems
maxItems
uniqueItems
minProperties
maxProperties

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But aren't those constraints specific to types? If you had minItems it would only apply to dict and array, right? My take is if they have any of those constraints then they should be fully explicit and list all types. We are just trying to cover this one super general yoy said it could be anything case. What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That way that constraint question is the headache of specific generators. I agree with you about adding this model/schema once and then using it multiple places if the writers used it multiple places.

Copy link
Contributor Author

@sebastien-rosset sebastien-rosset Apr 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But aren't those constraints specific to types? If you had minItems it would only apply to dict and array, right? My take is if they have any of those constraints then they should be fully explicit and list all types. We are just trying to cover this one super general yoy said it could be anything case. What do you think?

I am planning to handle these edge cases later. Right now just trying to make the simple case work, i.e. no OAS attribute whatsoever is defined in the OAS schema, it's really any type. Even this simple case is not so simple.

Also, the good news is most of these constraints are specific to a type. The only constraints that apply to all types are type, enum and const: https://tools.ietf.org/html/draft-handrews-json-schema-validation-02

}
return fromModel(anyTypeName, cs);
return cs;
}

// Returns a model that encapsulates the JSON schema "any type". Its value
// can be any of null, integer, boolean, number, string, array or map.
// "Any type" is a schema that does not have the "type" attribute
// specified in the OpenAPI schema. That means the value can be any valid
// payload, i.e. the null value, boolean, string, integer, number,
// array or map.
// Numerical payloads may match more than one type, for example "2" may
// match integer and number. Hence the use of 'anyOf'.
public CodegenModel getAnyTypeModel(String name, Schema schema) {
return fromModel(name, getAnyTypeSchema(name, schema));
}

/**
Expand Down Expand Up @@ -2843,7 +2875,7 @@ public String getterAndSetterCapitalize(String name) {
* Convert OAS Property object to Codegen Property object
*
* @param name name of the property
* @param p OAS property object
* @param p OAS property schema
* @return Codegen Property object
*/
public CodegenProperty fromProperty(String name, Schema p) {
Expand All @@ -2856,6 +2888,9 @@ public CodegenProperty fromProperty(String name, Schema p) {
// unalias schema
p = ModelUtils.unaliasSchema(this.openAPI, p, importMapping);

if (ModelUtils.isAnyTypeSchema(p)) {
p = getAnyTypeSchema(name, p);
}
CodegenProperty property = CodegenModelFactory.newInstance(CodegenModelType.PROPERTY);

ModelUtils.syncValidationProperties(p, property);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,8 +289,8 @@ public void anyTypeSchemaTest() {
codegen.setOpenAPI(openAPI);
final CodegenModel cm = codegen.fromModel("sample", schema);
//
Assert.assertEquals(cm.name, "sample.AnyType");
Assert.assertEquals(cm.classname, "SampleAnyType");
Assert.assertEquals(cm.name, "sample");
Assert.assertEquals(cm.classname, "Sample");
Assert.assertEquals(cm.description, "a sample model");
}

Expand Down