diff --git a/docs/generators/java.md b/docs/generators/java.md index 15aaa3c8e56..36e24737312 100644 --- a/docs/generators/java.md +++ b/docs/generators/java.md @@ -10,6 +10,7 @@ title: Documentation for the java generator | generator stability | EXPERIMENTAL | | | generator type | CLIENT | | | generator language | Java | | +| generator language version | 17 | | | generator default templating engine | handlebars | | | helpTxt | Generates a Java client library (HTTP lib: Jersey (1.x, 2.x), Retrofit (2.x), OpenFeign (10.x) and more. | | diff --git a/samples/client/petstore/java/.openapi-generator/FILES b/samples/client/petstore/java/.openapi-generator/FILES index 1e3d5643ddd..95fce7d5197 100644 --- a/samples/client/petstore/java/.openapi-generator/FILES +++ b/samples/client/petstore/java/.openapi-generator/FILES @@ -1,4 +1,13 @@ README.md pom.xml +src/main/java/org/openapijsonschematools/configurations/JsonSchemaKeywordFlags.java +src/main/java/org/openapijsonschematools/configurations/SchemaConfiguration.java src/main/java/org/openapijsonschematools/schemas/CustomIsoparser.java +src/main/java/org/openapijsonschematools/schemas/PathToSchemasMap.java +src/main/java/org/openapijsonschematools/schemas/SchemaValidator.java +src/main/java/org/openapijsonschematools/schemas/ValidationMetadata.java +src/main/java/org/openapijsonschematools/schemas/validators/KeywordValidator.java +src/main/java/org/openapijsonschematools/schemas/validators/TypeValidator.java +src/test/java/org/openapijsonschematools/configurations/JsonSchemaKeywordFlagsTest.java src/test/java/org/openapijsonschematools/schemas/CustomIsoparserTest.java +src/test/java/org/openapijsonschematools/schemas/validators/TypeValidatorTest.java diff --git a/samples/client/petstore/java/README.md b/samples/client/petstore/java/README.md index 2c563a97ef8..0b0b1290533 100644 --- a/samples/client/petstore/java/README.md +++ b/samples/client/petstore/java/README.md @@ -7,13 +7,12 @@ This Java package is automatically generated by the [OpenAPI JSON Schema Generat - API version: 1.0.0 - Package version: - - Build package: JavaClientGenerator ## Requirements -Python +Java 17 ## Migration Guides - [3.0.0 Migration Guide](migration_3_0_0.md) diff --git a/samples/client/petstore/java/pom.xml b/samples/client/petstore/java/pom.xml index 14ca5ae356b..cbb4c421108 100644 --- a/samples/client/petstore/java/pom.xml +++ b/samples/client/petstore/java/pom.xml @@ -41,8 +41,6 @@ maven-compiler-plugin 3.8.1 - 1.8 - 1.8 true 128m 512m @@ -281,6 +279,7 @@ + 17 UTF-8 1.6.3 1.19.4 diff --git a/samples/client/petstore/java/src/main/java/org/openapijsonschematools/configurations/JsonSchemaKeywordFlags.java b/samples/client/petstore/java/src/main/java/org/openapijsonschematools/configurations/JsonSchemaKeywordFlags.java new file mode 100644 index 00000000000..61c645d5e7e --- /dev/null +++ b/samples/client/petstore/java/src/main/java/org/openapijsonschematools/configurations/JsonSchemaKeywordFlags.java @@ -0,0 +1,134 @@ +package org.openapijsonschematools.configurations; + +import java.util.LinkedHashSet; + +public record JsonSchemaKeywordFlags( + boolean additionalProperties, + boolean allOf, + boolean anyOf, + boolean const_, + boolean contains, + boolean dependentRequired, + boolean dependentSchemas, + boolean discriminator, + boolean else_, + boolean enum_, + boolean exclusiveMaximum, + boolean exclusiveMinimum, + boolean format, + boolean if_, + boolean maximum, + boolean minimum, + boolean items, + boolean maxContains, + boolean maxItems, + boolean maxLength, + boolean maxProperties, + boolean minContains, + boolean minItems, + boolean minLength, + boolean minProperties, + boolean multipleOf, + boolean not, + boolean oneOf, + boolean pattern, + boolean patternProperties, + boolean prefixItems, + boolean properties, + boolean propertyNames, + boolean required, + boolean then, + boolean type, + boolean uniqueItems, + boolean unevaluatedItems, + boolean unevaluatedProperties + ) { + + public static JsonSchemaKeywordFlags ofNone() { + return new JsonSchemaKeywordFlags( + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false + ); + } + + public LinkedHashSet getKeywords() { + LinkedHashSet enabledKeywords = new LinkedHashSet<>(); + if (additionalProperties) { enabledKeywords.add("additionalProperties"); } + if (allOf) { enabledKeywords.add("allOf"); } + if (anyOf) { enabledKeywords.add("anyOf"); } + if (const_) { enabledKeywords.add("const_"); } + if (contains) { enabledKeywords.add("contains"); } + if (dependentRequired) { enabledKeywords.add("dependentRequired"); } + if (dependentSchemas) { enabledKeywords.add("dependentSchemas"); } + if (discriminator) { enabledKeywords.add("discriminator"); } + if (else_) { enabledKeywords.add("else_"); } + if (enum_) { enabledKeywords.add("enum_"); } + if (exclusiveMaximum) { enabledKeywords.add("exclusiveMaximum"); } + if (exclusiveMinimum) { enabledKeywords.add("exclusiveMinimum"); } + if (format) { enabledKeywords.add("format"); } + if (if_) { enabledKeywords.add("if_"); } + if (maximum) { enabledKeywords.add("maximum"); } + if (minimum) { enabledKeywords.add("minimum"); } + if (items) { enabledKeywords.add("items"); } + if (maxContains) { enabledKeywords.add("maxContains"); } + if (maxItems) { enabledKeywords.add("maxItems"); } + if (maxLength) { enabledKeywords.add("maxLength"); } + if (maxProperties) { enabledKeywords.add("maxProperties"); } + if (minContains) { enabledKeywords.add("minContains"); } + if (minItems) { enabledKeywords.add("minItems"); } + if (minLength) { enabledKeywords.add("minLength"); } + if (minProperties) { enabledKeywords.add("minProperties"); } + if (multipleOf) { enabledKeywords.add("multipleOf"); } + if (not) { enabledKeywords.add("not"); } + if (oneOf) { enabledKeywords.add("oneOf"); } + if (pattern) { enabledKeywords.add("pattern"); } + if (patternProperties) { enabledKeywords.add("patternProperties"); } + if (prefixItems) { enabledKeywords.add("prefixItems"); } + if (properties) { enabledKeywords.add("properties"); } + if (propertyNames) { enabledKeywords.add("propertyNames"); } + if (required) { enabledKeywords.add("required"); } + if (then) { enabledKeywords.add("then"); } + if (type) { enabledKeywords.add("type"); } + if (uniqueItems) { enabledKeywords.add("uniqueItems"); } + if (unevaluatedItems) { enabledKeywords.add("unevaluatedItems"); } + if (unevaluatedProperties) { enabledKeywords.add("unevaluatedProperties"); } + return enabledKeywords; + } +} \ No newline at end of file diff --git a/samples/client/petstore/java/src/main/java/org/openapijsonschematools/configurations/SchemaConfiguration.java b/samples/client/petstore/java/src/main/java/org/openapijsonschematools/configurations/SchemaConfiguration.java new file mode 100644 index 00000000000..498e6298691 --- /dev/null +++ b/samples/client/petstore/java/src/main/java/org/openapijsonschematools/configurations/SchemaConfiguration.java @@ -0,0 +1,4 @@ +package org.openapijsonschematools.configurations; + +public record SchemaConfiguration(JsonSchemaKeywordFlags disabledKeywordFlags) { +} \ No newline at end of file diff --git a/samples/client/petstore/java/src/main/java/org/openapijsonschematools/schemas/PathToSchemasMap.java b/samples/client/petstore/java/src/main/java/org/openapijsonschematools/schemas/PathToSchemasMap.java new file mode 100644 index 00000000000..915813b0f6f --- /dev/null +++ b/samples/client/petstore/java/src/main/java/org/openapijsonschematools/schemas/PathToSchemasMap.java @@ -0,0 +1,8 @@ +package org.openapijsonschematools.schemas; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class PathToSchemasMap extends HashMap, Map, Void>> { +} \ No newline at end of file diff --git a/samples/client/petstore/java/src/main/java/org/openapijsonschematools/schemas/SchemaValidator.java b/samples/client/petstore/java/src/main/java/org/openapijsonschematools/schemas/SchemaValidator.java new file mode 100644 index 00000000000..1f46de8520e --- /dev/null +++ b/samples/client/petstore/java/src/main/java/org/openapijsonschematools/schemas/SchemaValidator.java @@ -0,0 +1,130 @@ +package org.openapijsonschematools.schemas; + +import org.openapijsonschematools.schemas.validators.KeywordValidator; +import org.openapijsonschematools.schemas.validators.TypeValidator; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; + +public class SchemaValidator { + static final HashMap keywordToValidator = new HashMap(){{ + put("type", new TypeValidator()); + }}; + static PathToSchemasMap _validate( + Class cls, + Object arg, + ValidationMetadata validationMetadata + ) throws InstantiationException, IllegalAccessException { + SchemaValidator clsSchema = cls.newInstance(); + Field[] fields = cls.getDeclaredFields(); + HashMap fieldsToValues = new HashMap<>(); + LinkedHashSet disabledKeywords = validationMetadata.configuration().disabledKeywordFlags().getKeywords(); + for (Field field : fields) { + String fieldName = field.getName(); + if (disabledKeywords.contains(fieldName)) { + continue; + } + Object value = field.get(clsSchema); + fieldsToValues.put(fieldName, value); + } + PathToSchemasMap pathToSchemas = new PathToSchemasMap(); + for (Map.Entry entry: fieldsToValues.entrySet()) { + String jsonKeyword = entry.getKey(); + Object value = entry.getValue(); + KeywordValidator validatorClass = keywordToValidator.get(jsonKeyword); + PathToSchemasMap otherPathToSchemas = validatorClass.validate( + arg, + value, + null, + cls, + validationMetadata + ); + } + return pathToSchemas; + } +} +/** + * @classmethod + * def _validate( + * cls, + * arg, + * validation_metadata: ValidationMetadata, + * ) -> PathToSchemasType: + * """ + * SchemaValidator validate + * All keyword validation except for type checking was done in calling stack frames + * If those validations passed, the validated classes are collected in path_to_schemas + * """ + * cls_schema = cls() + * json_schema_data = { + * k: v + * for k, v in vars(cls_schema).items() + * if k not in cls.__excluded_cls_properties + * and k + * not in validation_metadata.configuration.disabled_json_schema_python_keywords + * } + * contains_path_to_schemas = [] + * path_to_schemas: PathToSchemasType = {} + * if 'contains' in vars(cls_schema): + * contains_path_to_schemas = _get_contains_path_to_schemas( + * arg, + * vars(cls_schema)['contains'], + * validation_metadata, + * path_to_schemas + * ) + * if_path_to_schemas = None + * if 'if_' in vars(cls_schema): + * if_path_to_schemas = _get_if_path_to_schemas( + * arg, + * vars(cls_schema)['if_'], + * validation_metadata, + * ) + * validated_pattern_properties: typing.Optional[PathToSchemasType] = None + * if 'pattern_properties' in vars(cls_schema): + * validated_pattern_properties = _get_validated_pattern_properties( + * arg, + * vars(cls_schema)['pattern_properties'], + * cls, + * validation_metadata + * ) + * prefix_items_length = 0 + * if 'prefix_items' in vars(cls_schema): + * prefix_items_length = len(vars(cls_schema)['prefix_items']) + * for keyword, val in json_schema_data.items(): + * used_val: typing.Any + * if keyword in {'contains', 'min_contains', 'max_contains'}: + * used_val = (val, contains_path_to_schemas) + * elif keyword == 'items': + * used_val = (val, prefix_items_length) + * elif keyword in {'unevaluated_items', 'unevaluated_properties'}: + * used_val = (val, path_to_schemas) + * elif keyword in {'types'}: + * format: typing.Optional[str] = vars(cls_schema).get('format', None) + * used_val = (val, format) + * elif keyword in {'pattern_properties', 'additional_properties'}: + * used_val = (val, validated_pattern_properties) + * elif keyword in {'if_', 'then', 'else_'}: + * used_val = (val, if_path_to_schemas) + * else: + * used_val = val + * validator = json_schema_keyword_to_validator[keyword] + * + * other_path_to_schemas = validator( + * arg, + * used_val, + * cls, + * validation_metadata, + * + * ) + * if other_path_to_schemas: + * update(path_to_schemas, other_path_to_schemas) + * + * base_class = type(arg) + * if validation_metadata.path_to_item not in path_to_schemas: + * path_to_schemas[validation_metadata.path_to_item] = dict() + * path_to_schemas[validation_metadata.path_to_item][base_class] = None + * path_to_schemas[validation_metadata.path_to_item][cls] = None + * return path_to_schemas + */ diff --git a/samples/client/petstore/java/src/main/java/org/openapijsonschematools/schemas/ValidationMetadata.java b/samples/client/petstore/java/src/main/java/org/openapijsonschematools/schemas/ValidationMetadata.java new file mode 100644 index 00000000000..b6745c11648 --- /dev/null +++ b/samples/client/petstore/java/src/main/java/org/openapijsonschematools/schemas/ValidationMetadata.java @@ -0,0 +1,26 @@ +package org.openapijsonschematools.schemas; + +import org.openapijsonschematools.configurations.SchemaConfiguration; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public record ValidationMetadata( + List pathToItem, + SchemaConfiguration configuration, + PathToSchemasMap validatedPathToSchemas, + Set> seenClasses +) { + + protected boolean validationRanEarlier(Class cls) { + Map, Void> validatedSchemas = validatedPathToSchemas.getOrDefault(pathToItem, null); + if (validatedSchemas != null && validatedSchemas.containsKey(cls)) { + return true; + } + if (seenClasses.contains(cls)) { + return true; + } + return false; + } + +} \ No newline at end of file diff --git a/samples/client/petstore/java/src/main/java/org/openapijsonschematools/schemas/validators/KeywordValidator.java b/samples/client/petstore/java/src/main/java/org/openapijsonschematools/schemas/validators/KeywordValidator.java new file mode 100644 index 00000000000..f341089b1d5 --- /dev/null +++ b/samples/client/petstore/java/src/main/java/org/openapijsonschematools/schemas/validators/KeywordValidator.java @@ -0,0 +1,14 @@ +package org.openapijsonschematools.schemas.validators; + +import org.openapijsonschematools.schemas.PathToSchemasMap; +import org.openapijsonschematools.schemas.SchemaValidator; +import org.openapijsonschematools.schemas.ValidationMetadata; + +public interface KeywordValidator { + PathToSchemasMap validate( + Object arg, + Object value, + Object extra, + Class cls, + ValidationMetadata validationMetadata); +} diff --git a/samples/client/petstore/java/src/main/java/org/openapijsonschematools/schemas/validators/TypeValidator.java b/samples/client/petstore/java/src/main/java/org/openapijsonschematools/schemas/validators/TypeValidator.java new file mode 100644 index 00000000000..64d78937612 --- /dev/null +++ b/samples/client/petstore/java/src/main/java/org/openapijsonschematools/schemas/validators/TypeValidator.java @@ -0,0 +1,18 @@ +package org.openapijsonschematools.schemas.validators; + +import org.openapijsonschematools.schemas.PathToSchemasMap; +import org.openapijsonschematools.schemas.SchemaValidator; +import org.openapijsonschematools.schemas.ValidationMetadata; + +import java.util.HashSet; + +public class TypeValidator implements KeywordValidator { + @Override + public PathToSchemasMap validate(Object arg, Object value, Object extra, Class cls, ValidationMetadata validationMetadata) { + HashSet> types = (HashSet>) value; + if (!types.contains(arg.getClass())) { + throw new RuntimeException("invalid type"); + } + return null; + } +} diff --git a/samples/client/petstore/java/src/test/java/org/openapijsonschematools/configurations/JsonSchemaKeywordFlagsTest.java b/samples/client/petstore/java/src/test/java/org/openapijsonschematools/configurations/JsonSchemaKeywordFlagsTest.java new file mode 100644 index 00000000000..a472ccc5bad --- /dev/null +++ b/samples/client/petstore/java/src/test/java/org/openapijsonschematools/configurations/JsonSchemaKeywordFlagsTest.java @@ -0,0 +1,143 @@ +package org.openapijsonschematools.configurations; + +import org.junit.Assert; +import org.junit.Test; +import java.util.LinkedHashSet; + +public final class JsonSchemaKeywordFlagsTest { + + @Test + public void testGetEnabledKeywords() { + final JsonSchemaKeywordFlags jsonSchemaKeywordFlags = new JsonSchemaKeywordFlags( + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true + ); + LinkedHashSet enabledKeywords = jsonSchemaKeywordFlags.getKeywords(); + LinkedHashSet expectedEnabledKeywords = new LinkedHashSet<>(); + expectedEnabledKeywords.add("additionalProperties"); + expectedEnabledKeywords.add("allOf"); + expectedEnabledKeywords.add("anyOf"); + expectedEnabledKeywords.add("const_"); + expectedEnabledKeywords.add("contains"); + expectedEnabledKeywords.add("dependentRequired"); + expectedEnabledKeywords.add("dependentSchemas"); + expectedEnabledKeywords.add("discriminator"); + expectedEnabledKeywords.add("else_"); + expectedEnabledKeywords.add("enum_"); + expectedEnabledKeywords.add("exclusiveMaximum"); + expectedEnabledKeywords.add("exclusiveMinimum"); + expectedEnabledKeywords.add("format"); + expectedEnabledKeywords.add("if_"); + expectedEnabledKeywords.add("maximum"); + expectedEnabledKeywords.add("minimum"); + expectedEnabledKeywords.add("items"); + expectedEnabledKeywords.add("maxContains"); + expectedEnabledKeywords.add("maxItems"); + expectedEnabledKeywords.add("maxLength"); + expectedEnabledKeywords.add("maxProperties"); + expectedEnabledKeywords.add("minContains"); + expectedEnabledKeywords.add("minItems"); + expectedEnabledKeywords.add("minLength"); + expectedEnabledKeywords.add("minProperties"); + expectedEnabledKeywords.add("multipleOf"); + expectedEnabledKeywords.add("not"); + expectedEnabledKeywords.add("oneOf"); + expectedEnabledKeywords.add("pattern"); + expectedEnabledKeywords.add("patternProperties"); + expectedEnabledKeywords.add("prefixItems"); + expectedEnabledKeywords.add("properties"); + expectedEnabledKeywords.add("propertyNames"); + expectedEnabledKeywords.add("required"); + expectedEnabledKeywords.add("then"); + expectedEnabledKeywords.add("type"); + expectedEnabledKeywords.add("uniqueItems"); + expectedEnabledKeywords.add("unevaluatedItems"); + expectedEnabledKeywords.add("unevaluatedProperties"); + Assert.assertEquals(enabledKeywords, expectedEnabledKeywords); + } + + @Test + public void testGetNoEnabledKeywords() { + final JsonSchemaKeywordFlags jsonSchemaKeywordFlags = new JsonSchemaKeywordFlags( + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false + ); + LinkedHashSet enabledKeywords = jsonSchemaKeywordFlags.getKeywords(); + LinkedHashSet expectedEnabledKeywords = new LinkedHashSet<>(); + Assert.assertEquals(enabledKeywords, expectedEnabledKeywords); + } +} diff --git a/samples/client/petstore/java/src/test/java/org/openapijsonschematools/schemas/validators/TypeValidatorTest.java b/samples/client/petstore/java/src/test/java/org/openapijsonschematools/schemas/validators/TypeValidatorTest.java new file mode 100644 index 00000000000..a908abea0b7 --- /dev/null +++ b/samples/client/petstore/java/src/test/java/org/openapijsonschematools/schemas/validators/TypeValidatorTest.java @@ -0,0 +1,56 @@ +package org.openapijsonschematools.schemas.validators; + +import org.junit.Assert; +import org.junit.Test; +import org.openapijsonschematools.configurations.JsonSchemaKeywordFlags; +import org.openapijsonschematools.configurations.SchemaConfiguration; +import org.openapijsonschematools.schemas.PathToSchemasMap; +import org.openapijsonschematools.schemas.SchemaValidator; +import org.openapijsonschematools.schemas.ValidationMetadata; + +import java.util.ArrayList; +import java.util.LinkedHashSet; + +public class TypeValidatorTest { + + @Test + public void testValidateSucceeds() { + final TypeValidator validator = new TypeValidator(); + LinkedHashSet> value = new LinkedHashSet<>(); + value.add(String.class); + ValidationMetadata validationMetadata = new ValidationMetadata( + new ArrayList<>(), + new SchemaConfiguration(JsonSchemaKeywordFlags.ofNone()), + new PathToSchemasMap(), + new LinkedHashSet<>() + ); + PathToSchemasMap pathToSchemasMap = validator.validate( + "hi", + value, + null, + SchemaValidator.class, + validationMetadata + ); + Assert.assertNull(pathToSchemasMap); + } + + @Test + public void testValidateFailsIntIsNotString() throws RuntimeException { + final TypeValidator validator = new TypeValidator(); + LinkedHashSet> value = new LinkedHashSet<>(); + value.add(String.class); + ValidationMetadata validationMetadata = new ValidationMetadata( + new ArrayList<>(), + new SchemaConfiguration(JsonSchemaKeywordFlags.ofNone()), + new PathToSchemasMap(), + new LinkedHashSet<>() + ); + Assert.assertThrows(RuntimeException.class, () -> validator.validate( + 1, + value, + null, + SchemaValidator.class, + validationMetadata + )); + } +} diff --git a/src/main/java/org/openapijsonschematools/codegen/generators/JavaClientGenerator.java b/src/main/java/org/openapijsonschematools/codegen/generators/JavaClientGenerator.java index 3afe4efd6f8..081d462c87b 100644 --- a/src/main/java/org/openapijsonschematools/codegen/generators/JavaClientGenerator.java +++ b/src/main/java/org/openapijsonschematools/codegen/generators/JavaClientGenerator.java @@ -136,6 +136,11 @@ protected Stability getStability() { return Stability.EXPERIMENTAL; } + @Override + public String generatorLanguageVersion() { + return "17"; + } + public JavaClientGenerator() { super(); @@ -264,6 +269,47 @@ public void processOpts() { "src/test/java/org/openapitools/schemas/CustomIsoparserTest.hbs", testPackagePath() + File.separatorChar + "schemas", "CustomIsoparserTest.java")); + supportingFiles.add(new SupportingFile( + "src/main/java/org/openapitools/schemas/ValidationMetadata.hbs", + packagePath() + File.separatorChar + "schemas", + "ValidationMetadata.java")); + supportingFiles.add(new SupportingFile( + "src/main/java/org/openapitools/schemas/PathToSchemasMap.hbs", + packagePath() + File.separatorChar + "schemas", + "PathToSchemasMap.java")); + supportingFiles.add(new SupportingFile( + "src/main/java/org/openapitools/schemas/SchemaValidator.hbs", + packagePath() + File.separatorChar + "schemas", + "SchemaValidator.java")); + + // keyword validators + supportingFiles.add(new SupportingFile( + "src/main/java/org/openapitools/schemas/validators/KeywordValidator.hbs", + packagePath() + File.separatorChar + "schemas" + File.separatorChar + "validators", + "KeywordValidator.java")); + // type + supportingFiles.add(new SupportingFile( + "src/main/java/org/openapitools/schemas/validators/TypeValidator.hbs", + packagePath() + File.separatorChar + "schemas" + File.separatorChar + "validators", + "TypeValidator.java")); + supportingFiles.add(new SupportingFile( + "src/test/java/org/openapitools/schemas/validators/TypeValidatorTest.hbs", + testPackagePath() + File.separatorChar + "schemas" + File.separatorChar + "validators", + "TypeValidatorTest.java")); + + // configuration + supportingFiles.add(new SupportingFile( + "src/main/java/org/openapitools/configurations/JsonSchemaKeywordFlags.hbs", + packagePath() + File.separatorChar + "configurations", + "JsonSchemaKeywordFlags.java")); + supportingFiles.add(new SupportingFile( + "src/test/java/org/openapitools/configurations/JsonSchemaKeywordFlagsTest.hbs", + testPackagePath() + File.separatorChar + "configurations", + "JsonSchemaKeywordFlagsTest.java")); + supportingFiles.add(new SupportingFile( + "src/main/java/org/openapitools/configurations/SchemaConfiguration.hbs", + packagePath() + File.separatorChar + "configurations", + "SchemaConfiguration.java")); // jsonPathDocTemplateFiles.put( // CodegenConstants.JSON_PATH_LOCATION_TYPE.SCHEMA, diff --git a/src/main/resources/java/README.hbs b/src/main/resources/java/README.hbs index 0a6670f1ba4..735a1677bff 100644 --- a/src/main/resources/java/README.hbs +++ b/src/main/resources/java/README.hbs @@ -7,9 +7,6 @@ This Java package is automatically generated by the [OpenAPI JSON Schema Generat - API version: {{appVersion}} - Package version: {{packageVersion}} -{{#unless hideGenerationTimestamp}} -- Build date: {{generatedDate}} -{{/unless}} - Build package: {{generatorClass}} {{#if infoUrl}} For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}}) @@ -17,7 +14,7 @@ For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}}) ## Requirements -Python {{generatorLanguageVersion}} +Java {{generatorLanguageVersion}} ## Migration Guides - [3.0.0 Migration Guide](migration_3_0_0.md) diff --git a/src/main/resources/java/pom.hbs b/src/main/resources/java/pom.hbs index e6bf5bed0bc..1e0ba83059c 100644 --- a/src/main/resources/java/pom.hbs +++ b/src/main/resources/java/pom.hbs @@ -47,8 +47,6 @@ maven-compiler-plugin 3.8.1 - 1.8 - 1.8 true 128m 512m @@ -316,6 +314,7 @@ + 17 UTF-8 1.6.3 1.19.4 diff --git a/src/main/resources/java/src/main/java/org/openapitools/configurations/JsonSchemaKeywordFlags.hbs b/src/main/resources/java/src/main/java/org/openapitools/configurations/JsonSchemaKeywordFlags.hbs new file mode 100644 index 00000000000..8b5da889f65 --- /dev/null +++ b/src/main/resources/java/src/main/java/org/openapitools/configurations/JsonSchemaKeywordFlags.hbs @@ -0,0 +1,134 @@ +package {{{packageName}}}.configurations; + +import java.util.LinkedHashSet; + +public record JsonSchemaKeywordFlags( + boolean additionalProperties, + boolean allOf, + boolean anyOf, + boolean const_, + boolean contains, + boolean dependentRequired, + boolean dependentSchemas, + boolean discriminator, + boolean else_, + boolean enum_, + boolean exclusiveMaximum, + boolean exclusiveMinimum, + boolean format, + boolean if_, + boolean maximum, + boolean minimum, + boolean items, + boolean maxContains, + boolean maxItems, + boolean maxLength, + boolean maxProperties, + boolean minContains, + boolean minItems, + boolean minLength, + boolean minProperties, + boolean multipleOf, + boolean not, + boolean oneOf, + boolean pattern, + boolean patternProperties, + boolean prefixItems, + boolean properties, + boolean propertyNames, + boolean required, + boolean then, + boolean type, + boolean uniqueItems, + boolean unevaluatedItems, + boolean unevaluatedProperties + ) { + + public static JsonSchemaKeywordFlags ofNone() { + return new JsonSchemaKeywordFlags( + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false + ); + } + + public LinkedHashSet getKeywords() { + LinkedHashSet enabledKeywords = new LinkedHashSet<>(); + if (additionalProperties) { enabledKeywords.add("additionalProperties"); } + if (allOf) { enabledKeywords.add("allOf"); } + if (anyOf) { enabledKeywords.add("anyOf"); } + if (const_) { enabledKeywords.add("const_"); } + if (contains) { enabledKeywords.add("contains"); } + if (dependentRequired) { enabledKeywords.add("dependentRequired"); } + if (dependentSchemas) { enabledKeywords.add("dependentSchemas"); } + if (discriminator) { enabledKeywords.add("discriminator"); } + if (else_) { enabledKeywords.add("else_"); } + if (enum_) { enabledKeywords.add("enum_"); } + if (exclusiveMaximum) { enabledKeywords.add("exclusiveMaximum"); } + if (exclusiveMinimum) { enabledKeywords.add("exclusiveMinimum"); } + if (format) { enabledKeywords.add("format"); } + if (if_) { enabledKeywords.add("if_"); } + if (maximum) { enabledKeywords.add("maximum"); } + if (minimum) { enabledKeywords.add("minimum"); } + if (items) { enabledKeywords.add("items"); } + if (maxContains) { enabledKeywords.add("maxContains"); } + if (maxItems) { enabledKeywords.add("maxItems"); } + if (maxLength) { enabledKeywords.add("maxLength"); } + if (maxProperties) { enabledKeywords.add("maxProperties"); } + if (minContains) { enabledKeywords.add("minContains"); } + if (minItems) { enabledKeywords.add("minItems"); } + if (minLength) { enabledKeywords.add("minLength"); } + if (minProperties) { enabledKeywords.add("minProperties"); } + if (multipleOf) { enabledKeywords.add("multipleOf"); } + if (not) { enabledKeywords.add("not"); } + if (oneOf) { enabledKeywords.add("oneOf"); } + if (pattern) { enabledKeywords.add("pattern"); } + if (patternProperties) { enabledKeywords.add("patternProperties"); } + if (prefixItems) { enabledKeywords.add("prefixItems"); } + if (properties) { enabledKeywords.add("properties"); } + if (propertyNames) { enabledKeywords.add("propertyNames"); } + if (required) { enabledKeywords.add("required"); } + if (then) { enabledKeywords.add("then"); } + if (type) { enabledKeywords.add("type"); } + if (uniqueItems) { enabledKeywords.add("uniqueItems"); } + if (unevaluatedItems) { enabledKeywords.add("unevaluatedItems"); } + if (unevaluatedProperties) { enabledKeywords.add("unevaluatedProperties"); } + return enabledKeywords; + } +} \ No newline at end of file diff --git a/src/main/resources/java/src/main/java/org/openapitools/configurations/SchemaConfiguration.hbs b/src/main/resources/java/src/main/java/org/openapitools/configurations/SchemaConfiguration.hbs new file mode 100644 index 00000000000..c63928c0d7f --- /dev/null +++ b/src/main/resources/java/src/main/java/org/openapitools/configurations/SchemaConfiguration.hbs @@ -0,0 +1,4 @@ +package {{{packageName}}}.configurations; + +public record SchemaConfiguration(JsonSchemaKeywordFlags disabledKeywordFlags) { +} \ No newline at end of file diff --git a/src/main/resources/java/src/main/java/org/openapitools/schemas/PathToSchemasMap.hbs b/src/main/resources/java/src/main/java/org/openapitools/schemas/PathToSchemasMap.hbs new file mode 100644 index 00000000000..cc8526fb767 --- /dev/null +++ b/src/main/resources/java/src/main/java/org/openapitools/schemas/PathToSchemasMap.hbs @@ -0,0 +1,8 @@ +package {{{packageName}}}.schemas; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class PathToSchemasMap extends HashMap, Map, Void>> { +} \ No newline at end of file diff --git a/src/main/resources/java/src/main/java/org/openapitools/schemas/SchemaValidator.hbs b/src/main/resources/java/src/main/java/org/openapitools/schemas/SchemaValidator.hbs new file mode 100644 index 00000000000..f731be796c4 --- /dev/null +++ b/src/main/resources/java/src/main/java/org/openapitools/schemas/SchemaValidator.hbs @@ -0,0 +1,132 @@ +package org.openapijsonschematools.schemas; + +import org.openapijsonschematools.schemas.validators.KeywordValidator; +import org.openapijsonschematools.schemas.validators.TypeValidator; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; + +public class SchemaValidator { + static final HashMap keywordToValidator = new HashMap()\{{ + put("type", new TypeValidator()); + }}; + static PathToSchemasMap _validate( + Class cls, + Object arg, + ValidationMetadata validationMetadata + ) throws InstantiationException, IllegalAccessException { + SchemaValidator clsSchema = cls.newInstance(); + Field[] fields = cls.getDeclaredFields(); + HashMap fieldsToValues = new HashMap<>(); + LinkedHashSet disabledKeywords = validationMetadata.configuration().disabledKeywordFlags().getKeywords(); + for (Field field : fields) { + String fieldName = field.getName(); + if (disabledKeywords.contains(fieldName)) { + continue; + } + Object value = field.get(clsSchema); + fieldsToValues.put(fieldName, value); + } + PathToSchemasMap pathToSchemas = new PathToSchemasMap(); + for (Map.Entry entry: fieldsToValues.entrySet()) { + String jsonKeyword = entry.getKey(); + Object value = entry.getValue(); + KeywordValidator validatorClass = keywordToValidator.get(jsonKeyword); + PathToSchemasMap otherPathToSchemas = validatorClass.validate( + arg, + value, + null, + cls, + validationMetadata + ); + } + return pathToSchemas; + } +} +/** + * @classmethod + * def _validate( + * cls, + * arg, + * validation_metadata: ValidationMetadata, + * ) -> PathToSchemasType: + * """ + * SchemaValidator validate + * All keyword validation except for type checking was done in calling stack frames + * If those validations passed, the validated classes are collected in path_to_schemas + * """ + * cls_schema = cls() + * json_schema_data = { + * k: v + * for k, v in vars(cls_schema).items() + * if k not in cls.__excluded_cls_properties + * and k + * not in validation_metadata.configuration.disabled_json_schema_python_keywords + * } + * contains_path_to_schemas = [] + * path_to_schemas: PathToSchemasType = {} + * if 'contains' in vars(cls_schema): + * contains_path_to_schemas = _get_contains_path_to_schemas( + * arg, + * vars(cls_schema)['contains'], + * validation_metadata, + * path_to_schemas + * ) + * if_path_to_schemas = None + * if 'if_' in vars(cls_schema): + * if_path_to_schemas = _get_if_path_to_schemas( + * arg, + * vars(cls_schema)['if_'], + * validation_metadata, + * ) + * validated_pattern_properties: typing.Optional[PathToSchemasType] = None + * if 'pattern_properties' in vars(cls_schema): + * validated_pattern_properties = _get_validated_pattern_properties( + * arg, + * vars(cls_schema)['pattern_properties'], + * cls, + * validation_metadata + * ) + * prefix_items_length = 0 + * if 'prefix_items' in vars(cls_schema): + * prefix_items_length = len(vars(cls_schema)['prefix_items']) + * for keyword, val in json_schema_data.items(): + * used_val: typing.Any + * if keyword in {'contains', 'min_contains', 'max_contains'}: + * used_val = (val, contains_path_to_schemas) + * elif keyword == 'items': + * used_val = (val, prefix_items_length) + * elif keyword in {'unevaluated_items', 'unevaluated_properties'}: + * used_val = (val, path_to_schemas) + * elif keyword in {'types'}: + * format: typing.Optional[str] = vars(cls_schema).get('format', None) + * used_val = (val, format) + * elif keyword in {'pattern_properties', 'additional_properties'}: + * used_val = (val, validated_pattern_properties) + * elif keyword in {'if_', 'then', 'else_'}: + * used_val = (val, if_path_to_schemas) + * else: + * used_val = val + * validator = json_schema_keyword_to_validator[keyword] + * + * other_path_to_schemas = validator( + * arg, + * used_val, + * cls, + * validation_metadata, + * {{#if nonCompliantUseDiscriminatorIfCompositionFails}} + * **kwargs + * {{/if}} + * ) + * if other_path_to_schemas: + * update(path_to_schemas, other_path_to_schemas) + * + * base_class = type(arg) + * if validation_metadata.path_to_item not in path_to_schemas: + * path_to_schemas[validation_metadata.path_to_item] = dict() + * path_to_schemas[validation_metadata.path_to_item][base_class] = None + * path_to_schemas[validation_metadata.path_to_item][cls] = None + * return path_to_schemas + */ diff --git a/src/main/resources/java/src/main/java/org/openapitools/schemas/ValidationMetadata.hbs b/src/main/resources/java/src/main/java/org/openapitools/schemas/ValidationMetadata.hbs new file mode 100644 index 00000000000..9557ac8260c --- /dev/null +++ b/src/main/resources/java/src/main/java/org/openapitools/schemas/ValidationMetadata.hbs @@ -0,0 +1,26 @@ +package {{{packageName}}}.schemas; + +import {{{packageName}}}.configurations.SchemaConfiguration; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public record ValidationMetadata( + List pathToItem, + SchemaConfiguration configuration, + PathToSchemasMap validatedPathToSchemas, + Set> seenClasses +) { + + protected boolean validationRanEarlier(Class cls) { + Map, Void> validatedSchemas = validatedPathToSchemas.getOrDefault(pathToItem, null); + if (validatedSchemas != null && validatedSchemas.containsKey(cls)) { + return true; + } + if (seenClasses.contains(cls)) { + return true; + } + return false; + } + +} \ No newline at end of file diff --git a/src/main/resources/java/src/main/java/org/openapitools/schemas/validators/KeywordValidator.hbs b/src/main/resources/java/src/main/java/org/openapitools/schemas/validators/KeywordValidator.hbs new file mode 100644 index 00000000000..8228de6fd74 --- /dev/null +++ b/src/main/resources/java/src/main/java/org/openapitools/schemas/validators/KeywordValidator.hbs @@ -0,0 +1,14 @@ +package {{{packageName}}}.schemas.validators; + +import {{{packageName}}}.schemas.PathToSchemasMap; +import {{{packageName}}}.schemas.SchemaValidator; +import {{{packageName}}}.schemas.ValidationMetadata; + +public interface KeywordValidator { + PathToSchemasMap validate( + Object arg, + Object value, + Object extra, + Class cls, + ValidationMetadata validationMetadata); +} diff --git a/src/main/resources/java/src/main/java/org/openapitools/schemas/validators/TypeValidator.hbs b/src/main/resources/java/src/main/java/org/openapitools/schemas/validators/TypeValidator.hbs new file mode 100644 index 00000000000..2baeea76b43 --- /dev/null +++ b/src/main/resources/java/src/main/java/org/openapitools/schemas/validators/TypeValidator.hbs @@ -0,0 +1,18 @@ +package {{{packageName}}}.schemas.validators; + +import {{{packageName}}}.schemas.PathToSchemasMap; +import {{{packageName}}}.schemas.SchemaValidator; +import {{{packageName}}}.schemas.ValidationMetadata; + +import java.util.HashSet; + +public class TypeValidator implements KeywordValidator { + @Override + public PathToSchemasMap validate(Object arg, Object value, Object extra, Class cls, ValidationMetadata validationMetadata) { + HashSet> types = (HashSet>) value; + if (!types.contains(arg.getClass())) { + throw new RuntimeException("invalid type"); + } + return null; + } +} diff --git a/src/main/resources/java/src/test/java/org/openapitools/configurations/JsonSchemaKeywordFlagsTest.hbs b/src/main/resources/java/src/test/java/org/openapitools/configurations/JsonSchemaKeywordFlagsTest.hbs new file mode 100644 index 00000000000..15f9d79b450 --- /dev/null +++ b/src/main/resources/java/src/test/java/org/openapitools/configurations/JsonSchemaKeywordFlagsTest.hbs @@ -0,0 +1,143 @@ +package {{{packageName}}}.configurations; + +import org.junit.Assert; +import org.junit.Test; +import java.util.LinkedHashSet; + +public final class JsonSchemaKeywordFlagsTest { + + @Test + public void testGetEnabledKeywords() { + final JsonSchemaKeywordFlags jsonSchemaKeywordFlags = new JsonSchemaKeywordFlags( + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true + ); + LinkedHashSet enabledKeywords = jsonSchemaKeywordFlags.getKeywords(); + LinkedHashSet expectedEnabledKeywords = new LinkedHashSet<>(); + expectedEnabledKeywords.add("additionalProperties"); + expectedEnabledKeywords.add("allOf"); + expectedEnabledKeywords.add("anyOf"); + expectedEnabledKeywords.add("const_"); + expectedEnabledKeywords.add("contains"); + expectedEnabledKeywords.add("dependentRequired"); + expectedEnabledKeywords.add("dependentSchemas"); + expectedEnabledKeywords.add("discriminator"); + expectedEnabledKeywords.add("else_"); + expectedEnabledKeywords.add("enum_"); + expectedEnabledKeywords.add("exclusiveMaximum"); + expectedEnabledKeywords.add("exclusiveMinimum"); + expectedEnabledKeywords.add("format"); + expectedEnabledKeywords.add("if_"); + expectedEnabledKeywords.add("maximum"); + expectedEnabledKeywords.add("minimum"); + expectedEnabledKeywords.add("items"); + expectedEnabledKeywords.add("maxContains"); + expectedEnabledKeywords.add("maxItems"); + expectedEnabledKeywords.add("maxLength"); + expectedEnabledKeywords.add("maxProperties"); + expectedEnabledKeywords.add("minContains"); + expectedEnabledKeywords.add("minItems"); + expectedEnabledKeywords.add("minLength"); + expectedEnabledKeywords.add("minProperties"); + expectedEnabledKeywords.add("multipleOf"); + expectedEnabledKeywords.add("not"); + expectedEnabledKeywords.add("oneOf"); + expectedEnabledKeywords.add("pattern"); + expectedEnabledKeywords.add("patternProperties"); + expectedEnabledKeywords.add("prefixItems"); + expectedEnabledKeywords.add("properties"); + expectedEnabledKeywords.add("propertyNames"); + expectedEnabledKeywords.add("required"); + expectedEnabledKeywords.add("then"); + expectedEnabledKeywords.add("type"); + expectedEnabledKeywords.add("uniqueItems"); + expectedEnabledKeywords.add("unevaluatedItems"); + expectedEnabledKeywords.add("unevaluatedProperties"); + Assert.assertEquals(enabledKeywords, expectedEnabledKeywords); + } + + @Test + public void testGetNoEnabledKeywords() { + final JsonSchemaKeywordFlags jsonSchemaKeywordFlags = new JsonSchemaKeywordFlags( + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false + ); + LinkedHashSet enabledKeywords = jsonSchemaKeywordFlags.getKeywords(); + LinkedHashSet expectedEnabledKeywords = new LinkedHashSet<>(); + Assert.assertEquals(enabledKeywords, expectedEnabledKeywords); + } +} diff --git a/src/main/resources/java/src/test/java/org/openapitools/schemas/validators/TypeValidatorTest.hbs b/src/main/resources/java/src/test/java/org/openapitools/schemas/validators/TypeValidatorTest.hbs new file mode 100644 index 00000000000..ea2e3373f47 --- /dev/null +++ b/src/main/resources/java/src/test/java/org/openapitools/schemas/validators/TypeValidatorTest.hbs @@ -0,0 +1,56 @@ +package {{{packageName}}}.schemas.validators; + +import org.junit.Assert; +import org.junit.Test; +import {{{packageName}}}.configurations.JsonSchemaKeywordFlags; +import {{{packageName}}}.configurations.SchemaConfiguration; +import {{{packageName}}}.schemas.PathToSchemasMap; +import {{{packageName}}}.schemas.SchemaValidator; +import {{{packageName}}}.schemas.ValidationMetadata; + +import java.util.ArrayList; +import java.util.LinkedHashSet; + +public class TypeValidatorTest { + + @Test + public void testValidateSucceeds() { + final TypeValidator validator = new TypeValidator(); + LinkedHashSet> value = new LinkedHashSet<>(); + value.add(String.class); + ValidationMetadata validationMetadata = new ValidationMetadata( + new ArrayList<>(), + new SchemaConfiguration(JsonSchemaKeywordFlags.ofNone()), + new PathToSchemasMap(), + new LinkedHashSet<>() + ); + PathToSchemasMap pathToSchemasMap = validator.validate( + "hi", + value, + null, + SchemaValidator.class, + validationMetadata + ); + Assert.assertNull(pathToSchemasMap); + } + + @Test + public void testValidateFailsIntIsNotString() throws RuntimeException { + final TypeValidator validator = new TypeValidator(); + LinkedHashSet> value = new LinkedHashSet<>(); + value.add(String.class); + ValidationMetadata validationMetadata = new ValidationMetadata( + new ArrayList<>(), + new SchemaConfiguration(JsonSchemaKeywordFlags.ofNone()), + new PathToSchemasMap(), + new LinkedHashSet<>() + ); + Assert.assertThrows(RuntimeException.class, () -> validator.validate( + 1, + value, + null, + SchemaValidator.class, + validationMetadata + )); + } +}