From e1806aafcabe355ca2b32bfeb416fb1e453f524e Mon Sep 17 00:00:00 2001 From: Andra Busoniu Date: Wed, 18 Nov 2020 09:43:10 +0100 Subject: [PATCH] Set default value & allowed values correctly for UUIDs --- .../schemagen/SchemaPropertyGenerator.java | 57 ++++++++---- .../RestJsonSchemaGeneratorTest.java | 90 ++++++++++++------- .../SchemaJsonPropertyGeneratorTest.java | 41 ++++++++- 3 files changed, 138 insertions(+), 50 deletions(-) diff --git a/src/main/java/com/mercateo/common/rest/schemagen/SchemaPropertyGenerator.java b/src/main/java/com/mercateo/common/rest/schemagen/SchemaPropertyGenerator.java index 594389e..203013a 100644 --- a/src/main/java/com/mercateo/common/rest/schemagen/SchemaPropertyGenerator.java +++ b/src/main/java/com/mercateo/common/rest/schemagen/SchemaPropertyGenerator.java @@ -27,11 +27,13 @@ import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; import com.fasterxml.jackson.annotation.JsonUnwrapped; import com.google.common.collect.ImmutableMap; +import com.mercateo.common.rest.schemagen.JsonProperty.Builder; import com.mercateo.common.rest.schemagen.generator.ImmutableJsonPropertyResult; import com.mercateo.common.rest.schemagen.generator.JsonPropertyResult; import com.mercateo.common.rest.schemagen.generator.ObjectContext; @@ -43,14 +45,17 @@ public class SchemaPropertyGenerator { - private static final Map, JsonProperty> builtins = ImmutableMap.of( // - OffsetDateTime.class, JsonProperty.builderFor(String.class).withName("n/a").withFormat("date-time").build(), // - // TODO: "full-time" is no standard JSON-schema format - OffsetTime.class, JsonProperty.builderFor(String.class).withName("n/a").withFormat("full-time").build(), // - // TODO: "uuid" is no standard JSON-schema format, the pattern is - // for a v4 UUID. Is that enough? - UUID.class, JsonProperty.builderFor(String.class).withName("n/a").withFormat("uuid") - .withPattern("^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$").build()); + private static final Map, Function> builtinsValueSerializers = ImmutableMap.of( + UUID.class, Object::toString); + + private static final Map, JsonProperty> builtins = ImmutableMap.of( // + OffsetDateTime.class, JsonProperty.builderFor(String.class).withName("n/a").withFormat("date-time").build(), + // TODO: "full-time" is no standard JSON-schema format + OffsetTime.class, JsonProperty.builderFor(String.class).withName("n/a").withFormat("full-time").build(), // + // TODO: "uuid" is no standard JSON-schema format, the pattern is + // for a v4 UUID. Is that enough? + UUID.class, JsonProperty.builderFor(String.class).withName("n/a").withFormat("uuid") + .withPattern("^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$").build()); private final ReferencedJsonPropertyFinder referencedJsonPropertyFinder; @@ -174,14 +179,34 @@ private JsonProperty determineProperty(String name, ObjectContext objectConte SchemaPropertyContext context) { JsonProperty determineObjectProperty = determineObjectProperty(name, objectContext, pathContext, context); - if (builtins.containsKey(objectContext.getRawType())) { - JsonProperty jsonPropertyTemplate = builtins.get(objectContext.getRawType()); - return JsonProperty.builderFrom(jsonPropertyTemplate).withName(name) - .withIsRequired(determineObjectProperty.isRequired()).build(); - } else { - return determineObjectProperty; - } - } + if (builtins.containsKey(objectContext.getRawType())) { + return getBuiltinsJsonProperty(name, objectContext, determineObjectProperty); + } else { + return determineObjectProperty; + } + } + + private JsonProperty getBuiltinsJsonProperty(String name, ObjectContext objectContext, + JsonProperty determineObjectProperty) { + JsonProperty jsonPropertyTemplate = builtins.get(objectContext.getRawType()); + + final Builder builder = JsonProperty.builderFrom(jsonPropertyTemplate).withName(name) + .withIsRequired(determineObjectProperty.isRequired()) + .withPath(determineObjectProperty.getPath()); + if (builtinsValueSerializers.containsKey(objectContext.getRawType())) { + final Function serialize = builtinsValueSerializers + .get(objectContext.getRawType()); + if (determineObjectProperty.getAllowedValues() != null) { + builder.withAllowedValues(determineObjectProperty.getAllowedValues().stream().map(serialize) + .collect(Collectors.toList())); + } + if (determineObjectProperty.getDefaultValue() != null) { + final Object defaultValue = determineObjectProperty.getDefaultValue(); + builder.withDefaultValue(serialize.apply(defaultValue)); + } + } + return builder.build(); + } private JsonProperty determineObjectProperty(String name, ObjectContext objectContext, PathContext pathContext, SchemaPropertyContext context) { diff --git a/src/test/java/com/mercateo/common/rest/schemagen/RestJsonSchemaGeneratorTest.java b/src/test/java/com/mercateo/common/rest/schemagen/RestJsonSchemaGeneratorTest.java index bc14b6a..abdda91 100644 --- a/src/test/java/com/mercateo/common/rest/schemagen/RestJsonSchemaGeneratorTest.java +++ b/src/test/java/com/mercateo/common/rest/schemagen/RestJsonSchemaGeneratorTest.java @@ -50,12 +50,12 @@ public class RestJsonSchemaGeneratorTest { private FieldCheckerForSchema fieldCheckerForSchema = (o, c) -> true; @Before - public void setUp() throws NoSuchMethodException { + public void setUp() { schemaGenerator = new RestJsonSchemaGenerator(); } @Test - public void createInputSchema() throws NoSuchMethodException { + public void createInputSchema() { final Method getStrings = getTestResourceMethod("getStrings", int.class, int.class); final Optional inputSchema = schemaGenerator.createInputSchema(new CallScope(TestResource.class, getStrings, new Object[0], null), fieldCheckerForSchema); @@ -63,7 +63,7 @@ public void createInputSchema() throws NoSuchMethodException { } @Test - public void createInputSchemaForBuiltins() throws NoSuchMethodException { + public void createInputSchemaForBuiltins() { final Method dateTime = getTestResourceMethod("dateTime", DateTimeParam.class); final Optional inputSchema = schemaGenerator .createInputSchema( @@ -90,7 +90,7 @@ public void createInputSchemaWithHeaderParams() { } @Test - public void createInputSchemaWithSimpleParam() throws NoSuchMethodException { + public void createInputSchemaWithSimpleParam() { final Method getStrings = getTestResourceMethod("setValue", String.class, boolean.class); final Optional inputSchema = schemaGenerator.createInputSchema(new CallScope(TestResource.class, getStrings, new Object[0], CallContext.create()), fieldCheckerForSchema); @@ -99,7 +99,7 @@ public void createInputSchemaWithSimpleParam() throws NoSuchMethodException { } @Test - public void createInputSchemaWithSimpleParamAndDefaultValue() throws NoSuchMethodException { + public void createInputSchemaWithSimpleParamAndDefaultValue() { final Method getStrings = getTestResourceMethod("setValue", String.class, boolean.class); final CallContext callContext = CallContext.create(); final Parameter parameter = callContext.builderFor(String.class) @@ -111,7 +111,7 @@ public void createInputSchemaWithSimpleParamAndDefaultValue() throws NoSuchMetho } @Test - public void createInputSchemaWithSimpleParamAndAllowedValues() throws NoSuchMethodException { + public void createInputSchemaWithSimpleParamAndAllowedValues() { final Method getStrings = getTestResourceMethod("setValue", String.class, boolean.class); final CallContext callContext = CallContext.create(); final Parameter parameter = callContext.builderFor(String.class) @@ -123,7 +123,7 @@ public void createInputSchemaWithSimpleParamAndAllowedValues() throws NoSuchMeth } @Test - public void createOutputSchemaWithVoidMethod() throws NoSuchMethodException { + public void createOutputSchemaWithVoidMethod() { final Method getStrings = getTestResourceMethod("setValue", String.class, boolean.class); final Optional outputSchema = schemaGenerator.createOutputSchema(new CallScope(TestResource.class, getStrings, new Object[0], null), fieldCheckerForSchema); @@ -131,7 +131,7 @@ public void createOutputSchemaWithVoidMethod() throws NoSuchMethodException { } @Test - public void createInputSchemaWithMultipleSimpleFormParams() throws NoSuchMethodException { + public void createInputSchemaWithMultipleSimpleFormParams() { final Method getStrings = getTestResourceMethod("setName", String.class, String.class); final Optional inputSchema = schemaGenerator.createInputSchema(new CallScope(TestResource.class, getStrings, new Object[0], CallContext.create()), fieldCheckerForSchema); @@ -141,7 +141,7 @@ public void createInputSchemaWithMultipleSimpleFormParams() throws NoSuchMethodE } @Test - public void createInputSchemaWithBeanParam() throws NoSuchMethodException { + public void createInputSchemaWithBeanParam() { final Method getStrings = getTestResourceMethod("paramBean", TestBeanParam.class); final Optional inputSchema = schemaGenerator.createInputSchema(new CallScope(TestResource.class, getStrings, new Object[0], CallContext.create()), fieldCheckerForSchema); @@ -151,7 +151,7 @@ public void createInputSchemaWithBeanParam() throws NoSuchMethodException { } @Test - public void createInputSchemaWithBeanParamAndPayload() throws NoSuchMethodException { + public void createInputSchemaWithBeanParamAndPayload() { final Method getStrings = getTestResourceMethod("paramBeanWithPayload", TestBeanParam.class, String.class); final Optional inputSchema = schemaGenerator.createInputSchema(new CallScope(TestResource.class, getStrings, @@ -162,7 +162,7 @@ public void createInputSchemaWithBeanParamAndPayload() throws NoSuchMethodExcept } @Test - public void createInputSchemaWithPathBeanParamAndPayload() throws NoSuchMethodException { + public void createInputSchemaWithPathBeanParamAndPayload() { final Method getStrings = getTestResourceMethod("paramBeanWithPayload", TestPathBeanParam.class, String.class); final Optional inputSchema = schemaGenerator.createInputSchema(new CallScope(TestResource.class, getStrings, new Object[0], CallContext.create()), fieldCheckerForSchema); @@ -172,7 +172,7 @@ public void createInputSchemaWithPathBeanParamAndPayload() throws NoSuchMethodEx } @Test - public void createInputSchemaWithNullPathBeanParam() throws NoSuchMethodException { + public void createInputSchemaWithNullPathBeanParam() { final Method getStrings = getTestResourceMethod("paramBean", TestPathBeanParam.class); final Optional inputSchema = schemaGenerator.createInputSchema(new CallScope(TestResource.class, getStrings, new Object[0], CallContext.create()), fieldCheckerForSchema); @@ -181,7 +181,7 @@ public void createInputSchemaWithNullPathBeanParam() throws NoSuchMethodExceptio } @Test - public void createInputSchemaWithPathBeanParamWithNullValue() throws NoSuchMethodException { + public void createInputSchemaWithPathBeanParamWithNullValue() { final Method getStrings = getTestResourceMethod("paramBean", TestPathBeanParam.class); final Optional inputSchema = schemaGenerator.createInputSchema(new CallScope(TestResource.class, getStrings, new Object[]{new TestPathBeanParam(null)}, CallContext.create()), fieldCheckerForSchema); @@ -191,7 +191,7 @@ public void createInputSchemaWithPathBeanParamWithNullValue() throws NoSuchMetho } @Test - public void createInputSchemaWithPathBeanParamWithValue() throws NoSuchMethodException { + public void createInputSchemaWithPathBeanParamWithValue() { final Method getStrings = getTestResourceMethod("paramBean", TestPathBeanParam.class); final Optional inputSchema = schemaGenerator.createInputSchema(new CallScope(TestResource.class, getStrings, new Object[]{new TestPathBeanParam("foo")}, CallContext.create()), fieldCheckerForSchema); @@ -200,7 +200,7 @@ public void createInputSchemaWithPathBeanParamWithValue() throws NoSuchMethodExc } @Test - public void createInputSchemaWithContextParam() throws NoSuchMethodException { + public void createInputSchemaWithContextParam() { final Method getStrings = getTestResourceMethod("context", String.class); final Optional inputSchema = schemaGenerator.createInputSchema(new CallScope(TestResource.class, getStrings, new Object[0], null), fieldCheckerForSchema); @@ -209,27 +209,41 @@ public void createInputSchemaWithContextParam() throws NoSuchMethodException { } @Test - public void createInputSchemaWithEnumParamAndAllowedValue() throws NoSuchMethodException { - final Method enumValue = getTestResourceMethod("enumValue", TestEnum.class); - final Parameter parameter = Parameter.createContext().builderFor(TestEnum.class) - .allowValues(TestEnum.FOO_VALUE).build(); + public void createInputSchemaWithIntegerAndAllowedValue() { + final Method integerMethod = getTestResourceMethod("integer", Integer.class); + final Parameter parameter = Parameter.createContext().builderFor(Integer.class) + .allowValues(10, 20).build(); + final Optional inputSchema = schemaGenerator.createInputSchema(new CallScope(TestResource.class, + integerMethod, new Object[] { parameter.get() }, parameter.context()), fieldCheckerForSchema); + + assertThat(inputSchema.isPresent()).isTrue(); + assertThat(inputSchema.get()).containsIgnoringCase("{\"type\":\"integer\",\"enum\":[20,10]}"); + } + + @Test + public void createInputSchemaWithUUIDAndAllowedValue() { + final Method uuidMethod = getTestResourceMethod("uuid", UUID.class); + final Parameter parameter = Parameter.createContext().builderFor(UUID.class) + .allowValues(UUID.fromString("156a3b9c-f9d0-447e-a412-ef6d339bf01b"), + UUID.fromString("2364568a-f9d0-447e-a412-ef6d339bf01a")).build(); final Optional inputSchema = schemaGenerator.createInputSchema(new CallScope(TestResource.class, - enumValue, new Object[] { parameter.get() }, parameter.context()), fieldCheckerForSchema); + uuidMethod, new Object[] { parameter.get() }, parameter.context()), fieldCheckerForSchema); assertThat(inputSchema.isPresent()).isTrue(); assertThat(inputSchema.get()).containsIgnoringCase( - "{\"type\":\"string\",\"enum\":[\"FOO_VALUE\"]}"); + "{\"type\":\"string\",\"enum\":[\"156a3b9c-f9d0-447e-a412-ef6d339bf01b\",\"2364568a-f9d0-447e-a412-ef6d339bf01a\"]" + + ",\"format\":\"uuid\",\"pattern\":\"^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$\"}"); } @Test - public void createInputSchemaWithEnumParamAndDefaultValue() throws NoSuchMethodException { + public void createInputSchemaWithEnumParamAndDefaultValue() { final Method enumValue = getTestResourceMethod("enumValue", TestEnum.class); final Parameter parameter = Parameter.createContext().builderFor(TestEnum.class) .defaultValue(TestEnum.FOO_VALUE).build(); final Optional inputSchema = schemaGenerator.createInputSchema(new CallScope(TestResource.class, - enumValue, new Object[]{parameter.get()}, parameter - .context()), - fieldCheckerForSchema); + enumValue, new Object[] { parameter.get() }, parameter + .context()), + fieldCheckerForSchema); assertThat(inputSchema.isPresent()).isTrue(); assertThat(inputSchema.get()).containsIgnoringCase( @@ -241,7 +255,7 @@ public void createInputSchemaWithEnumParamAndDefaultValue() throws NoSuchMethodE } @Test - public void createInputSchemaWithEnumParam() throws NoSuchMethodException { + public void createInputSchemaWithEnumParam() { final Method enumValue = getTestResourceMethod("enumValue", TestEnum.class); final Optional inputSchema = schemaGenerator.createInputSchema(new CallScope(TestResource.class, enumValue, new Object[] { null }, CallContext.create()), fieldCheckerForSchema); @@ -254,7 +268,7 @@ public void createInputSchemaWithEnumParam() throws NoSuchMethodException { } @Test - public void createInputSchemaWithEnumParamWithJsonValue() throws NoSuchMethodException { + public void createInputSchemaWithEnumParamWithJsonValue() { final Method enumValue = getTestResourceMethod("enumValueJsonValue", TestEnumJsonValue.class); final Optional inputSchema = schemaGenerator.createInputSchema(new CallScope(TestResource.class, enumValue, new Object[] { null }, CallContext.create()), fieldCheckerForSchema); @@ -418,11 +432,23 @@ public void withHeader(String body, @HeaderParam("key") String values) { // Nothing to do. } - @POST - @Path("datetime") - public void dateTime(DateTimeParam dateTimeParam) { - // Nothing to do. - } + @POST + @Path("datetime") + public void dateTime(DateTimeParam dateTimeParam) { + // Nothing to do. + } + + @POST + @Path("uuid") + public void uuid(UUID uuid) { + // Nothing to do. + } + + @POST + @Path("integer") + public void integer(Integer integer) { + // Nothing to do. + } } public static String toCamelCaseFirstLowerCase(Enum> enumInstance) { diff --git a/src/test/java/com/mercateo/common/rest/schemagen/SchemaJsonPropertyGeneratorTest.java b/src/test/java/com/mercateo/common/rest/schemagen/SchemaJsonPropertyGeneratorTest.java index e46f1d2..95063c8 100644 --- a/src/test/java/com/mercateo/common/rest/schemagen/SchemaJsonPropertyGeneratorTest.java +++ b/src/test/java/com/mercateo/common/rest/schemagen/SchemaJsonPropertyGeneratorTest.java @@ -85,6 +85,7 @@ public void testObjectDefaultValues() { public void testObjectAllowedValues() { SchemaObject allowedValues = new SchemaObject(); allowedValues.name = "foo"; + allowedValues.count = 5; final JsonProperty jsonProperty = generateSchemaProperty(ObjectContext.buildFor(SchemaObject.class) .addAllowedValues(allowedValues)); @@ -93,7 +94,7 @@ public void testObjectAllowedValues() { assertThat(properties).hasSize(2); assertThat(properties.get(0).getName()).isEqualTo("count"); - assertThat(properties.get(0).getAllowedValues()).isEmpty(); + assertThat(properties.get(0).getAllowedValues()).containsExactly(5); assertThat(properties.get(0).getDefaultValue()).isNull(); assertThat(properties.get(1).getName()).isEqualTo("name"); @@ -133,6 +134,7 @@ public void testObjectAllowedAndDefaultValues() { SchemaObject defaultValue = new SchemaObject(); defaultValue.name = "baz"; + defaultValue.count = 5; final JsonProperty jsonProperty = generateSchemaProperty(ObjectContext.buildFor(SchemaObject.class) .addAllowedValues(allowedValues).withDefaultValue(defaultValue)); @@ -142,7 +144,7 @@ public void testObjectAllowedAndDefaultValues() { assertThat(properties.get(0).getName()).isEqualTo("count"); assertThat(properties.get(0).getAllowedValues()).isEmpty(); - assertThat(properties.get(0).getDefaultValue()).isNull(); + assertThat(properties.get(0).getDefaultValue()).isEqualTo(5); assertThat(properties.get(1).getName()).isEqualTo("name"); assertThat(properties.get(1).getAllowedValues()).containsOnly("foo|bar|baz"); @@ -579,6 +581,41 @@ public void testUUIDSchemaGeneration() { final JsonProperty idJsonProperty = jsonProperty.getPropertyByName("id"); assertThat(idJsonProperty.getType()).isEqualTo(PropertyType.STRING); + assertThat(idJsonProperty.getPattern()) + .isEqualTo("^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$"); + } + + @Test + public void testUUIDSchemaGenerationAllowedValues() { + + UUIDSchemaObject allowedValues = new UUIDSchemaObject(); + allowedValues.id = UUID.randomUUID(); + + final JsonProperty jsonProperty = generateSchemaProperty(ObjectContext.buildFor(UUIDSchemaObject.class) + .addAllowedValues(allowedValues)); + + final JsonProperty idJsonProperty = jsonProperty.getPropertyByName("id"); + + assertThat(idJsonProperty.getType()).isEqualTo(PropertyType.STRING); + assertThat(idJsonProperty.getName()).isEqualTo("id"); + assertThat(idJsonProperty.getAllowedValues()).containsExactly(allowedValues.id.toString()); + assertThat(idJsonProperty.getDefaultValue()).isNull(); + } + + @Test + public void testUUIDSchemaGenerationDefaultValue() { + + UUIDSchemaObject defaultValue = new UUIDSchemaObject(); + defaultValue.id = UUID.randomUUID(); + + final JsonProperty jsonProperty = generateSchemaProperty(ObjectContext.buildFor(UUIDSchemaObject.class) + .withDefaultValue(defaultValue)); + + final JsonProperty idJsonProperty = jsonProperty.getPropertyByName("id"); + + assertThat(idJsonProperty.getType()).isEqualTo(PropertyType.STRING); + assertThat(idJsonProperty.getName()).isEqualTo("id"); + assertThat(idJsonProperty.getDefaultValue()).isEqualTo(defaultValue.id.toString()); } @Test