Merge pull request 'feat(ext-openapi): a route can say it is not part of the API' (#23) from feat/openapi/undocumented into master
Publish Maven packages / publish (push) Successful in 2m56s

This commit was merged in pull request #23.
This commit is contained in:
2026-09-23 15:47:37 +00:00
4 changed files with 57 additions and 1 deletions
@@ -33,6 +33,18 @@ Every class-based route is documented, annotated or not. Read off the code:
Annotations add what the code cannot say: prose, extra statuses, examples. They never repeat it.
## Leaving a route out
```java
@GET("/healthz")
@Undocumented
public final class Health extends RequestHandler { ... }
```
Every route is documented, so a document never lies by omission. `@Undocumented` says a route is
not part of the API — a health check, an internal callback, something on its way out. On a base
class it leaves out every handler written against it.
## Request bodies
A handler that extends `BodyHandler``JsonHandler` and `XmlHandler`, and anything else that
@@ -65,8 +65,13 @@ public final class OpenApiBuilder {
public OpenApiBuilder description(String description) { this.description = description; return this; }
void setContributorRegistry(OpenApiContributorRegistry registry) { this.contributorRegistry = registry; }
/** Documents one route. {@code op} is optional: a route without it is still an operation. */
/**
* Documents one route. {@code op} is optional: a route without it is still an operation.
* A handler marked {@link Undocumented} is left out entirely.
*/
public void addOperation(Route route, ApiOperation op, Class<?> handlerClass) {
if (handlerClass.isAnnotationPresent(Undocumented.class)) return;
String path = normalizePath(route.path());
String method = route.method().name().toLowerCase(Locale.ROOT);
@@ -0,0 +1,21 @@
package dev.relism.flash.ext.openapi;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Keeps a route out of the published document.
*
* <p>Every class-based route is documented, which is what stops a document from lying by
* omission. Some routes are not part of the API anyway — a health check, an internal callback,
* something on its way out — and this says so, once, where the handler is.
*
* <p>Inherited: on a base class it leaves out every handler written against it.
*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Undocumented {}
@@ -220,6 +220,24 @@ class OpenApiBuilderTest {
assertEquals("array", schema.get("type"));
}
@GET("/internal")
@Undocumented
static class InternalHandler extends RequestHandler {
@Override public Object handle(Request request, Response response) { return null; }
}
@Test
void a_route_that_says_it_is_not_part_of_the_api_is_left_out() {
OpenApiBuilder b = new OpenApiBuilder();
b.addOperation(OpenApiBuilder.routeOf(InternalHandler.class), null, InternalHandler.class);
b.addOperation(OpenApiBuilder.routeOf(BareHandler.class), null, BareHandler.class);
Map<String, Object> paths = cast(b.build().get("paths"));
assertFalse(paths.containsKey("/internal"));
assertTrue(paths.containsKey("/bare"), "the others are still documented");
}
@Test
void a_route_with_no_annotations_is_still_documented() {
OpenApiBuilder b = new OpenApiBuilder();