Skip to content

Improve Canton openApi Docs generating directly from annotated protos#2

Open
monsieurleberre wants to merge 4 commits into
mainfrom
experiment/google-api-http-full-coverage
Open

Improve Canton openApi Docs generating directly from annotated protos#2
monsieurleberre wants to merge 4 commits into
mainfrom
experiment/google-api-http-full-coverage

Conversation

@monsieurleberre

@monsieurleberre monsieurleberre commented May 11, 2026

Copy link
Copy Markdown

Before : SwaggerHub with current openApi Docs
After : SwaggerHub with proto generated openApi Docs


Extends #1 from a single service to every HTTP-exposed RPC of the JSON Ledger API.

Builds on the same design and rationale — see #1 for the why.

What's done

Intentional deviations from tapir

  • StateService.GetActiveContractsPage is POST, not GET. The tapir route uses GET-with-body, which grpc-ecosystem/openapiv2 rejects and which OpenAPI generally disallows. The request carries a required EventFormat message that doesn't flatten to query params.
  • Server-streaming RPCs (CompletionStream, GetActiveContracts, GetUpdates) get the POST list mapping only. The WebSocket GET form is JSON-API-only and stays in the AsyncAPI doc.

Deliberately not annotated

  • 8 deprecated legacy aliases targeted for 3.5.0 removal (/v2/commands/submit-and-wait-for-transaction-tree, /v2/package-vetting, /v2/updates/{flats,trees,transaction-*}).
  • /v2/authenticated-user — custom JSON-only, no gRPC backing.
  • Non-HTTP-exposed RPCs (TimeService, CommandInspectionService, ParticipantPruningService, PackageManagementService.ListKnownPackages, PartyManagementService.UpdatePartyIdentityProviderId).

Left to do (not in this PR)

  • Tapir route alignment: decide whether to update tapir's GetActiveContractsPage to POST so the JSON-API and the proto-derived spec agree on that one route.
  • Decide whether to additionally cover the deprecated legacy aliases via additional_bindings for the 3.4 → 3.5 transition window, or treat their removal from the spec as a feature of this change.
  • Wire the proto-derived OpenAPI into a build target (so it can be diffed against the tapir-emitted yaml in CI).
  • Upstream contribution path: strip http-annotations-evidence/ and split into the minimal commits before any PR to digital-asset/canton.

Test plan

…e options

Demonstrates Option 4 from the C# SDK proposal appendix: annotate Canton's
.proto files with google.api.http so the JSON HTTP surface can be derived
from the protos directly, bypassing tapir's translation layer.

Scope (one service end-to-end):

- community/ledger-api-proto/.../v2/command_service.proto: imports
  google/api/annotations.proto and adds an option (google.api.http) block
  to each of the three RPCs (SubmitAndWait, SubmitAndWaitForTransaction,
  SubmitAndWaitForReassignment), matching the existing tapir routes.

- All v2 .proto files (22) plus value.proto: add option go_package next to
  the existing csharp_namespace/java_package options. Required by every
  off-the-shelf OpenAPI generator in the ecosystem. Harmless to existing
  C#/Java/Scala codegen, which does not read this option.

Verified with `buf build`: clean compile. Verified end-to-end OpenAPI
generation with buf.build/grpc-ecosystem/openapiv2:v2.28.0: the output
fixes all four type-system defects catalogued in the C# SDK proposal
(numbered duplicate schemas, single-key oneOf wrappers, inline duplicated
enums, untyped DAML payload fields).

See peacefulstudio/documentation/dev-funding/openApi/upstream-fork-experiment.md
for the full write-up.
Builds on the CommandService experiment by annotating every other v2 RPC
the JSON API exposes over HTTP, plus adding go_package to the admin/,
testing/, and interactive/ sub-packages so off-the-shelf OpenAPI
generators can resolve the full proto closure.

Annotated services (route source: community/ledger/ledger-json-api Js*Service.scala):

v2/
- CommandSubmissionService:      Submit, SubmitReassignment
- CommandCompletionService:      CompletionStream
- ContractService:               GetContract
- EventQueryService:             GetEventsByContractId
- PackageService:                ListPackages, GetPackage,
                                 GetPackageStatus, ListVettedPackages
- StateService:                  GetActiveContracts, GetActiveContractsPage,
                                 GetConnectedSynchronizers, GetLedgerEnd,
                                 GetLatestPrunedOffsets
- UpdateService:                 GetUpdates, GetUpdateByOffset,
                                 GetUpdateById, GetUpdatesPage
- VersionService:                GetLedgerApiVersion

v2/admin/
- IdentityProviderConfigService: 5 RPCs
- PackageManagementService:      UploadDarFile, ValidateDarFile,
                                 UpdateVettedPackages
- PartyManagementService:        7 HTTP-exposed RPCs
- UserManagementService:         9 RPCs

v2/interactive/
- InteractiveSubmissionService:  6 RPCs

Each annotation matches the existing tapir route (method + path + body
mapping) so the proto-derived OpenAPI is comparable to the current
tapir-emitted document.

Scope notes:
- Server-streaming RPCs (CompletionStream, GetActiveContracts,
  GetUpdates) get only the canonical POST/list mapping; the WebSocket
  GET form is a JSON-API-only convenience that has no representation in
  google.api.http and remains in the AsyncAPI doc.
- Legacy JSON-only paths (/v2/updates/flats|trees|transaction-*,
  /v2/authenticated-user, /v2/definitions/*) are not annotated — they
  either lack a 1:1 proto RPC or are non-gRPC custom endpoints.
- Non-HTTP-exposed RPCs (TimeService, CommandInspectionService,
  ParticipantPruningService, PackageManagementService.ListKnownPackages,
  PartyManagementService.UpdatePartyIdentityProviderId) are deliberately
  left unannotated.

Verified locally with `buf build community/ledger-api-proto/src/main/protobuf`:
clean compile.
The JSON API exposes this as GET with a JSON body containing a required
EventFormat message — non-standard REST, and rejected by
grpc-ecosystem/openapiv2 ("must not set request body when http method
is GET"). Switching to POST is the canonical mapping for a request
with a non-trivial body, and is the single intentional deviation from
the existing tapir route for the proto-derived OpenAPI.
Mirrors the experiment-evidence pattern from the single-service
experiment, scaled to the full HTTP API:

- openapi-3.0/openapi.yaml — OpenAPI 3.0.3 YAML generated via
  buf.build/community/google-gnostic-openapi:v0.7.0; same major version
  as the existing community/ledger/ledger-json-api/.../openapi.yaml, so
  the two can be diffed directly.
- swagger-2.0/canton-ledger-api.swagger.yaml — Swagger 2.0 YAML via
  buf.build/grpc-ecosystem/openapiv2:v2.28.0; same plugin as the
  upstream CommandService experiment, merged across every annotated
  service.
- coverage-comparison/ — sorted path inventories and the diff between
  the JSON API spec and the proto-derived spec, after normalizing
  path-parameter names (proto field names ↔ tapir placeholder names).
  9 paths-only-in-JSON-API (all deliberate skips: 1 custom JSON-only
  endpoint + 8 deprecated legacy aliases), 0 paths-only-in-proto.
- reproduce.sh — one-shot regen of every artefact in this directory.

Internal to the Peaceful Studio fork. To be deleted before any upstream
PR to digital-asset/canton.
@monsieurleberre monsieurleberre changed the title experiment: google.api.http on every JSON-API endpoint (Peaceful Studio internal) Improve Canton openApi Docs generating directly from annotated protos May 11, 2026
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