Skip to content

feat(build): add strict server-interface generation to the build pipeline #764

Description

@PragalvaXFREZ

Problem

meshery/schemas generates types only. Every other dimension of the OpenAPI contract, path registration, path-parameter decoding, query-parameter binding, operation dispatch, and response shape enforcement, is re-implemented by hand in meshery/meshery and meshery/meshery-cloud. The result is structural, not incidental, drift:

  • 63 path-parameter mismatches (e.g., {orgID} in consumer vs. {orgId} in spec)
  • 217 ghost query parameters declared in the spec and never read by any handler
  • 15 response-type mismatches where the handler returns a materially different Go type than the contract advertises
  • Only 6 of ~135 audited endpoints are confirmed schema-driven in both consumer repos

None of these failures are visible to the compiler. Types-only generation is the root cause; it discards the exact portion of the contract where mismatches are most frequent and most invisible.


Requested change for meshery/schemas

Extend the make build pipeline to emit a strict server artifact per construct alongside the existing type files.

New generated files per construct

models/<version>/<construct>/
  <construct>.go        # EXISTING: request/response structs (unchanged)
  server.go             # NEW: ServerInterface + router wiring
  params.go             # NEW: one *Params struct per operationId

server.go: ServerInterface

One Go interface whose method set is in 1:1 correspondence with operationId entries in api.yml:

// Code generated by oapi-codegen. DO NOT EDIT.
type ServerInterface interface {
    GetWorkspaces(w http.ResponseWriter, r *http.Request, params GetWorkspacesParams)
    GetWorkspace(w http.ResponseWriter, r *http.Request, workspaceId openapi_types.UUID)
    UpdateWorkspace(w http.ResponseWriter, r *http.Request, workspaceId openapi_types.UUID)
    // ... one method per operationId
}

A consumer handler that omits any operationId will not compile. Renaming a path parameter in the spec produces an immediate compile error in every downstream repo.

params.go: typed parameter structs

One struct per operationId containing the exact set of query/path/header parameters declared in the spec:

type GetWorkspacesParams struct {
    Page     *int    `form:"page,omitempty"`
    PageSize *int    `form:"page_size,omitempty" validate:"omitempty,min=1"`
    Filter   *string `form:"filter,omitempty"`
    Order    *string `form:"order,omitempty"`
    Search   *string `form:"search,omitempty"`
}

Ghost parameters become structurally impossible: the params struct is the parameter universe.

Strict response envelopes (strict-server mode)

type GetWorkspaceResponseObject interface {
    VisitGetWorkspaceResponse(w http.ResponseWriter) error
}
type GetWorkspace200JSONResponse Workspace
type GetWorkspace404Response     struct{}

A handler cannot return an undeclared status code or a mismatched content type.

Framework adapters

Generate thin adapter files (adapter_gorilla.go, adapter_echo.go) that register the spec's paths on the host router. Consumer repos stop hand-authoring router.HandleFunc(...) for migrated constructs.

spec.go: embedded document

Embed the bundled OpenAPI document and expose a Spec() accessor for /api/openapi.json. Runtime and spec become the same artifact.


Generator configuration

Use oapi-codegen in strict-server mode. Target: --generate std-http-server,strict-server,types. Keep existing --generate types output unchanged. Gate new output behind a build flag (GENERATE_SERVER=true) so the pipeline is opt-in per construct during rollout.


Phased rollout (scope for this repo)

Phase Work in meshery/schemas
0 Add strict-server generation target behind feature flag; pilot on key + keychain constructs
1 Normalize all 63 path-parameter mismatches in api.yml to camelCase + Id (e.g., {orgId}, {connectionId}); promote Rule 4 from suppressed to blocking in CI by wiring --style-debt into schema-audit.yml (the rule logic already exists in validation/rules_naming.go)
2–4 Enable generation for remaining constructs in drift-rank order: workspaces → connections → designs → environments → teams → organizations → events → roles → tokens

Consumer-repo migration (wiring handlers to the generated interfaces) is tracked separately in meshery/meshery and meshery/meshery-cloud.


Acceptance criteria

  • make build emits server.go, params.go, and spec.go for pilot constructs (key, keychain)
  • ServerInterface has one method per operationId in the construct's api.yml
  • *Params structs contain exactly the parameters declared in the spec (no extras, no omissions)
  • Strict response envelopes compile and are importable by consumer repos
  • adapter_gorilla.go and adapter_echo.go register spec-defined routes without requiring hand-authored HandleFunc calls
  • spec.go embeds the bundled document and exports Spec() []byte
  • schema-audit.yml runs validate-schemas-strict (or passes --style-debt) on PRs so Rule 4 (IsBadPathParam) is blocking in CI, not suppressed. The rule and the SuggestPathParam fix suggestion already exist in validation/rules_naming.go; only the CI wiring is missing
  • make build remains green; existing type-only consumers are unaffected until they opt in

Out of scope

  • Handler business logic: handler bodies remain the contributor's responsibility
  • Framework migration: Gorilla and Echo are preserved via generated adapters

References

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions