grpc-ecosystem/grpc-gateway · error

openapi: %s declares %q but %s declares %q

Error message

openapi: %s declares %q but %s declares %q

What it means

Raised in mergeAll when two inputs declare different `openapi` version strings. The library's strict-merge policy requires all inputs to agree on the OpenAPI version; the first input's version establishes the expected value and any later contradicting input is rejected, naming both files and versions.

Source

Thrown at openapiv3-merge/internal/merge/merge.go:275

// values that later inputs must not contradict.
func mergeAll(docs []*document) (*document, error) {
	first := docs[0]
	out := &document{
		name:         "merged",
		OpenAPI:      first.OpenAPI,
		Info:         first.Info,
		Servers:      first.Servers,
		Paths:        newOrderedObject(),
		Webhooks:     newOrderedObject(),
		Components:   &components{},
		ExternalDocs: first.ExternalDocs,
		extras:       newOrderedObject(),
	}
	seenTags := map[string]json.RawMessage{}

	for _, d := range docs {
		if d.OpenAPI != out.OpenAPI {
			return nil, fmt.Errorf("openapi: %s declares %q but %s declares %q",
				first.name, out.OpenAPI, d.name, d.OpenAPI)
		}
		// info/servers/externalDocs and unknown top-level keys are first-wins,
		// silently. The first input's value is already in `out`; nothing to
		// do for later inputs.
		if err := mergeOrdered("paths", out.Paths, d.Paths, d.name); err != nil {
			return nil, err
		}
		if err := mergeOrdered("webhooks", out.Webhooks, d.Webhooks, d.name); err != nil {
			return nil, err
		}
		if err := mergeComponents(out.Components, d.Components, d.name); err != nil {
			return nil, err
		}
		if err := mergeTags(out, seenTags, d); err != nil {
			return nil, err
		}
		if err := mergeSecurity(out, d); err != nil {

View on GitHub (pinned to a58a4436a3)

Solutions

  1. Regenerate or update the mismatching document so its "openapi" version matches the first input
  2. Pin all generator invocations to the same OpenAPI output version
  3. If the older file is 3.0.x, migrate it to 3.1 first, then merge
  4. Align dependency versions of the generator across packages

Example fix

// before (b.json)
"openapi": "3.0.3"
// after (b.json)
"openapi": "3.1.0"
Defensive patterns

Strategy: validation

Validate before calling

func versionsMatch(inputs []merge.Input) error {
	var first string
	for _, in := range inputs {
		var doc struct { OpenAPI string `json:"openapi"` }
		if err := json.Unmarshal(in.Data, &doc); err != nil { return err }
		if first == "" { first = doc.OpenAPI; continue }
		if doc.OpenAPI != first {
			return fmt.Errorf("version mismatch: %q vs %q in %s", first, doc.OpenAPI, in.Name)
		}
	}
	return nil
}

Try / catch

if err := versionsMatch(inputs); err != nil {
	return fmt.Errorf("pre-merge check: %w", err)
}
merged, err := merge.Merge(inputs)
if err != nil { return err }

Prevention

When it happens

Trigger: Calling Merge with inputs where docs[0].OpenAPI != docs[i].OpenAPI — e.g. one file says "3.1.0" and another says "3.0.3" or "3.1.1".

Common situations: Mixing documents generated by different generator versions (one pinned to 3.0.x, another upgraded to 3.1), partially migrated specs, or vendored third-party specs pinned to a different version.

Related errors


AI-assisted analysis of grpc-ecosystem/grpc-gateway@a58a4436a3 (2026-09-02). Data as JSON: /api/errors/f6b9befb6b96594e. Report an issue: GitHub.