diff --git a/CHANGELOG.md b/CHANGELOG.md index b1d1b22c..c7a34e64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] - +### `jsonschema-module-swagger-2` +#### Added +- consider `@Schema(additionalProperties = ...)` attribute (only values `TRUE` and `FALSE`), when it is annotated on a type (not on a member) ## [4.31.1] - 2023-04-28 ### `jsonschema-generator` @@ -83,7 +85,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - enable look-up of annotations on a member's type parameter (e.g., a `Map`'s value type) - enable providing full custom schema definition to be included in `additionalProperties` or `patternProperties` - new function `TypeContext.getTypeWithAnnotation()` for finding also super type of interface with certain type annotation -- new function `TypeContext.getTypeAnnotationConsideringHierarchy()ยด for searching type annotations also on super types and interfaces +- new function `TypeContext.getTypeAnnotationConsideringHierarchy()` for searching type annotations also on super types and interfaces #### Changed - consider annotations on `Map` value types when using `Option.MAP_VALUES_AS_ADDITIONAL_PROPERTIES` diff --git a/jsonschema-module-swagger-2/src/main/java/com/github/victools/jsonschema/module/swagger2/Swagger2Module.java b/jsonschema-module-swagger-2/src/main/java/com/github/victools/jsonschema/module/swagger2/Swagger2Module.java index c76626bc..ef5ed6d7 100644 --- a/jsonschema-module-swagger-2/src/main/java/com/github/victools/jsonschema/module/swagger2/Swagger2Module.java +++ b/jsonschema-module-swagger-2/src/main/java/com/github/victools/jsonschema/module/swagger2/Swagger2Module.java @@ -32,11 +32,13 @@ import com.github.victools.jsonschema.generator.TypeScope; import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Schema; +import java.lang.reflect.Type; import java.math.BigDecimal; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.Function; @@ -60,6 +62,7 @@ public void applyToConfigBuilder(SchemaGeneratorConfigBuilder builder) { private void applyToConfigBuilder(SchemaGeneratorGeneralConfigPart configPart) { configPart.withDescriptionResolver(this.createTypePropertyResolver(Schema::description, description -> !description.isEmpty())); configPart.withTitleResolver(this.createTypePropertyResolver(Schema::title, title -> !title.isEmpty())); + configPart.withAdditionalPropertiesResolver(this.createTypePropertyResolver(this::mapAdditionalPropertiesEnumValue, Objects::nonNull)); configPart.withCustomDefinitionProvider(new ExternalRefCustomDefinitionProvider()); configPart.withSubtypeResolver(new Swagger2SubtypeResolver()); @@ -179,6 +182,30 @@ protected boolean checkNullable(MemberScope member) { .isPresent(); } + /** + * Derive the allowed type of a schema's additional properties from the given annotation. + * + * @param annotation annotation to check + * @return {@code Object.class} (if true or an external "$ref" is specified), {@code Void.class} (if forbidden) or {@code null} (if undefined) + */ + protected Type mapAdditionalPropertiesEnumValue(Schema annotation) { + if (!annotation.ref().isEmpty()) { + // prevent invalid combination of "$ref" with "additionalProperties": false + return Object.class; + } + switch (annotation.additionalProperties()) { + case TRUE: + // allow any additional properties + return Object.class; + case FALSE: + // block any additional properties + return Void.class; + default: + // fall-back on other configuration, e.g., as per Option.FORBIDDEN_ADDITIONAL_PROPERTIES_BY_DEFAULT + return null; + } + } + /** * Determine whether the given field/method is deemed read-only based on {@code @Schema(accessMode = AccessMode.READ_ONLY)}. * diff --git a/jsonschema-module-swagger-2/src/test/java/com/github/victools/jsonschema/module/swagger2/IntegrationTest.java b/jsonschema-module-swagger-2/src/test/java/com/github/victools/jsonschema/module/swagger2/IntegrationTest.java index 22d38bd2..116cee5e 100644 --- a/jsonschema-module-swagger-2/src/test/java/com/github/victools/jsonschema/module/swagger2/IntegrationTest.java +++ b/jsonschema-module-swagger-2/src/test/java/com/github/victools/jsonschema/module/swagger2/IntegrationTest.java @@ -133,12 +133,14 @@ public String getName() { } } + @Schema(additionalProperties = Schema.AdditionalPropertiesValue.FALSE) static class Foo { @Schema(implementation = PersonReference.class, accessMode = Schema.AccessMode.WRITE_ONLY) private Reference person; - @Schema(ref = "http://example.com/bar", accessMode = Schema.AccessMode.READ_ONLY) + @Schema(ref = "http://example.com/bar", accessMode = Schema.AccessMode.READ_ONLY, + additionalProperties = Schema.AdditionalPropertiesValue.TRUE) private Object bar; @Schema(anyOf = {Double.class, Integer.class}) @@ -147,6 +149,8 @@ static class Foo { @Schema(oneOf = {Boolean.class, String.class}) private Object oneOfBooleanOrString; + // on a member, the "additionalProperties" attribute is ignored + @Schema(additionalProperties = Schema.AdditionalPropertiesValue.FALSE) private TestClass test; } } diff --git a/jsonschema-module-swagger-2/src/test/java/com/github/victools/jsonschema/module/swagger2/Swagger2ModuleTest.java b/jsonschema-module-swagger-2/src/test/java/com/github/victools/jsonschema/module/swagger2/Swagger2ModuleTest.java index e3c78f69..00ce3d34 100644 --- a/jsonschema-module-swagger-2/src/test/java/com/github/victools/jsonschema/module/swagger2/Swagger2ModuleTest.java +++ b/jsonschema-module-swagger-2/src/test/java/com/github/victools/jsonschema/module/swagger2/Swagger2ModuleTest.java @@ -16,12 +16,14 @@ package com.github.victools.jsonschema.module.swagger2; +import com.github.victools.jsonschema.generator.ConfigFunction; import com.github.victools.jsonschema.generator.FieldScope; import com.github.victools.jsonschema.generator.InstanceAttributeOverrideV2; import com.github.victools.jsonschema.generator.MethodScope; import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder; import com.github.victools.jsonschema.generator.SchemaGeneratorConfigPart; import com.github.victools.jsonschema.generator.SchemaGeneratorGeneralConfigPart; +import java.util.function.BiFunction; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -69,6 +71,8 @@ public void testApplyToConfigBuilder() { Mockito.verify(this.typesInGeneralConfigPart).withDescriptionResolver(Mockito.any()); Mockito.verify(this.typesInGeneralConfigPart).withTitleResolver(Mockito.any()); + Mockito.verify(this.typesInGeneralConfigPart).withAdditionalPropertiesResolver(Mockito.any(ConfigFunction.class)); + Mockito.verify(this.typesInGeneralConfigPart).withAdditionalPropertiesResolver(Mockito.any(BiFunction.class)); Mockito.verify(this.typesInGeneralConfigPart).withCustomDefinitionProvider(Mockito.any(ExternalRefCustomDefinitionProvider.class)); Mockito.verify(this.typesInGeneralConfigPart).withSubtypeResolver(Mockito.any(Swagger2SubtypeResolver.class)); Mockito.verify(this.typesInGeneralConfigPart).getDefinitionNamingStrategy(); diff --git a/jsonschema-module-swagger-2/src/test/resources/com/github/victools/jsonschema/module/swagger2/integration-test-result-Foo.json b/jsonschema-module-swagger-2/src/test/resources/com/github/victools/jsonschema/module/swagger2/integration-test-result-Foo.json index 0032f35e..bd259c27 100644 --- a/jsonschema-module-swagger-2/src/test/resources/com/github/victools/jsonschema/module/swagger2/integration-test-result-Foo.json +++ b/jsonschema-module-swagger-2/src/test/resources/com/github/victools/jsonschema/module/swagger2/integration-test-result-Foo.json @@ -40,5 +40,6 @@ "test": { "$ref": "./TestClass-schema.json" } - } + }, + "additionalProperties": false } \ No newline at end of file