Skip to content

Serve the OpenAPI document at /openapi.json, as NodeNorm Redis does - #49

Draft
gaurav wants to merge 8 commits into
biothings:mainfrom
TranslatorSRI:serve-openapi-at-root
Draft

gaurav wants to merge 8 commits into
biothings:mainfrom
TranslatorSRI:serve-openapi-at-root

Conversation

@gaurav

@gaurav gaurav commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Builds on #42 — review only the last commit, "Serve the OpenAPI document at /openapi.json". Until #42 merges, this PR's diff against main includes it.

#42 makes /openapi.json redirect to /webapp/openapi.json. This PR serves the document at /openapi.json directly instead, which is where NodeNorm Redis (FastAPI's default) and most Translator services serve theirs, and points the Swagger UI at / there. Clients that fetch /openapi.json get the document without following a redirect, and the root address becomes the canonical one.

Calls worth overruling

  • /webapp/openapi.json keeps working. It costs nothing, because the existing /webapp/(.*) asset handler already serves the file. Babel Validation's tests/targets.ini sets NodeNormOpenAPIPath = webapp/openapi.json for the ES deployments, and the Translator diagram's components/nodenorm-es.yaml does the same, so removing that address would break both. Once this is deployed, both can drop the override and use the default.
  • The file stays at src/nodenorm/webapp/openapi.json. Only the route changes; /status and the tests read the file there, so moving it would only add churn.

For the reviewer

The same change could be applied to NameRes ES (biothings/NameResolutionAPI), which has the same /webapp layout and serves its document only at /webapp/openapi.json, unlike NameRes Redis. The node annotator is in the same position. This PR changes only NodeNorm ES. If you'd like NameRes ES to match, it would be a separate PR in that repository (which could also escape the dot in its /favicon.ico route, as #42 does here), and the Translator diagram's component-metadata.md, which currently calls webapp/openapi.json the intended convention for these services, would need updating too.

Verification

On e33e84d, the host-free tests (test_openapi.py, test_cors.py, test_health.py, test_version.py) pass. They check that both addresses return the document with a JSON content type, that /openapiXjson is a 404, and that the Swagger UI loads /openapi.json. The Swagger UI wasn't opened in a browser.

🤖 Generated with Claude Code

gaurav and others added 8 commits October 12, 2026 00:41
The document was copied from NodeNorm Redis and never pruned, so it
advertised TRAPI 1.5 (info.x-trapi), carried 145 schemas no path
referenced, named RENCI's developer as the contact and sent readers to
/get_curie_prefixes, which this service has never served. It also
failed openapi-spec-validator: the /get_allowed_conflations response
had no description, and FilterKgraphTopNParameters.max_edges declared
maximum 0 (NCATSTranslator/NodeNormalization#409).

Rewritten from scratch against build_handlers(): the six routes this
service registers, nothing else. Responses that were `schema: {}` now
have schemas (NormalizedNodes, SetIDResponse), the FastAPI 422 responses
are replaced by the 400 the Tornado handlers raise, and the examples are
taken from the live CI instance. The servers block lists the ci and
test deployments at ITRB instead of RENCI. Contact is Will Liu and the
team is DOGSURF (biothings#35); infores and
termsOfService are unchanged for now. info.version is bumped to 1.1.0
because /status reports it and the response shapes change in the next
commit.

handlers/curie_prefix.py was an unregistered copy of the semantic-types
handler (its class is even named SemanticTypeHandler), so it goes too.

Refs biothings#35, biothings#36

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
GET /get_allowed_conflations finished a bare list, and Tornado's write()
refuses a top-level list ("Lists not accepted for security reasons"),
so it returned HTTP 500 on every deployment. It now returns
{"conflations": [...]}, which is also what NodeNorm Redis returns.

GET /get_semantic_types lower-cased every class ("biolink:namedthing");
NodeNorm Redis returns the Biolink class URI as is ("biolink:NamedThing").

/openapi.json now redirects to /webapp/openapi.json so the FastAPI
default address used by NodeNorm Redis, SmartAPI and Babel Validation
works here too; it is excluded from OpenTelemetry like the other static
paths.

The POST set-ID test posted a single object where the handler expects
a list of sets, and the normalized-nodes docstring showed a "curie" key
where the handler reads "curies"; both corrected. POST /get_setid still
answers with an object keyed "0", "1", ... (the same Tornado limitation);
returning a list as NodeNorm Redis does is a breaking change, so it is
left to its own pull request.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The document is hand-written and served as a static file, so these
tests are what stops it drifting: the spec validates; its paths are
exactly the routes build_handlers() registers; each path documents the
GET/POST methods its handler defines; each GET documents the query
parameters its handler reads (found by scanning the handler source for
get_argument calls); and the SetIDResponse schema lists the dataclass's
fields. Each was checked to fail when the spec is mutated.

Also a regression test for /get_allowed_conflations, which returned
HTTP 500 until the previous commit: it mounts just that handler and
validates the body against the documented ConflationList schema.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
It ran only against the su10 Elasticsearch host before. This mounts the
handler alone with a fake Elasticsearch client and validates the body
against the documented schema.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dot in each redirect pattern was unescaped, so /openapiXjson and
/faviconXico redirected too. The OpenTelemetry exclusions already escape
it. A test now checks the /openapi.json redirect and that a near-miss
path is a 404.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ment

The query-parameter check scanned only get() for get_argument calls, so
a parameter read in a helper method would drift unnoticed; it now scans
the whole handler class. A new check compares the fields each post()
reads with .get("...") against the properties of the documented request
body (CurieList, and the SetIDQuery items of the /get_setid array).
Both were confirmed to fail when a field is removed from the document or
a parameter is renamed in a helper.

The checks still only see literal names; the README now says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/openapi.json redirected to /webapp/openapi.json. It now serves the
document itself, which is where NodeNorm Redis (FastAPI's default) and
most Translator services serve theirs, and the Swagger UI at / loads it
from there.

/webapp/openapi.json keeps working through the existing /webapp asset
handler, because Babel Validation's targets.ini and the Translator
diagram's nodenorm-es component are configured with that address.
Removing it would break them and gain nothing; they can switch to the
default once this is deployed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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