Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ public static String generateForMethodInput(Method method, SchemaOption... schem
if (StringUtils.hasText(parameterDescription)) {
parameterNode.put("description", parameterDescription);
}
applySchemaConstraints(method.getParameters()[i], parameterNode);
properties.set(parameterName, parameterNode);
}

Expand Down Expand Up @@ -256,6 +257,61 @@ private static boolean isMethodParameterRequired(Method method, int index) {
return null;
}

/**
* Applies constraint attributes from a parameter-level {@code @Schema} annotation
* (e.g. minimum, maximum, pattern, example, allowableValues) to the generated schema
* node. Description and required/requiredMode are handled separately and are not
* re-applied here.
*/
private static void applySchemaConstraints(Parameter parameter, ObjectNode parameterNode) {
var schemaAnnotation = parameter.getAnnotation(Schema.class);
if (schemaAnnotation == null) {
return;
}
if (StringUtils.hasText(schemaAnnotation.minimum())) {
tryParseDouble(schemaAnnotation.minimum(), parameterNode, "minimum");
}
if (StringUtils.hasText(schemaAnnotation.maximum())) {
tryParseDouble(schemaAnnotation.maximum(), parameterNode, "maximum");
}
if (schemaAnnotation.exclusiveMinimum()) {
parameterNode.put("exclusiveMinimum", true);
}
if (schemaAnnotation.exclusiveMaximum()) {
parameterNode.put("exclusiveMaximum", true);
}
if (schemaAnnotation.minLength() > 0) {
parameterNode.put("minLength", schemaAnnotation.minLength());
}
if (schemaAnnotation.maxLength() < Integer.MAX_VALUE) {
parameterNode.put("maxLength", schemaAnnotation.maxLength());
}
if (StringUtils.hasText(schemaAnnotation.pattern())) {
parameterNode.put("pattern", schemaAnnotation.pattern());
}
if (StringUtils.hasText(schemaAnnotation.example())) {
parameterNode.put("example", schemaAnnotation.example());
}
if (schemaAnnotation.allowableValues().length > 0) {
var enumArray = parameterNode.putArray("enum");
for (String value : schemaAnnotation.allowableValues()) {
enumArray.add(value);
}
}
if (schemaAnnotation.multipleOf() > 0) {
parameterNode.put("multipleOf", schemaAnnotation.multipleOf());
}
}

private static void tryParseDouble(String value, ObjectNode node, String fieldName) {
try {
node.put(fieldName, Double.parseDouble(value));
}
catch (NumberFormatException ignored) {
// If the value isn't a valid number, skip it.
}
}

// Based on the method in ModelOptionsUtils.
public static void convertTypeValuesToUpperCase(ObjectNode node) {
if (node.isObject()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,32 @@ void generateSchemaForMethodWithOpenApiSchemaAnnotations() throws Exception {
assertThat(schema).isEqualToIgnoringWhitespace(expectedJsonSchema);
}

@Test
void generateSchemaForMethodWithOpenApiSchemaConstraints() throws Exception {
Method method = TestMethods.class.getDeclaredMethod("schemaConstraintsMethod", int.class, String.class,
String.class);

String schema = JsonSchemaGenerator.generateForMethodInput(method);
JsonNode schemaNode = JsonParser.getJsonMapper().readTree(schema);
JsonNode properties = schemaNode.get("properties");

JsonNode chanceNode = properties.get("chance");
assertThat(chanceNode.get("description").asText()).isEqualTo("Chance percentage");
assertThat(chanceNode.get("minimum").asDouble()).isEqualTo(0.0);
assertThat(chanceNode.get("maximum").asDouble()).isEqualTo(100.0);

JsonNode codeNode = properties.get("code");
assertThat(codeNode.get("minLength").asInt()).isEqualTo(3);
assertThat(codeNode.get("maxLength").asInt()).isEqualTo(10);
assertThat(codeNode.get("pattern").asText()).isEqualTo("[A-Z]+");

JsonNode statusNode = properties.get("status");
assertThat(statusNode.get("enum")).isNotNull();
List<String> enumValues = new java.util.ArrayList<>();
statusNode.get("enum").forEach(n -> enumValues.add(n.asText()));
assertThat(enumValues).containsExactly("ACTIVE", "INACTIVE", "PENDING");
}

@Test
void generateSchemaForMethodWithObjectParam() throws Exception {
Method method = TestMethods.class.getDeclaredMethod("objectParamMethod", Object.class);
Expand Down Expand Up @@ -730,6 +756,12 @@ public void jacksonMethod(
public void nullableMethod(@Nullable String username, String password) {
}

public void schemaConstraintsMethod(
@Schema(description = "Chance percentage", minimum = "0", maximum = "100") int chance,
@Schema(minLength = 3, maxLength = 10, pattern = "[A-Z]+") String code,
@Schema(allowableValues = { "ACTIVE", "INACTIVE", "PENDING" }) String status) {
}

public void complexMethod(List<String> items, TestData data, MoreTestData moreData) {
}

Expand Down