diff --git a/flash-extensions/flash-ext-openapi/README.md b/flash-extensions/flash-ext-openapi/README.md index e32d760..2ced2e2 100644 --- a/flash-extensions/flash-ext-openapi/README.md +++ b/flash-extensions/flash-ext-openapi/README.md @@ -128,6 +128,25 @@ Field-level exclusion: `@Schema(hidden = true)`, `@SchemaProperty(hidden = true) `@NotBlank`, `@NotEmpty`, `@Size`, `@Min`, `@Max`, `@Email`, `@Pattern`) become the schema's own bounds and required fields, so a rule is written once and documented for free. +A `@Pattern` that declares a message says it in the description too, after whatever the property +already said: a regex is precise and unreadable, and both belong in the document. + +```java +@SchemaProperty(description = "Unique in the project.") +@Pattern(regexp = "[a-z.]+", message = "uses lowercase letters and dots") +String key +``` + +```yaml +key: + type: string + description: Unique in the project. Uses lowercase letters and dots. + pattern: '[a-z.]+' +``` + +No other constraint does this: `required`, `maxLength` and `format: email` are already readable, +and repeating them as prose would be noise. + ## Contributor API OpenAPI is extension-agnostic. Other extensions contribute through `OpenApiContributor`, held in diff --git a/flash-extensions/flash-ext-openapi/src/main/java/dev/relism/flash/ext/openapi/ConstraintHints.java b/flash-extensions/flash-ext-openapi/src/main/java/dev/relism/flash/ext/openapi/ConstraintHints.java index a92a8aa..971a835 100644 --- a/flash-extensions/flash-ext-openapi/src/main/java/dev/relism/flash/ext/openapi/ConstraintHints.java +++ b/flash-extensions/flash-ext-openapi/src/main/java/dev/relism/flash/ext/openapi/ConstraintHints.java @@ -64,7 +64,10 @@ final class ConstraintHints { if (field.isAnnotationPresent(Email.class)) property.putIfAbsent("format", "email"); Pattern pattern = field.getAnnotation(Pattern.class); - if (pattern != null) property.putIfAbsent("pattern", pattern.regexp()); + if (pattern != null) { + property.putIfAbsent("pattern", pattern.regexp()); + describe(property, pattern.message()); + } if (field.isAnnotationPresent(NotBlank.class) && isString) property.putIfAbsent("minLength", 1); if (field.isAnnotationPresent(NotEmpty.class)) { @@ -78,6 +81,25 @@ final class ConstraintHints { } /** Constraint annotations this bridge understands, for documentation and tests. */ + /** + * Says in words what a regex says in symbols. + * + *
Only {@code @Pattern} needs this: every other constraint has a keyword a reader
+ * understands — {@code required}, {@code maxLength}, {@code format: email} — and repeating it
+ * as prose would be noise. A regex has none, so the rule's own message goes in the
+ * description beside it, after whatever the property already said.
+ */
+ private static void describe(Map