Repository navigation
Conversation
Add tools/api_contracts_to_typescript.py and `make build-api-contracts`, which turn the internal OpenAPI spec into static/app/utils/api/apiContracts.generated.ts: a type per component schema, plus ApiMapping from each KnownSentryApiUrls route to the response type of each method that declares one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
billyvg
force-pushed
the
billyvg/feat/apidocs-internal-spec
branch
from
November 3, 2026 20:44
c781b98 to
ba526f9
Compare
billyvg
force-pushed
the
billyvg/feat/api-contracts-to-typescript
branch
from
November 3, 2026 20:44
4cd9af6 to
1bfb1e5
Compare
…contracts ApiMapping spelled out a response whose schema isn't a component, such as the ones the legacy api-docs JSON declares inline, and every component repeated its enums inline, such as the 598 timezones three types share. ApiMapping now holds only type names. A response that isn't a bare component reference is declared as `<OperationId>Response` (the method and route name it when there is no usable operation id), once per operation however many routes reach it, with a number after it when the name is taken. An enum with more than one member that types properties in more than one place is declared once, as `<Property>Enum`, after the property it types most often. Like drf-spectacular's enum hook, a blank or null member stays at the use site (`TimezoneEnum | ''`), so a choice set is one enum whether or not a field also accepts a blank or null, and enums that would share a name are each qualified by the first type that has them. One-member enums are discriminators, such as `type: 'http'`, and stay inline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why?
#129315 types
apiOptionsresponses from contracts generated from the backend. This pulls out the generator that writesstatic/app/utils/api/apiContracts.generated.ts, so it can be reviewed apart from the frontend changes.Stacked on the PR below, which builds the internal spec this reads:
How?
make build-api-contractsrunsmake build-internal-api-docs, thentools/api_contracts_to_typescript.py, then oxfmt. The script writes:<OperationId>Responsefor each success body that isn't a bare component reference, such as the ones the legacyapi-docsJSON declares inline<Property>Enumfor each enum of two or more members that types properties in more than one place, such as the 598 timezones three types share, so it's declared onceApiMapping, from eachKnownSentryApiUrlsroute to{response: T}for each method that declares a success body, with a comment giving the operation and its publish status.Tis always a type name.Routes the frontend can't name are skipped. A route spelled with a literal alternation, such as
(?:issues|groups), is mapped under every path inx-sentry-path-aliases. Where a path is spelled directly and also reached as another route's alias, the direct spelling wins.Decisions
unknown. Responses markedx-sentry-undocumented, and ones with no body, get noApiMappingentry, so they don't look like contracts. A type hint the build couldn't resolve (x-sentry-unresolved) does becomeunknown, since the rest of its schema is still useful.x-sentry-*extension names itself. A test intests/sentry/apidocs/internal/test_generator.pychecks they match the names the internal build writes.postprocess_schema_enums. Turned on for the internal build, that hook walks only a component's top-level properties, so most repeated enums stay inline. It also compares choice labels as well as values, so the timezones still came out as two 598-member enums. And it names collisions with a hash of the choices (Status42cEnum), which changes whenever the choices do. Here, as in that hook, a blank or null member stays at the use site (TimezoneEnum | ''). Enums that would share a name are each qualified by the first type that has them (GroupDetailsResponseStatusEnum).@sentry/sort-interface-keys. Arrays areT[]orArray<T>astypescript/array-typerequires, and enum literals are sorted so the output doesn't change between builds.Not in this PR: the generated file, the CI job that regenerates and commits it, the knip entry,
apiExamples.generated.ts, andapiOptions.contract(). Until #129568 lands,make build-api-contractsfails at the internal spec's--validatestep on the csrf endpoint.🤖 Generated with Claude Code