Skip to content

exp/api/openapi: add oapi-codegen generated API client PoC - #2080

Open
AmariahAK wants to merge 7 commits into
prometheus:mainfrom
AmariahAK:main
Open

exp/api/openapi: add oapi-codegen generated API client PoC#2080
AmariahAK wants to merge 7 commits into
prometheus:mainfrom
AmariahAK:main

Conversation

@AmariahAK

Copy link
Copy Markdown

Description

Closes #1998

@bwplotka @kakkoyun @vesari


i. What was the issue

The Prometheus HTTP API client at api/prometheus/v1/api.go is entirely hand-written (1,585 lines). Maintaining it manually creates ongoing overhead — fixing bugs, syncing with Prometheus API changes, and maintaining custom unsafe-based JSON decoders for performance. Prometheus now officially serves an OpenAPI 3.1/3.2 specification at /api/v1/openapi.yaml, opening the door to code generation. The question: can we replace or supplement the hand-written client with a generated one?

ii. Where was the issue

The hand-written client uses github.com/json-iterator/go with custom unsafe decoders for the hot-path types (SamplePair, SampleHistogramPair, SampleStream). A generated client would produce strict JSON-serializable Go structs using standard encoding/json, potentially losing both the convenient model.Value interface (Scalar/Vector/Matrix dispatch) and the optimized decoding performance.

iii. How we investigated

Built a proof-of-concept under exp/api/openapi/, following the existing exp/api/remote/ pattern for experimental modules.

Generated client:

  • Created an OpenAPI 3.0.3 specification (spec.yaml, 1,055 lines) covering all 22 v1 API endpoints with proper response envelopes ({status, data, errorType, error, warnings, infos})
  • Generated client + types (openapi.gen.go, 5,216 lines) using oapi-codegen v2.8.0
  • Built a high-level wrapper (client.go) that dispatches resultType to model.Scalar/model.Vector/model.Matrix
  • Also tested against the real Prometheus OpenAPI 3.1 golden spec (spec_real_31.yaml, 5,510 lines from openapi_3.1_golden.yaml), which generates 12,739 lines — compiles but produces ParseQueryResponse name collisions from shared GET/POST response types

Compatibility verified:

  • Auth: ✅ RequestEditorFn for bearer tokens and custom headers
  • HTTP client injection: ✅ WithHTTPClient for custom transport
  • Response envelope: ✅ warnings/infos/errorType all handled
  • Option pattern: ✅ QueryOptions struct with Timeout, LookbackDelta, Stats, Limit

Performance (Apple M1):

Scenario Round-trip decode
Vector (100 series) ~89µs, 30KB, 819 allocs
Matrix (10×1000 datapoints) ~2.4ms, 234KB, 101 allocs
Histogram (10×100 datapoints) ~1.2ms, 327KB, 7,101 allocs

The overhead comes from double JSON encoding: json.Unmarshal into generated types → json.Marshaljson.Unmarshal into model.Value. Custom oapi-codegen templates could inject json-iterator for the hot path.

Tests:

  • 8 unit tests covering scalar, vector, matrix, errors, label names, label values, warnings/infos propagation
  • 3 decode benchmarks (vector, matrix, histogram)
  • 21 existing root module tests continue to pass — zero breakage

iv. Why oapi-codegen over alternatives

Tool Verdict
oapi-codegen ✅ Recommended — idiomatic Go, OpenAPI 3.0/3.1 support, template-customizable, Go-only toolchain (no JVM), active community
OpenAPI Generator ❌ Requires Java/Docker, produces less idiomatic Go (Java-style patterns), complex template customization (Mustache)
go-swagger ❌ Swagger 2.0 only — no OpenAPI 3.x support
gnostic ❌ Low-level protobuf-focused tool, not designed for developer-friendly HTTP clients

v. How to test locally

# Run the generated client tests
cd exp && go test ./api/openapi/ -v

# Run benchmarks
cd exp && go test ./api/openapi/ -bench=. -benchmem

# Verify no breakage to existing client
go test ./api/prometheus/v1/ -v -count=1

# Regenerate from spec
cd exp/api/openapi && oapi-codegen --config oapi-codegen.yaml spec.yaml

Key findings for the maintainers

  1. oapi-codegen is viable. The generated code is idiomatic, compilable, and covers all endpoints.
  2. model.Value retention requires a wrapper — the result field varies by resultType (array for scalar, array-of-objects for vector/matrix), which OpenAPI cannot express as a discriminated union. The wrapper round-trips through JSON to dispatch to the correct type.
  3. Performance gap is measurable but narrowable — the double-encoding overhead is ~7K allocs for histogram results. Custom oapi-codegen templates injecting json-iterator for SamplePair/SampleHistogramPair could close most of this gap.
  4. Real spec is 5× larger — the 5,510-line golden file generates 12,739 lines, with minor name collisions that are resolvable via oapi-codegen configuration.
  5. Experimental module placement worksexp/api/openapi/ follows the same pattern as exp/api/remote/ and doesn't break the stable module.

AmariahAK and others added 2 commits July 27, 2026 19:48
…#1998

Add an experimental OpenAPI-based HTTP API client under exp/api/openapi/
as a proof-of-concept for issue prometheus#1998 (Consider moving to OpenAPI based HTTP API).

The package includes:
- An OpenAPI 3.0.3 specification covering all 22 Prometheus v1 endpoints
- Generated client + types (5,216 lines) produced by oapi-codegen v2.8.0
- A high-level APIClient wrapper with model.Value dispatch (scalar/vector/matrix)
- Unit tests covering instant query, range query, and label endpoints
- Decode benchmarks: ~95µs (vector 100 series), ~2.5ms (matrix 10×1000)

Key findings: oapi-codegen is viable. Main challenges are type fidelity
(the query result field cannot be typed as model.Value in OpenAPI) and
performance (standard encoding/json is slower than the current
json-iterator+unsafe decoders; custom template injection could help).

Co-authored-by: atlarix-agent <agent@atlarix.dev>
Signed-off-by: Amariah Kamau <110414493+AmariahAK@users.noreply.github.com>
- Add 3 new tests: LabelValues, WarningsAndInfos, QueryWithWarnings
- Add histogram decode benchmark (10x100 samples, ~1.2ms, 327KB)
- Evaluate real Prometheus OpenAPI 3.1 spec (5,510 lines from golden file):
  generates 12,739 lines but has ParseQueryResponse name collisions due to
  shared response types between GET/POST endpoints
- Update README with real spec findings, histogram benchmark data, and
  updated test count (8 tests, 3 benchmarks)

Co-authored-by: atlarix-agent <agent@atlarix.dev>
Signed-off-by: Amariah Kamau <110414493+AmariahAK@users.noreply.github.com>
AmariahAK and others added 5 commits July 27, 2026 20:25
client.go and client_test.go were missing the standard Prometheus
Apache License 2.0 header required by the CI license compliance check
in Makefile.common.

Co-authored-by: atlarix-agent <agent@atlarix.dev>
Signed-off-by: Amariah Kamau <110414493+AmariahAK@users.noreply.github.com>
The CI license check rejects 'Copyright 2026 The Prometheus Authors'
and instead requires 'Copyright The Prometheus Authors' (no year)
for files with copyright year 2026 or later.

Co-authored-by: atlarix-agent <agent@atlarix.dev>
Signed-off-by: Amariah Kamau <110414493+AmariahAK@users.noreply.github.com>
Add concrete resolution steps for the two issues blocking the real
OpenAPI 3.1 spec from generating a compilable client:
1. Parse function collisions (duplicate QueryOutputBody refs)
2. Type name mismatches between hand-crafted and real operation IDs

Also explain why the PoC ships a hand-crafted spec: the real spec
issues are implementable but not blocking for the viability assessment.

Co-authored-by: atlarix-agent <agent@atlarix.dev>
Signed-off-by: Amariah Kamau <110414493+AmariahAK@users.noreply.github.com>
Add x-go-name extensions to /query GET and POST operations in the real
Prometheus OpenAPI 3.1 specification to avoid struct/function name
collisions (ParseQueryResponse redeclared) when generating with oapi-codegen.

These x-go-name hints are part of the documented fix path for the
remaining 2 collision issues. Together with allOf wrapper schemas
(for duplicate response type refs across GET/POST pairs), the real
spec can generate a compilable 12K-line client.

Co-authored-by: atlarix-agent <agent@atlarix.dev>
Signed-off-by: Amariah Kamau <110414493+AmariahAK@users.noreply.github.com>
Resolve all 9 ParseQueryResponse/ParseQueryPostResponse collisions in
the real Prometheus OpenAPI 3.1 spec by:
1. Adding allOf wrapper schemas for 7 GET/POST response type pairs
2. Adding x-go-name extensions to /query operations
3. Renaming /parse-query operationId to /parse-promql to avoid struct
   name collision with /query parser functions

The real spec now generates a compilable 12,757-line client. The
wrapper (client.go) still uses our hand-crafted spec's type names
since the real spec uses oneOf union types that need wrapper adaptation.

Co-authored-by: atlarix-agent <agent@atlarix.dev>
Signed-off-by: Amariah Kamau <110414493+AmariahAK@users.noreply.github.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.

Consider moving to OpenAPI based HTTP API

1 participant