Skip to content

NMS-20263: Improve shipped OpenAPI/Swagger Docs - #8817

Open
marshallmassengill wants to merge 7 commits into
foundation-2026from
mm/NMS-20263-smoke
Open

NMS-20263: Improve shipped OpenAPI/Swagger Docs#8817
marshallmassengill wants to merge 7 commits into
foundation-2026from
mm/NMS-20263-smoke

Conversation

@marshallmassengill

Copy link
Copy Markdown
Contributor

After some user feedback in mattermost it's pretty apparent we need to add response/request examples and schemas to pretty much all of our API docs. This gets that done in bulk so it's a hefty update but it does not touch any runtime logic.

In theory, every operation in both v1 and v2 APIs now carries a summary, a unique operationId, a request schema and example where it reads a body, and a schema and example for each status code it can actually return. Each was exercised against a running instance, so the annotations describe observed behavior rather than intent.

I leveraged multiple claude agents to do this in one go so I would treat this as a bulk operation on the premise that some documentation that could be wrong is better than a complete void of documentation.

It definitely will need further refinement and review. I've spot checked a handful of these and they do seem accurate enough that someone can more easily sort out how the API works and it's a meaningful improvement over what we have today.

This one is a "I'd claim assisted but that feels like cheating" by Anthropic Claude Opus 5 (x10).

External References

Seven modules carry JAX-RS resources or their DTOs but had no
swagger-annotations dependency, so their operations could not be annotated
at all. Scope is provided: the jar already ships in the webapp's WEB-INF/lib.
… and examples

Every one of the 356 v2 operations now carries a summary, a unique
operationId, a request schema and example where it reads a body, and a
schema and example per reachable status code. Each was exercised against a
running instance and the annotations describe what it did, including where
that differs from what the code appears to intend.

Operations with a summary go from 93 to 355, response contents with a schema
from 65 of 610 to 682 of 693, and response examples from none to 653.
Generator-assigned operationIds such as createSpecific_14 drop from 213 to 5.
Sixty-seven operations carried duplicate parameter declarations, which is
invalid OpenAPI; none do now.

The generic collection operations come from AbstractDaoRestServiceWithDTO,
which is generic, so a single annotation there would give every DAO-backed
resource the same operationId and no per-family entity schema. Each concrete
resource instead declares delegating overrides that call super and carry only
swagger annotations. No runtime logic changes.

Handlers that assemble an ad-hoc Map have no type to reference, so this adds
documentation-only classes describing those shapes. The bsm response DTOs
that carry a custom @JsonSerialize are documented by example alone: swagger-core
2.1.12 introspects such serializers through a Jackson method removed after
2.12, which aborts document generation.
… and examples

All 310 v1 operations now carry a summary, a unique operationId, and a schema
and example per reachable status code, each verified against a running
instance. Operations with a summary go from 32 to 310, response contents with
a schema from 273 of 494 to 606 of 606, and response examples from none to 516.

Where behaviour and intent disagree, the annotations record the behaviour: v1
error bodies are text/plain even under a JSON @produces, form-encoded keys are
bean property names rather than the hyphenated names the read endpoints emit,
several fields are XML attributes and are dropped if sent as elements, dates
are epoch milliseconds in JSON and ISO-8601 in XML from the same field, and a
node may be addressed either by database id or by foreignSource:foreignId.

The /config family is documented for the first time; several of its resources
carried no annotations at all. Its writes re-marshal the whole target file,
which is noted on the tag because it discards comments in the shipped etc XML.

The /measurements component schemas named properties the API rejects: those
model classes carry Jackson 1 annotations and JAXB names that the generator
cannot see, and three @XmlValue properties were missing from the schema
entirely, leaving no way to express an expression or a filter parameter value.
A request assembled purely from the corrected schema now succeeds.
marshallmassengill and others added 4 commits August 26, 2026 11:08
…bserved behaviour

Reviewer feedback was that the wording was too much and should state only what
is true. Every description now holds facts a reader can check against a running
instance. Advice, unmeasured performance claims, guesses at intent, appeals to
the reader and disclaimer-style negatives are gone, and the long tag overviews
are shorter. URL trees, parameter lists and enumerated values are untouched.

Rendered prose drops from 128969 to 118763 characters in v2 and from 156608 to
142234 in v1, roughly 24600 characters. Operation count, operationIds,
summaries, request and response schemas and every example are unchanged.

Four descriptions were wrong rather than merely wordy. All nine enlinkd DTO
pairs described both the create and the last-poll timestamp as a poll
timestamp. EventConfEventEditRequest said both members were optional while its
operation documents a 500 for a body carrying only `enabled`. The v1 event
publish description treated `uei` as required, contradicting the documented
empty-body 202. Nineteen literal newline escapes inside text blocks in the
heatmap descriptions rendered as a line break mid-sentence.

Seven places where a description and its response set disagreed are reconciled
against the instance rather than by deleting the prose. `GET /nodes/service-types`
answers 500 for an XML Accept, `PUT` and `DELETE /ipinterfaces/{id}` answer 400
for an ambiguous address, `POST /ksc` answers 415, `POST /events` does consume
`application/atom+xml`, and the three `/acks` reads answer 403 for a caller
holding none of the roles the filter chain requires, as the alarm reads already
documented. The status business-service list serializes as `businessservices`,
so the example that showed `business-services` was corrected; the sibling
`/business-services` list does emit the hyphenated key. Bridge elements are one
per discovered VLAN, with a single null-vlan entry when the device has no VLAN
table, not one per bridge base address.
…undles

The swagger annotations have runtime retention, so adding them to classes
in bundle-packaged modules made bnd emit a mandatory Import-Package for
io.swagger.v3.oas.annotations and its subpackages. Sentinel does not ship
that bundle, so its feature resolution failed on
org.opennms.features.measurements.api and every sentinel smoke test timed
out waiting for a health check.

The annotations are only read at build time by opennms-openapi-docs, which
works off the compile classpath, so an optional import is enough. This
matches what opennms-config-model and features/events/api already do.
Every operation's documented status codes, error bodies and examples were
re-verified against the handlers, the shared helpers and the DAOs, with
read-only live probes where the code path was ambiguous. Fixes the claims
that did not survive:

- v1 error bodies: ~75 4xx/5xx responses across seven services declared
  application/json where OnmsRestService's helpers emit text/plain
- v2 Minion and MonitoringLocation JSON/XML PUT: an unknown id is not a
  404, saveOrUpdate inserts a new row and answers 204
- unknown query parameters in v1 node/interface/ifservices resources fail
  with 500 'Unknown entity: null' rather than being ignored; the v1
  minions filter 400 is likewise really a 500
- situations: unknown alarm ids fail with 500 rather than being dropped,
  the _s behaviour claim was wrong in both directions, the 403 examples
  now quote SecurityHelper's real messages, and the update/memo/journal/
  ticket operations document their reachable 403
- unreachable responses corrected: v1 whoami 204 (really 404), category
  availability 404 (rewrapped as 500), eventconf delete 400 (mapped to
  500), web-assets fallback 404 (broken list mutation, 500)
- wrong facts: KSC timespan list missing twelve valid values, the three
  datacollection child filters fall back to sorting by name rather than
  createdTime, wrong schema classes on the v1 trapd and datacollection
  config resources, kebab-case JSON keys on the snmp config resource,
  a malformed JSON example in GraphRestService, totalCount:null examples
  contradicted by JaxbListWrapper's count fallback, and ten
  datacollection operations missing their real 500 response
- the v1 trapd operationId no longer collides with the v2 one

The Jackson 2 @JsonProperty annotations added to the measurements model
for the OpenAPI generator are now named explicitly so a Jackson 2
consumer would serialize the real wire name 'value', and the new
com.fasterxml.jackson.annotation import on that bundle is optional, in
line with the swagger imports. Also drops two unused imports and an
unrelated license-header reflow.
Pointing the v1 trapd response at org.opennms.netmgt.config.trapd.TrapdConfiguration
broke document generation: swagger-core introspects the class with Jackson, which
rejects its conflicting array and List setters for snmpv3User. Document the
operation by example only, as the BSM responses with custom serializers already
are. The v2 TrapdConfigDto is not a substitute here because the v1 response
carries the XSD attribute names.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant