Skip to content

Commit

Permalink
Set default value & allowed values correctly for UUIDs
Browse files Browse the repository at this point in the history
  • Loading branch information
Busoniu committed Nov 18, 2020
1 parent d9fb245 commit e1806aa
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 50 deletions.
Expand Up @@ -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;
Expand All @@ -43,14 +45,17 @@

public class SchemaPropertyGenerator {

private static final Map<Class<?>, 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<Class<?>, Function<Object, String>> builtinsValueSerializers = ImmutableMap.of(
UUID.class, Object::toString);

private static final Map<Class<?>, 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;

Expand Down Expand Up @@ -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<Object, String> 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) {
Expand Down
Expand Up @@ -50,20 +50,20 @@ 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<String> inputSchema = schemaGenerator.createInputSchema(new CallScope(TestResource.class,
getStrings, new Object[0], null), fieldCheckerForSchema);
assertThat(inputSchema.isPresent()).isFalse();
}

@Test
public void createInputSchemaForBuiltins() throws NoSuchMethodException {
public void createInputSchemaForBuiltins() {
final Method dateTime = getTestResourceMethod("dateTime", DateTimeParam.class);
final Optional<String> inputSchema = schemaGenerator
.createInputSchema(
Expand All @@ -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<String> inputSchema = schemaGenerator.createInputSchema(new CallScope(TestResource.class,
getStrings, new Object[0], CallContext.create()), fieldCheckerForSchema);
Expand All @@ -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<String> parameter = callContext.builderFor(String.class)
Expand All @@ -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<String> parameter = callContext.builderFor(String.class)
Expand All @@ -123,15 +123,15 @@ 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<String> outputSchema = schemaGenerator.createOutputSchema(new CallScope(TestResource.class,
getStrings, new Object[0], null), fieldCheckerForSchema);
assertThat(outputSchema.isPresent()).isFalse();
}

@Test
public void createInputSchemaWithMultipleSimpleFormParams() throws NoSuchMethodException {
public void createInputSchemaWithMultipleSimpleFormParams() {
final Method getStrings = getTestResourceMethod("setName", String.class, String.class);
final Optional<String> inputSchema = schemaGenerator.createInputSchema(new CallScope(TestResource.class,
getStrings, new Object[0], CallContext.create()), fieldCheckerForSchema);
Expand All @@ -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<String> inputSchema = schemaGenerator.createInputSchema(new CallScope(TestResource.class,
getStrings, new Object[0], CallContext.create()), fieldCheckerForSchema);
Expand All @@ -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<String> inputSchema = schemaGenerator.createInputSchema(new CallScope(TestResource.class, getStrings,
Expand All @@ -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<String> inputSchema = schemaGenerator.createInputSchema(new CallScope(TestResource.class,
getStrings, new Object[0], CallContext.create()), fieldCheckerForSchema);
Expand All @@ -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<String> inputSchema = schemaGenerator.createInputSchema(new CallScope(TestResource.class,
getStrings, new Object[0], CallContext.create()), fieldCheckerForSchema);
Expand All @@ -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<String> inputSchema = schemaGenerator.createInputSchema(new CallScope(TestResource.class,
getStrings, new Object[]{new TestPathBeanParam(null)}, CallContext.create()), fieldCheckerForSchema);
Expand All @@ -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<String> inputSchema = schemaGenerator.createInputSchema(new CallScope(TestResource.class,
getStrings, new Object[]{new TestPathBeanParam("foo")}, CallContext.create()), fieldCheckerForSchema);
Expand All @@ -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<String> inputSchema = schemaGenerator.createInputSchema(new CallScope(TestResource.class,
getStrings, new Object[0], null), fieldCheckerForSchema);
Expand All @@ -209,27 +209,41 @@ public void createInputSchemaWithContextParam() throws NoSuchMethodException {
}

@Test
public void createInputSchemaWithEnumParamAndAllowedValue() throws NoSuchMethodException {
final Method enumValue = getTestResourceMethod("enumValue", TestEnum.class);
final Parameter<TestEnum> parameter = Parameter.createContext().builderFor(TestEnum.class)
.allowValues(TestEnum.FOO_VALUE).build();
public void createInputSchemaWithIntegerAndAllowedValue() {
final Method integerMethod = getTestResourceMethod("integer", Integer.class);
final Parameter<Integer> parameter = Parameter.createContext().builderFor(Integer.class)
.allowValues(10, 20).build();
final Optional<String> 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<UUID> parameter = Parameter.createContext().builderFor(UUID.class)
.allowValues(UUID.fromString("156a3b9c-f9d0-447e-a412-ef6d339bf01b"),
UUID.fromString("2364568a-f9d0-447e-a412-ef6d339bf01a")).build();
final Optional<String> 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<TestEnum> parameter = Parameter.createContext().builderFor(TestEnum.class)
.defaultValue(TestEnum.FOO_VALUE).build();
final Optional<String> 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(
Expand All @@ -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<String> inputSchema = schemaGenerator.createInputSchema(new CallScope(TestResource.class,
enumValue, new Object[] { null }, CallContext.create()), fieldCheckerForSchema);
Expand All @@ -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<String> inputSchema = schemaGenerator.createInputSchema(new CallScope(TestResource.class,
enumValue, new Object[] { null }, CallContext.create()), fieldCheckerForSchema);
Expand Down Expand Up @@ -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<? extends Enum<?>> enumInstance) {
Expand Down

0 comments on commit e1806aa

Please sign in to comment.