You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2026/06/01 18:57:30 UTC

(juneau) branch docs updated: Add fluent programmatic builder API for REST resources and mixins

This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch docs
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/docs by this push:
     new ba75ca38dd Add fluent programmatic builder API for REST resources and mixins
ba75ca38dd is described below

commit ba75ca38dde0500e710aef5194c478663c2eef4e
Author: James Bognar <ja...@apache.org>
AuthorDate: Mon Jun 1 14:57:24 2026 -0400

    Add fluent programmatic builder API for REST resources and mixins
    
    Reintroduces a public, fluent builder surface (RestBuilder / AbstractRestBuilder)
    for configuring @Rest resources and mixins programmatically (the Spring @Bean use
    case) with builder-set values winning over @Rest annotation values.
    
    - RestBuilder<SELF> / AbstractRestBuilder<R, SELF>: full self-type CRTP spine
      (the builder is the override bag); 55 @Rest members + mdcAsyncPropagation +
      the generic set() escape hatch.
    - Three flavor builders (RestServlet/RestResource/RestMixin) leave SELF open for
      true covariant chaining, each with a concrete DefaultBuilder<R> leaf.
    - Constructor trio (no-arg + Foo(RestBuilder) + Foo.Builder) with a package-private
      restBuilder stash; RestContext.Args carries it and RestContext prepends a
      synthesized @Rest so builder values beat annotations (rung-1 precedence).
    - OQ-11 mirror-and-forward for the worker-backed multi-flavor capabilities
      (FaviconMixin -> FaviconProvider, VersionMixin -> VersionProvider) via
      composition; WellKnownMixin/SeoMixin re-parented for a uniform surface.
    - BeanInstantiator exact-type builder preference (Option D): strict-typed builder
      candidates are preferred and weak supertype-only candidates are declined when a
      usable direct constructor exists, resolving the builder(Class) autodetection
      collision in mixin sub-context instantiation.
    
    Adds RestBuilder_Test and BeanInstantiator_OptionD_Test; updates the heuristic-
    pinning BeanInstantiator_Test.w09 to the new stricter selection. Also adds
    constructor-rationale Javadoc to the worker+builder constructors.
---
 pages/release-notes/9.5.0.md | 182 ++++++++++++++++++++++++++++++++-----------
 1 file changed, 136 insertions(+), 46 deletions(-)

diff --git a/pages/release-notes/9.5.0.md b/pages/release-notes/9.5.0.md
index 1c46e3c3d2..cd60d195bb 100644
--- a/pages/release-notes/9.5.0.md
+++ b/pages/release-notes/9.5.0.md
@@ -6,7 +6,7 @@ title: "Release 9.5.0"
 
 **Date:** TBD
 
-Juneau 9.5.0 is a minor release with native OpenAPI 3.1 emission (alongside Swagger v2, composed via the new four-class `org.apache.juneau.rest.docs` mixin pack — `BasicSwaggerResource` / `BasicSwaggerUiResource` / `BasicOpenApiResource` / `BasicRedocResource` — that replaces the previously-considered `apiFormat` string knob), native TOML and YAML support, BSON (Binary JSON) support for MongoDB-interoperable binary serialization, CBOR (Concise Binary Object Representation) per RFC 8949 f [...]
+Juneau 9.5.0 is a minor release with native OpenAPI 3.1 emission (alongside Swagger v2, composed via the new four-class `org.apache.juneau.rest.docs` mixin pack — `SwaggerMixin` / `SwaggerUiMixin` / `OpenApiMixin` / `RedocMixin` — that replaces the previously-considered `apiFormat` string knob), native TOML and YAML support, BSON (Binary JSON) support for MongoDB-interoperable binary serialization, CBOR (Concise Binary Object Representation) per RFC 8949 for IoT and constrained environme [...]
 
 ### Security
 
@@ -1451,7 +1451,7 @@ See [YAML Basics](/docs/topics/YamlBasics) for full documentation.
   utest suite (124k+ tests) runs at the same wall-clock as before.
 - Acceptance test: the full `BasicRestServlet` OpenAPI 3.1 document (six api-docs URLs + full
   components.schemas set) now round-trips JSON → YAML → JSON cleanly. Previously
-  `OpenApiYamlRoundTrip_Test#c01` had to scope to a single `BasicOpenApiResource` mount via
+  `OpenApiYamlRoundTrip_Test#c01` had to scope to a single `OpenApiMixin` mount via
   `noInherit={"mixins"}` to dodge the underflow; that workaround has been removed.
 
 #### Direct Iterator/Iterable/Stream/Enumeration Serialization
@@ -2308,8 +2308,13 @@ capability classes (it is retained only on the convenience-bundle bases that hav
 **New `RestMixin` base (opt-in).** A third base class `org.apache.juneau.rest.servlet.RestMixin` joins
 `RestServlet` and `RestResource` to complete the triad. It is **opt-in** — a plain `@Rest` POJO remains
 fully valid as a mixin or child via annotation-only configuration; extending `RestMixin` is a
-convenience and does not change composition semantics. (The fluent `RestBuilder` programmatic-config
-surface from TODO-143 is **not** part of this release and remains deferred.)
+convenience and does not change composition semantics. Beyond the fluent builder surface it shares with
+`RestServlet` / `RestResource` (see the builder-API entry below), `RestMixin` adds **`getHostContext()`**,
+which returns the host (mixed-into) resource's `RestContext` — backed by the mixin sub-context's parent
+linkage, so it is usable at config time and does not depend on an in-flight request — letting a mixin op
+introspect the host (e.g. enumerate the host's child resources for a navigation page) rather than its own
+childless sub-context. It returns `null` when a `RestMixin` subclass is instantiated standalone rather than
+composed via `@Rest(mixins=...)`.
 
 **Op-interfaces deleted; replaced by single-responsibility op-mixins.** `BasicRestOperations` and
 `BasicGroupOperations` are **removed**. Their endpoints are now true `@Rest(mixins=...)` mixins —
@@ -2324,25 +2329,110 @@ navigation page retains its `@HtmlDocConfig` chrome (a mixin op runs in its own
 lose the host's page decoration).
 
 **Favicon reconciliation.** The legacy `getFavIcon` op (which served `REST/favicon` → `images/juneau.png`
-through static files) is dropped in favor of the single convention `FaviconMixin`, which serves a
-classpath ICO (`/juneau-favicon.ico`) with a 30-day `Cache-Control`. This is a behavior change for the
-default favicon bytes and the configuration key.
-
-**Per-capability flavor fill-in (delegate-bean model).** Capabilities where standalone or routed-child
-deployment is a real use case gain their missing flavors as **independent classes** that delegate to a
-shared worker bean / mixin instance (so the forms cannot drift). New child-resource flavors:
+through static files) is dropped in favor of a `FaviconProvider`-backed flavor set: the flavor-neutral
+`FaviconProvider` worker bean holds the icon bytes + `Cache-Control` value and the serve logic, and the
+independent `FaviconMixin` (mixin) / `FaviconResource` (child) / `FaviconServlet` (servlet) flavors each
+hold a `FaviconProvider` and delegate to it. The default serves a classpath ICO (`/juneau-favicon.ico`)
+with a 30-day `Cache-Control` (`max-age=2592000, public`). This is a behavior change for both the default
+favicon bytes and the configuration key.
+
+**Per-capability flavor fill-in (flavor-neutral worker beans).** Capabilities where standalone or
+routed-child deployment is a real use case gain their missing flavors as **independent classes** that each
+delegate to a **flavor-neutral worker bean** — never to another flavor — so the forms cannot drift. The
+new worker beans are `FaviconProvider` / `VersionProvider` (in `rest.convention`), `AdminProvider` (in
+`rest.ops`), `HealthAggregator` (in `rest.health`), and the four view-engine raw-template dispatchers
+`JspDispatcher` / `MustacheDispatcher` / `ThymeleafDispatcher` / `FreemarkerDispatcher`; the static-files
+flavors resolve the existing `StaticFiles` worker bean from the request context
+(`RestContext.getStaticFiles()`) instead of holding a mixin instance. New child-resource flavors:
 `StaticFilesResource`, `VersionResource`, `AdminResource`, `HealthResource`, plus a `ViewResource`
 abstract base and per-engine `JspResource` / `MustacheResource` / `ThymeleafResource` /
 `FreemarkerResource`; new favicon flavors `FaviconServlet` and `FaviconResource`. `HealthServlet` /
-`HealthMixin` / `HealthResource` all delegate to a shared `HealthAggregator` worker bean (OQ-5 split of
-the former `BasicHealthResource`). All other capabilities (Swagger, SwaggerUi, OpenApi, Redoc,
-RouteIndex, Error, Stats, Htdoc, WellKnown, Seo) remain mixin-only.
+`HealthMixin` / `HealthResource` all delegate to a shared `HealthAggregator` (OQ-5 split of the former
+`BasicHealthResource`). All other capabilities (Swagger, SwaggerUi, OpenApi, Redoc, RouteIndex, Error,
+Stats, Htdoc, WellKnown, Seo) remain mixin-only.
 
 **`RestRequest.getStaticFiles()` removed.** The request-level forwarder is deleted; obtain the active
 `StaticFiles` bean via `RestContext.getStaticFiles()` (e.g. `req.getContext().getStaticFiles()`) or
 bean-store injection. `FileVar`, `Widget`, and the static-files flavors are repointed accordingly.
 `RestContext.getStaticFiles()` is unchanged.
 
+**Route-index endpoint now content-negotiated.** `RouteIndexMixin` (`/options`) returns a
+`RouteDescriptions` POJO instead of writing JSON directly, so the listing is now content-negotiated
+through the host's configured serializers: `Accept: text/html` renders a browsable table with clickable
+route links, while `application/json` / `text/xml` clients receive the same entries in their requested
+format (mirroring `NavigationMixin.getChildren(...)`). Because it now relies on the host's serializers,
+the host **must have serializers configured** (e.g. via `BasicUniversalConfig` or a `Basic*` servlet
+base) — a bare `@Rest` host with no serializers can no longer content-negotiate the listing, whereas the
+former JSON-only op did not require them. The historical `/routes` alias remains reachable via
+`-Djuneau.routeindex.path=routes`.
+
+#### Fluent programmatic builder API — `RestBuilder` / `AbstractRestBuilder` self-type CRTP (TODO-143)
+
+A public, fluent builder surface lets a `@Rest` resource, child resource, or mixin be configured
+**programmatically** — the primary use case being instantiating a configured resource bean in a Spring
+`@Bean` method — instead of (or layered over) the `@Rest` annotation. Builder-supplied values take
+**precedence** over the class's own `@Rest` annotation members.
+
+**`RestBuilder<SELF>` config surface + `AbstractRestBuilder<R, SELF>` base (full self-type CRTP).** The
+public interface `org.apache.juneau.rest.RestBuilder<SELF extends RestBuilder<SELF>>` declares one fluent
+setter per `@Rest` member, plus the programmatic-only `mdcAsyncPropagation(boolean)` knob and a
+`set(String, Object)` forward-compat escape hatch. The shared abstract base
+`AbstractRestBuilder<R, SELF extends AbstractRestBuilder<R, SELF>>` implements every setter once — recording
+into a backing `RestAnnotation.Builder` override bag and returning `self()` — and leaves the `SELF` self-type
+**open** so a builder subclass's bespoke setters chain with true covariant returns (Option B / CRTP, aligned
+with the project-wide `SELF` / `self()` convention).
+
+**Per-flavor builders with a concrete `DefaultBuilder<R>` leaf.** Each base class exposes its own self-typed
+flavor builder — `RestServlet.Builder<R, SELF>`, `RestResource.Builder<R, SELF>`, and
+`RestMixin.Builder<R, SELF>` — together with a concrete `DefaultBuilder<R>` leaf. The static factory
+`RestServlet.builder(MyRest.class)` (and the `RestResource` / `RestMixin` equivalents) returns the
+`DefaultBuilder<R>`, so the common, non-subclassed path never has to spell two type parameters:
+
+```java
+@Bean
+public MyRest myRest() {
+    return RestServlet.builder(MyRest.class)
+        .path("/foo")
+        .allowedHeaderParams("foo")
+        .build();
+}
+```
+
+A resource may also add a one-line `static Builder<MyRest> builder() { return RestServlet.builder(MyRest.class); }`
+sugar so callers can write `MyRest.builder().path("/foo")....build()`.
+
+**Constructor trio + builder-beats-annotation precedence.** Every family base provides the constructor trio:
+a no-arg constructor, a `Foo(RestBuilder)` builder-injection constructor, and a `Foo.Builder`. `build()`
+stashes the builder onto the produced instance (a package-private `restBuilder` field read non-reflectively
+via `getRestBuilder()`). At `RestContext` construction the stashed builder's override bag is turned into a
+**synthetic `@Rest` annotation prepended as the most-derived entry** of the resource's annotation chain, so
+builder-set values win over the class's own `@Rest` members (rung 1 of the existing override-resolution
+chain). Collection-valued members **replace** the annotation value rather than merging.
+
+**Mirror-and-forward builders for worker-backed capabilities (OQ-11).** For a multi-flavor capability whose
+logic lives in a flavor-neutral worker bean, the flavor builder **mirrors the worker bean's builder methods on
+its own surface and forwards** into the worker's builder, while inheriting the full `RestBuilder` surface from
+`RestMixin.Builder`. For example `FaviconMixin.create()` mirrors `bytes` / `classpath` / `cacheControl` into
+`FaviconProvider.Builder` and inherits `path` / `roleGuard` / etc.; `VersionMixin` mirrors `VersionProvider`
+the same way. This is how the three flavors share capability config without a shared flavor-builder base class
+(Java single inheritance forbids one — each flavor builder already extends its REST builder base):
+
+```java
+@Bean
+FaviconMixin favicon() {
+    return FaviconMixin.create()
+        .classpath("/myapp/icon.ico")   // mirrored → FaviconProvider.Builder
+        .path("/branding")              // inherited RestBuilder surface
+        .build();
+}
+```
+
+**Bean-instantiation preference (internal).** So the new generic `builder(Class)` factories don't hijack
+framework default instantiation, `BeanInstantiator` now prefers a builder that builds the **exact requested
+type** (or a usable direct constructor) over one that only promises a supertype. A flavor's own `create()` /
+nested `Builder` therefore wins over the inherited generic `builder(Class)` (whose `build()` erases to a base
+class), and a plain resource/mixin carrying only the inherited builder falls through to its no-arg constructor.
+
 #### Opt-in lazy `@Rest(children=...)` materialization (TODO-121)
 
 Parent resources annotated with `@Rest(lazyChildren=true)` now defer the construction of each child
@@ -3022,16 +3112,16 @@ The variable map:
 
 | Mixin | SVL variable | Default mount |
 |---|---|---|
-| `BasicSwaggerResource` | `${juneau.swagger.path:api}` | `/api/*` |
-| `BasicSwaggerUiResource` | `${juneau.swaggerui.path:swagger}` | `/swagger/*` |
-| `BasicOpenApiResource` | `${juneau.openapi.path:openapi}` | `/openapi/*`, `/openapi.json`, `/openapi.yaml` |
-| `BasicRedocResource` | `${juneau.redoc.path:redoc}` | `/redoc/*` |
-| `BasicStaticFilesResource` | `${juneau.staticfiles.path:static}` | `/static/*` |
-| `BasicVersionResource` | `${juneau.version.path:version}` | `/version` |
-| `BasicEchoResource` | `${juneau.echo.path:echo}` | `/echo/*` |
-| `BasicAdminResource` | `${juneau.admin.path:admin}` | `/admin/threads`, `/admin/heap`, `/admin/cache/flush`, `/admin/ratelimit` |
-| `BasicRouteIndexResource` | `${juneau.routeindex.path:options}` | `/options` |
-| `BasicJspResource` | `${juneau.jsp.path:jsp}` | `/jsp/*` |
+| `SwaggerMixin` | `${juneau.swagger.path:api}` | `/api/*` |
+| `SwaggerUiMixin` | `${juneau.swaggerui.path:swagger}` | `/swagger/*` |
+| `OpenApiMixin` | `${juneau.openapi.path:openapi}` | `/openapi/*`, `/openapi.json`, `/openapi.yaml` |
+| `RedocMixin` | `${juneau.redoc.path:redoc}` | `/redoc/*` |
+| `StaticFilesMixin` | `${juneau.staticfiles.path:static}` | `/static/*` |
+| `VersionMixin` | `${juneau.version.path:version}` | `/version` |
+| `EchoMixin` | `${juneau.echo.path:echo}` | `/echo/*` |
+| `AdminMixin` | `${juneau.admin.path:admin}` | `/admin/threads`, `/admin/heap`, `/admin/cache/flush`, `/admin/ratelimit` |
+| `RouteIndexMixin` | `${juneau.routeindex.path:options}` | `/options` |
+| `JspMixin` | `${juneau.jsp.path:jsp}` | `/jsp/*` |
 
 Example — relocate the Swagger UI mount to `/docs/swagger-ui`:
 
@@ -3042,11 +3132,11 @@ java -Djuneau.swaggerui.path=docs/swagger-ui -jar my-app.jar
 Three mixins remain hardcoded because their mount paths are specification-fixed and a runtime
 override would break spec-compliant clients:
 
-- `BasicFaviconResource` — `/favicon.ico` (browser convention + WHATWG HTML `rel="icon"` default).
-- `BasicSeoResource` — `/robots.txt` (RFC 9309 Robots Exclusion Protocol) and `/sitemap.xml`
+- `FaviconMixin` — `/favicon.ico` (browser convention + WHATWG HTML `rel="icon"` default).
+- `SeoMixin` — `/robots.txt` (RFC 9309 Robots Exclusion Protocol) and `/sitemap.xml`
   (sitemaps.org protocol). The two endpoints sit on separate handler methods, not a multi-path
   array, so they remain individually pinned.
-- `BasicWellKnownResource` — `/.well-known/security.txt` (RFC 8615 well-known URIs + RFC 9116
+- `WellKnownMixin` — `/.well-known/security.txt` (RFC 8615 well-known URIs + RFC 9116
   security.txt).
 
 **Migration note — multi-path collapse:** four mixins that previously declared multiple paths
@@ -3057,10 +3147,10 @@ default:
 
 | Mixin | Primary default | Removed secondary alias(es) | Migration |
 |---|---|---|---|
-| `BasicStaticFilesResource` | `/static/*` | `/htdocs/*` | `-Djuneau.staticfiles.path=htdocs` |
-| `BasicVersionResource` | `/version` | `/info`, `/about` | `-Djuneau.version.path=info` (or `about`) |
-| `BasicEchoResource` | `/echo/*` | `/debug/echo/*` | `-Djuneau.echo.path=debug/echo` |
-| `BasicRouteIndexResource` | `/options` | `/routes` | `-Djuneau.routeindex.path=routes` |
+| `StaticFilesMixin` | `/static/*` | `/htdocs/*` | `-Djuneau.staticfiles.path=htdocs` |
+| `VersionMixin` | `/version` | `/info`, `/about` | `-Djuneau.version.path=info` (or `about`) |
+| `EchoMixin` | `/echo/*` | `/debug/echo/*` | `-Djuneau.echo.path=debug/echo` |
+| `RouteIndexMixin` | `/options` | `/routes` | `-Djuneau.routeindex.path=routes` |
 
 Deployers who relied on a secondary default must either set the SVL override at startup or
 compose a second mixin instance with the override. The default `BasicStaticFiles` classpath
@@ -3120,31 +3210,31 @@ The api-docs surface is now four sibling `@Rest(mixins=...)` mixins in the new `
 
 | Mixin | Default `paths` | Owns |
 |---|---|---|
-| `BasicSwaggerResource` | `/api` | Swagger v2 spec + `SwaggerUI` swap. Standard content negotiation. |
-| `BasicSwaggerUiResource` | `/swagger` | HTML-first mount; `@Rest(defaultAccept="text/html")` so bare browser requests render the Swagger-UI view. Transitively pulls in `BasicSwaggerResource`. |
-| `BasicOpenApiResource` | `/openapi`, `/openapi.json`, `/openapi.yaml` | OpenAPI 3.1 spec + `RedocUI` swap on `/openapi`. The `.json` and `.yaml` paths are **format-pinned** — they ignore the request `Accept` header and always emit the file extension's wire format (implemented via `RestResponse.getDirectWriter(...)`, not content negotiation, so the pin survives `Accept: text/html`). Mixin declares `@Rest(serializers={YamlSerializer.class})` so YAML is available for the `/openapi` conten [...]
-| `BasicRedocResource` | `/redoc` | HTML-first mount; `@Rest(defaultAccept="text/html")`. Transitively pulls in `BasicOpenApiResource`. |
+| `SwaggerMixin` | `/api` | Swagger v2 spec + `SwaggerUI` swap. Standard content negotiation. |
+| `SwaggerUiMixin` | `/swagger` | HTML-first mount; `@Rest(defaultAccept="text/html")` so bare browser requests render the Swagger-UI view. Transitively pulls in `SwaggerMixin`. |
+| `OpenApiMixin` | `/openapi`, `/openapi.json`, `/openapi.yaml` | OpenAPI 3.1 spec + `RedocUI` swap on `/openapi`. The `.json` and `.yaml` paths are **format-pinned** — they ignore the request `Accept` header and always emit the file extension's wire format (implemented via `RestResponse.getDirectWriter(...)`, not content negotiation, so the pin survives `Accept: text/html`). Mixin declares `@Rest(serializers={YamlSerializer.class})` so YAML is available for the `/openapi` content-negoti [...]
+| `RedocMixin` | `/redoc` | HTML-first mount; `@Rest(defaultAccept="text/html")`. Transitively pulls in `OpenApiMixin`. |
 
 Pick the mixins you want and the URL surface follows. Three common compositions:
 
 ```java
 // OpenAPI 3.1 spec only — /openapi, /openapi.json, /openapi.yaml.
-@Rest(path="/api", mixins=BasicOpenApiResource.class)
+@Rest(path="/api", mixins=OpenApiMixin.class)
 public class ApiResource extends RestServlet { ... }
 
-// OpenAPI 3.1 + Redoc UI — pulls BasicOpenApiResource in transitively.
-@Rest(path="/api", mixins=BasicRedocResource.class)
+// OpenAPI 3.1 + Redoc UI — pulls OpenApiMixin in transitively.
+@Rest(path="/api", mixins=RedocMixin.class)
 public class ApiResource extends RestServlet { ... }
 
-// Everything (post-9.5.0 default for BasicRestServlet / BasicRestObject).
-@Rest(path="/api", mixins={BasicSwaggerUiResource.class, BasicRedocResource.class})
+// Everything (post-9.5.0 default for BasicRestServlet / BasicRestResource).
+@Rest(path="/api", mixins={SwaggerUiMixin.class, RedocMixin.class})
 public class ApiResource extends RestServlet { ... }
 // Serves: /api, /swagger, /openapi, /openapi.json, /openapi.yaml, /redoc
 ```
 
 Transitive resolution dedupes by class identity (`LinkedHashSet` in the framework's `collectRestMixins(...)` walk), so listing both UI mixins doesn't double-instantiate the spec mixins. Parent-chain aggregation also works — a child class's `@Rest(mixins=...)` unions with the parent's.
 
-`BasicRestServlet` and `BasicRestObject` migrated to declare `@Rest(mixins={BasicSwaggerUiResource.class, BasicRedocResource.class})` so subclasses get all six api-docs URLs by default. Apps that want to opt out (e.g. to mount only OpenAPI) can use `@Rest(noInherit={"mixins"}, mixins=...)` to cut off the inherited docs mixins.
+`BasicRestServlet` and `BasicRestResource` migrated to declare `@Rest(mixins={SwaggerUiMixin.class, RedocMixin.class})` so subclasses get all six api-docs URLs by default. Apps that want to opt out (e.g. to mount only OpenAPI) can use `@Rest(noInherit={"mixins"}, mixins=...)` to cut off the inherited docs mixins.
 
 **Breaking changes (TODO-74):**
 
@@ -3154,8 +3244,8 @@ Transitive resolution dedupes by class identity (`LinkedHashSet` in the framewor
 | `juneau.rest.apiFormat` system property | Removed. No replacement — apps that toggled this at runtime should pick mixins at compile time. |
 | `RestContext.getApiFormat()` | Removed. |
 | `RestServerConstants.API_FORMAT_SWAGGER` / `_OPENAPI` / `_BOTH` / `SYSPROP_apiFormat` / `PROPERTY_apiFormat` | Removed. |
-| `BasicRestOperations.getSwagger(...)` / `getOpenApi(...)` | Removed from the interface. `BasicRestServlet` / `BasicRestObject` mount these via `BasicSwaggerResource` / `BasicOpenApiResource` instead. |
-| `BasicGroupOperations.getChildrenSwagger(...)` / `getChildrenOpenApi(...)` (the `?Swagger` / `?OpenApi` query mirrors on group resources) | Removed. Hit `/api`, `/swagger`, `/openapi`, or `/redoc` directly — they are always mounted on `BasicRestServletGroup` / `BasicRestObjectGroup`. |
+| `BasicRestOperations.getSwagger(...)` / `getOpenApi(...)` | Removed from the interface. `BasicRestServlet` / `BasicRestResource` mount these via `SwaggerMixin` / `OpenApiMixin` instead. |
+| `BasicGroupOperations.getChildrenSwagger(...)` / `getChildrenOpenApi(...)` (the `?Swagger` / `?OpenApi` query mirrors on group resources) | Removed. Hit `/api`, `/swagger`, `/openapi`, or `/redoc` directly — they are always mounted on `BasicRestServletGroup` / `BasicRestResourceGroup`. |
 | `BasicGroupOperations.HasSwaggerQueryParam` / `HasOpenApiQueryParam` matcher inner classes | Removed (no consumers left). |
 
 Migration sketch:
@@ -3166,14 +3256,14 @@ Migration sketch:
 public class MyApi extends BasicRestServlet { ... }
 
 // 9.5.0:
-@Rest(noInherit={"mixins"}, mixins=BasicRedocResource.class)   // opt out of Swagger
+@Rest(noInherit={"mixins"}, mixins=RedocMixin.class)   // opt out of Swagger
 public class MyApi extends BasicRestServlet { ... }
 // or extend RestServlet directly and pick what you want:
-@Rest(mixins=BasicRedocResource.class)
+@Rest(mixins=RedocMixin.class)
 public class MyApi extends RestServlet implements BasicUniversalConfig { ... }
 ```
 
-Apps that subclass `BasicRestServlet` / `BasicRestObject` without setting `apiFormat` or relying on the query mirrors get the new behavior (six api-docs URLs instead of one) automatically.
+Apps that subclass `BasicRestServlet` / `BasicRestResource` without setting `apiFormat` or relying on the query mirrors get the new behavior (six api-docs URLs instead of one) automatically.
 
 #### Static-Files Mixin (TODO-75)