Skip to content
This repository was archived by the owner on Dec 25, 2024. It is now read-only.

Commit a8b84b6

Browse files
authored
v3 adds enum literal input and output type hints (#186)
* Fixes java warnings * Adds exception throw if enum values types are not allowed * Adds and uses enumInfo * Adds typeToValues enumInfo * Addsstring literal output for single schema validate with enums * Adds validate method writing for simple schema classes with enums * Adds string and int enum type hints to return type of validate * Adds string literal output type for single type, per literal input string * Removes are type in validate for enum classes of type int, str, bool * Fixes overload signatures for int/bool/str with enums * Adds bol and int enum to object output properties * object properties of type string with enum now have literal output types * Adds _helper_validate_str_overload template * Uses string overload template for multiple types * Adds and uses bool overload template * Adds and uses int validate overload template * Handles int enum value for number use case, java tests fixed, samples regen
1 parent 38e8560 commit a8b84b6

File tree

95 files changed

+2921
-303
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+2921
-303
lines changed

modules/openapi-json-schema-generator/src/main/java/org/openapijsonschematools/codegen/DefaultCodegen.java

Lines changed: 67 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
import org.openapijsonschematools.codegen.model.CodegenServer;
6565
import org.openapijsonschematools.codegen.model.CodegenTag;
6666
import org.openapijsonschematools.codegen.model.CodegenXml;
67+
import org.openapijsonschematools.codegen.model.EnumInfo;
6768
import org.openapijsonschematools.codegen.model.EnumValue;
6869
import org.openapijsonschematools.codegen.model.LinkedHashMapWithContext;
6970
import org.openapijsonschematools.codegen.model.PairCacheKey;
@@ -2282,7 +2283,7 @@ public CodegenSchema fromSchema(Schema p, String sourceJsonPath, String currentJ
22822283
property.items = fromSchema(
22832284
p.getItems(), sourceJsonPath, currentJsonPath + "/items");
22842285
}
2285-
property.enumValueToName = getEnumValueToName(p, currentJsonPath, sourceJsonPath);
2286+
property.enumInfo = getEnumInfo(p, currentJsonPath, sourceJsonPath, property.types);
22862287
List<Schema> anyOfs = ((Schema<?>) p).getAnyOf();
22872288
if (anyOfs != null && !anyOfs.isEmpty()) {
22882289
property.anyOf = getComposedProperties(anyOfs, "anyOf", sourceJsonPath, currentJsonPath);
@@ -2299,24 +2300,36 @@ public CodegenSchema fromSchema(Schema p, String sourceJsonPath, String currentJ
22992300
property.optionalProperties = getOptionalProperties(property.properties, required, sourceJsonPath, currentName);
23002301
if ((property.types == null || property.types.contains("object")) && sourceJsonPath != null) {
23012302
// only set mapInputJsonPathPiece when object input is possible
2302-
if (property.requiredProperties != null && property.optionalProperties == null) {
2303-
// only required
2304-
property.mapInputJsonPathPiece = property.requiredProperties.jsonPathPiece();
2305-
property.mapOutputJsonPathPiece = getKey(currentName + "Dict", "schemaProperty", sourceJsonPath);
2306-
} else if (property.requiredProperties == null && property.optionalProperties != null) {
2307-
// only optional
2308-
property.mapInputJsonPathPiece = property.optionalProperties.jsonPathPiece();
2309-
property.mapOutputJsonPathPiece = getKey(currentName + "Dict", "schemaProperty", sourceJsonPath);
2310-
} else if (property.requiredProperties != null && property.optionalProperties != null) {
2311-
// optional + required
2312-
property.mapInputJsonPathPiece = getKey(currentName + "DictInput", "schemaProperty", sourceJsonPath);
2313-
property.mapOutputJsonPathPiece = getKey(currentName + "Dict", "schemaProperty", sourceJsonPath);
2314-
} else if (property.additionalProperties != null) {
2315-
// only addProps
2316-
property.mapInputJsonPathPiece = getKey(currentName + "DictInput", "schemaProperty", sourceJsonPath);
2317-
// even though the definition is the same, mapOutputJsonPathPiece needs to be different
2318-
// so an implementing class can be written
2319-
property.mapOutputJsonPathPiece = getKey(currentName + "Dict", "schemaProperty", sourceJsonPath);
2303+
boolean requiredPropsExist = property.requiredProperties != null;
2304+
boolean optionalPropsExist = property.optionalProperties != null;
2305+
String firstBoolChar = requiredPropsExist? "1" : "0";
2306+
String SecondBoolChar = optionalPropsExist? "1" : "0";
2307+
String boolState = firstBoolChar + SecondBoolChar;
2308+
switch (boolState) {
2309+
case "00":
2310+
if (property.additionalProperties != null) {
2311+
// only addProps
2312+
property.mapInputJsonPathPiece = getKey(currentName + "DictInput", "schemaProperty", sourceJsonPath);
2313+
// even though the definition is the same, mapOutputJsonPathPiece needs to be different
2314+
// so an implementing class can be written
2315+
property.mapOutputJsonPathPiece = getKey(currentName + "Dict", "schemaProperty", sourceJsonPath);
2316+
}
2317+
break;
2318+
case "11":
2319+
// optional + required
2320+
property.mapInputJsonPathPiece = getKey(currentName + "DictInput", "schemaProperty", sourceJsonPath);
2321+
property.mapOutputJsonPathPiece = getKey(currentName + "Dict", "schemaProperty", sourceJsonPath);
2322+
break;
2323+
case "10":
2324+
// only required
2325+
property.mapInputJsonPathPiece = property.requiredProperties.jsonPathPiece();
2326+
property.mapOutputJsonPathPiece = getKey(currentName + "Dict", "schemaProperty", sourceJsonPath);
2327+
break;
2328+
case "01":
2329+
// only optional
2330+
property.mapInputJsonPathPiece = property.optionalProperties.jsonPathPiece();
2331+
property.mapOutputJsonPathPiece = getKey(currentName + "Dict", "schemaProperty", sourceJsonPath);
2332+
break;
23202333
}
23212334
}
23222335
if ((property.types == null || property.types.contains("array")) && sourceJsonPath != null && property.items != null) {
@@ -3479,19 +3492,22 @@ private void updateComponentsFilepath(String[] pathPieces) {
34793492
String requestBodiesIdentifier = "request_bodies";
34803493
String securitySchemesIdentifier = "security_schemes";
34813494
// rename schemas + requestBodies
3482-
if (pathPieces[2].equals("schemas")) {
3483-
// modelPackage replaces pathPieces[1] + pathPieces[2]
3484-
pathPieces[1] = modelPackagePathFragment();
3485-
pathPieces[2] = null;
3486-
if (pathPieces.length == 4) {
3487-
// #/components/schemas/SomeSchema
3488-
pathPieces[3] = getKey(pathPieces[3], "schemas").snakeCase;
3489-
}
3490-
return;
3491-
} else if (pathPieces[2].equals("requestBodies")) {
3492-
pathPieces[2] = requestBodiesIdentifier;
3493-
} else if (pathPieces[2].equals("securitySchemes")) {
3494-
pathPieces[2] = securitySchemesIdentifier;
3495+
switch (pathPieces[2]) {
3496+
case "schemas":
3497+
// modelPackage replaces pathPieces[1] + pathPieces[2]
3498+
pathPieces[1] = modelPackagePathFragment();
3499+
pathPieces[2] = null;
3500+
if (pathPieces.length == 4) {
3501+
// #/components/schemas/SomeSchema
3502+
pathPieces[3] = getKey(pathPieces[3], "schemas").snakeCase;
3503+
}
3504+
return;
3505+
case "requestBodies":
3506+
pathPieces[2] = requestBodiesIdentifier;
3507+
break;
3508+
case "securitySchemes":
3509+
pathPieces[2] = securitySchemesIdentifier;
3510+
break;
34953511
}
34963512
if (pathPieces.length < 4) {
34973513
return;
@@ -3946,13 +3962,14 @@ public String sanitizeTag(String tag) {
39463962
return tag;
39473963
}
39483964

3949-
protected LinkedHashMapWithContext<EnumValue, String> getEnumValueToName(Schema schema, String currentJsonPath, String sourceJsonPath) {
3965+
protected EnumInfo getEnumInfo(Schema schema, String currentJsonPath, String sourceJsonPath, LinkedHashSet<String> types) {
39503966
if (schema.getEnum() == null) {
39513967
return null;
39523968
}
39533969

39543970
ArrayList<Object> values = new ArrayList<>(((Schema<?>) schema).getEnum());
39553971
LinkedHashMapWithContext<EnumValue, String> enumValueToName = new LinkedHashMapWithContext<>();
3972+
HashMap<String, List<EnumValue>> typeToValues = new LinkedHashMap<>();
39563973
LinkedHashMap<String, EnumValue> enumNameToValue = new LinkedHashMap<>();
39573974
int truncateIdx = 0;
39583975

@@ -4014,7 +4031,22 @@ protected LinkedHashMapWithContext<EnumValue, String> getEnumValueToName(Schema
40144031

40154032
String usedName = toEnumVarName(enumName, schema);
40164033
EnumValue enumValue = getEnumValue(value, description);
4034+
boolean typeIsInteger = enumValue.type.equals("integer");
4035+
boolean intIsNumberUseCase = (typeIsInteger && types!=null && types.contains("number"));
4036+
if (types!=null && !types.contains(enumValue.type) && !intIsNumberUseCase) {
4037+
throw new RuntimeException("Enum value's type is not allowed by schema types for value="+enumValue.value+" types="+types + " jsonPath="+currentJsonPath);
4038+
}
40174039
enumValueToName.put(enumValue, usedName);
4040+
if (!typeToValues.containsKey(enumValue.type)) {
4041+
typeToValues.put(enumValue.type, new ArrayList<>());
4042+
}
4043+
if (typeIsInteger && !typeToValues.containsKey("number")) {
4044+
typeToValues.put("number", new ArrayList<>());
4045+
}
4046+
typeToValues.get(enumValue.type).add(enumValue);
4047+
if (typeIsInteger) {
4048+
typeToValues.get("number").add(enumValue);
4049+
}
40184050

40194051
if (!enumNameToValue.containsKey(usedName)) {
40204052
enumNameToValue.put(usedName, enumValue);
@@ -4033,7 +4065,7 @@ protected LinkedHashMapWithContext<EnumValue, String> getEnumValueToName(Schema
40334065
enumValueToName.setJsonPathPiece(key);
40344066
}
40354067

4036-
return enumValueToName;
4068+
return new EnumInfo(enumValueToName, typeToValues);
40374069
}
40384070

40394071
/**
@@ -4367,8 +4399,7 @@ private String getRefModuleLocation(String ref) {
43674399
String prefix = outputFolder + File.separatorChar + "src" + File.separatorChar;
43684400
int endIndex = filePath.lastIndexOf(File.separatorChar);
43694401
String localFilepath = filePath.substring(prefix.length(), endIndex);
4370-
String refModuleLocation = localFilepath.replaceAll(String.valueOf(File.separatorChar), ".");
4371-
return refModuleLocation;
4402+
return localFilepath.replaceAll(String.valueOf(File.separatorChar), ".");
43724403
}
43734404

43744405
private String getRefModuleAlias(String refModuleLocation, String refModule) {

modules/openapi-json-schema-generator/src/main/java/org/openapijsonschematools/codegen/languages/AbstractJavaCodegen.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -988,7 +988,7 @@ public void postProcessModelProperty(CodegenSchema model, CodegenSchema property
988988
}
989989
}
990990

991-
if (model.enumValueToName == null) {
991+
if (model.enumInfo == null) {
992992
// needed by all pojos, but not enums
993993
model.imports.add("ApiModelProperty");
994994
model.imports.add("ApiModel");

modules/openapi-json-schema-generator/src/main/java/org/openapijsonschematools/codegen/languages/AbstractKotlinCodegen.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ public TreeMap<String, CodegenSchema> postProcessModels(TreeMap<String, CodegenS
335335
}
336336

337337
for (CodegenSchema var : cm.properties.values()) {
338-
if (var.enumValueToName != null || isSerializableModel()) {
338+
if (var.enumInfo != null || isSerializableModel()) {
339339
cm.vendorExtensions.put("x-has-data-class-body", true);
340340
break;
341341
}

modules/openapi-json-schema-generator/src/main/java/org/openapijsonschematools/codegen/languages/JavaClientCodegen.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -722,7 +722,7 @@ private static boolean isMultipartType(List<Map<String, String>> consumes) {
722722
@Override
723723
public void postProcessModelProperty(CodegenSchema model, CodegenSchema property) {
724724
super.postProcessModelProperty(model, property);
725-
if (model.enumValueToName == null) {
725+
if (model.enumInfo == null) {
726726
//final String lib = getLibrary();
727727
//Needed imports for Jackson based libraries
728728
if (additionalProperties.containsKey(SERIALIZATION_LIBRARY_JACKSON)) {

modules/openapi-json-schema-generator/src/main/java/org/openapijsonschematools/codegen/languages/JavaJerseyServerCodegen.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public void postProcessModelProperty(CodegenSchema model, CodegenSchema property
8989
}
9090

9191
//Add imports for Jackson
92-
if (model.enumValueToName == null) {
92+
if (model.enumInfo == null) {
9393
model.imports.add("JsonProperty");
9494
}
9595
}

modules/openapi-json-schema-generator/src/main/java/org/openapijsonschematools/codegen/model/CodegenSchema.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public class CodegenSchema {
4545
public Integer maxProperties;
4646
public Integer minProperties;
4747
public LinkedHashMapWithContext<CodegenKey, CodegenSchema> requiredProperties; // used to store required info
48-
public LinkedHashMapWithContext<EnumValue, String> enumValueToName; // enum info
48+
public EnumInfo enumInfo;
4949
public String type;
5050
public ArrayListWithContext<CodegenSchema> allOf = null;
5151
public ArrayListWithContext<CodegenSchema> anyOf = null;
@@ -248,13 +248,13 @@ private void getAllSchemas(ArrayList<CodegenSchema> schemasBeforeImports, ArrayL
248248
schemasAfterImports.add(extraSchema);
249249
}
250250
}
251-
if (enumValueToName != null) {
251+
if (enumInfo != null) {
252252
// write the class as a separate entity so enum values do not collide with
253253
// json schema keywords
254254
CodegenSchema extraSchema = new CodegenSchema();
255255
extraSchema.jsonPathPiece = jsonPathPiece;
256256
extraSchema.instanceType = "enumClass";
257-
extraSchema.enumValueToName = enumValueToName;
257+
extraSchema.enumInfo = enumInfo;
258258
schemasBeforeImports.add(extraSchema);
259259
}
260260
boolean schemaAllAreInline = true;
@@ -480,7 +480,7 @@ protected void addInstanceInfo(StringBuilder sb) {
480480
sb.append(", readOnly=").append(readOnly);
481481
sb.append(", writeOnly=").append(writeOnly);
482482
sb.append(", nullable=").append(nullable);
483-
sb.append(", allowableValues=").append(enumValueToName);
483+
sb.append(", allowableValues=").append(enumInfo);
484484
sb.append(", items=").append(items);
485485
sb.append(", additionalProperties=").append(additionalProperties);
486486
sb.append(", vendorExtensions=").append(vendorExtensions);
@@ -566,7 +566,7 @@ public boolean equals(Object o) {
566566
Objects.equals(example, that.example) &&
567567
Objects.equals(minimum, that.minimum) &&
568568
Objects.equals(maximum, that.maximum) &&
569-
Objects.equals(enumValueToName, that.enumValueToName) &&
569+
Objects.equals(enumInfo, that.enumInfo) &&
570570
Objects.equals(items, that.items) &&
571571
Objects.equals(additionalProperties, that.additionalProperties) &&
572572
Objects.equals(vendorExtensions, that.vendorExtensions) &&
@@ -583,7 +583,7 @@ public int hashCode() {
583583
maxLength, minLength, patternInfo, example, minimum, maximum,
584584
exclusiveMinimum, exclusiveMaximum, deprecated, types,
585585
readOnly, writeOnly, nullable,
586-
enumValueToName, items, additionalProperties,
586+
enumInfo, items, additionalProperties,
587587
vendorExtensions, maxItems, minItems, xml,
588588
schemaIsFromAdditionalProperties, isBooleanSchemaTrue, isBooleanSchemaFalse,
589589
format, dependentRequired, contains, allOf, anyOf, oneOf, not,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package org.openapijsonschematools.codegen.model;
2+
3+
import java.util.HashMap;
4+
import java.util.List;
5+
import java.util.Objects;
6+
7+
public class EnumInfo {
8+
public LinkedHashMapWithContext<EnumValue, String> valueToName; // enum info
9+
public HashMap<String, List<EnumValue>> typeToValues;
10+
11+
public EnumInfo(LinkedHashMapWithContext<EnumValue, String> valueToName, HashMap<String, List<EnumValue>> typeToValues) {
12+
this.valueToName = valueToName;
13+
this.typeToValues = typeToValues;
14+
}
15+
16+
@Override
17+
public boolean equals(Object o) {
18+
if (this == o) return true;
19+
if (o == null || getClass() != o.getClass()) return false;
20+
EnumInfo that = (EnumInfo) o;
21+
return Objects.equals(valueToName, that.valueToName) &&
22+
Objects.equals(typeToValues, that.typeToValues);
23+
}
24+
25+
@Override
26+
public int hashCode() {
27+
return Objects.hash(valueToName, typeToValues);
28+
}
29+
}

modules/openapi-json-schema-generator/src/main/resources/python/components/schemas/_helper_enum_class.hbs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
{{#if enumValueToName}}
1+
{{#if enumInfo}}
22

33

4-
class {{enumValueToName.jsonPathPiece.camelCase}}:
5-
{{#each enumValueToName}}
4+
class {{enumInfo.valueToName.jsonPathPiece.camelCase}}:
5+
{{#each enumInfo.valueToName}}
66

77
@schemas.classproperty
88
{{#eq @key.type "string"}}
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{{#unless refInfo.refClass}}{{#if enumValueToName}} must be one of [{{#each enumValueToName}}{{#unless @first}}, {{/unless}}{{#eq @key.type "null"}}None{{/eq}}{{#eq @key.type "boolean"}}{{#if @key.value}}True{{else}}False{{/if}}{{/eq}}{{#eq @key.type "string"}}"{{{@key.value}}}"{{/eq}}{{#eq @key.type "number"}}{{{@key.value}}}{{/eq}}{{#eq @key.type "integer"}}{{{@key.value}}}{{/eq}}{{/each}}]{{/if}}{{#if defaultValue}}{{#unless requiredProperties}} if omitted the {{defaultUser}} will use the default value of {{{defaultValue.value}}}{{/unless}}{{/if}}{{#eq format "uuid"}} value must be a uuid{{/eq}}{{#eq format "date"}} value must conform to RFC-3339 full-date YYYY-MM-DD{{/eq}}{{#eq format "date-time"}} value must conform to RFC-3339 date-time{{/eq}}{{#eq format "number"}} value must be int or float numeric{{/eq}}{{#eq format "int32"}} value must be a 32 bit integer{{/eq}}{{#eq format "int64"}} value must be a 64 bit integer{{/eq}}{{#eq format "double"}} value must be a 64 bit float{{/eq}}{{#eq format "float"}} value must be a 32 bit float{{/eq}}{{/unless}}
1+
{{#unless refInfo.refClass}}{{#if enumInfo}} must be one of [{{#each enumInfo.valueToName}}{{#unless @first}}, {{/unless}}{{#eq @key.type "null"}}None{{/eq}}{{#eq @key.type "boolean"}}{{#if @key.value}}True{{else}}False{{/if}}{{/eq}}{{#eq @key.type "string"}}"{{{@key.value}}}"{{/eq}}{{#eq @key.type "number"}}{{{@key.value}}}{{/eq}}{{#eq @key.type "integer"}}{{{@key.value}}}{{/eq}}{{/each}}]{{/if}}{{#if defaultValue}}{{#unless requiredProperties}} if omitted the {{defaultUser}} will use the default value of {{{defaultValue.value}}}{{/unless}}{{/if}}{{#eq format "uuid"}} value must be a uuid{{/eq}}{{#eq format "date"}} value must conform to RFC-3339 full-date YYYY-MM-DD{{/eq}}{{#eq format "date-time"}} value must conform to RFC-3339 date-time{{/eq}}{{#eq format "number"}} value must be int or float numeric{{/eq}}{{#eq format "int32"}} value must be a 32 bit integer{{/eq}}{{#eq format "int64"}} value must be a 64 bit integer{{/eq}}{{#eq format "double"}} value must be a 64 bit float{{/eq}}{{#eq format "float"}} value must be a 32 bit float{{/eq}}{{/unless}}

modules/openapi-json-schema-generator/src/main/resources/python/components/schemas/_helper_schema_composed_or_anytype.hbs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ class {{jsonPathPiece.camelCase}}(
3030
{{#if format}}
3131
format: str = '{{format}}'
3232
{{/if}}
33-
{{#if enumValueToName}}
33+
{{#if enumInfo}}
3434
{{> components/schemas/_helper_schema_enum }}
35-
enums = {{enumValueToName.jsonPathPiece.camelCase}}
35+
enums = {{enumInfo.valueToName.jsonPathPiece.camelCase}}
3636
{{/if}}
3737
{{#if items}}
3838
{{> components/schemas/_helper_list_partial }}

modules/openapi-json-schema-generator/src/main/resources/python/components/schemas/_helper_schema_enum.hbs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
enum_value_to_name: typing.Mapping[typing.Union[int, float, str, bool, schemas.none_type_], str] = dataclasses.field(
1+
enum_value_to_name: typing.Mapping[typing.Union[int, float, str, schemas.Bool, None], str] = dataclasses.field(
22
default_factory=lambda: {
3-
{{#each enumValueToName}}
3+
{{#each enumInfo.valueToName}}
44
{{#eq @key.type "string"}}
55
"{{{@key.value}}}": "{{this}}",
66
{{/eq}}

0 commit comments

Comments
 (0)