grpc-ecosystem/grpc-gateway · error
tags[%q]: %w
Error message
tags[%q]: %w
What it means
mergeTags wraps an error returned by canonicalEqual while comparing two tag entries that share the same `name`. canonicalEqual (merge.go:435) only errors when canonicalize() fails to re-encode one of the two raw JSON tag objects, i.e. the tag entry contains malformed or unencodable JSON. The wrap adds the conflicting tag name so you know which duplicate tag's payload could not be canonicalized.
Source
Thrown at openapiv3-merge/internal/merge/merge.go:376
}
(*p.dst)[k] = v
}
}
return nil
}
// mergeTags appends tags from src to out, deduplicating by `name`. Two tag
// entries sharing a name must declare identical metadata.
func mergeTags(out *document, seen map[string]json.RawMessage, src *document) error {
for _, raw := range src.Tags {
name, err := tagName(raw)
if err != nil {
return fmt.Errorf("%s: tags: %w", src.name, err)
}
if prev, ok := seen[name]; ok {
same, err := canonicalEqual(prev, raw)
if err != nil {
return fmt.Errorf("tags[%q]: %w", name, err)
}
if !same {
return fmt.Errorf("tags[%q]: %s redefines a tag with different metadata", name, src.name)
}
continue
}
seen[name] = raw
out.Tags = append(out.Tags, raw)
}
return nil
}
// mergeSecurity applies first-wins to the root `security` array. The first
// input to declare a non-empty `security` value establishes it; later
// inputs that declare a different non-empty value are an error. (The root
// `security` is a list of alternatives that apply across the whole API; if
// two generators disagree, silently keeping one would change what callers
// are allowed to do.)View on GitHub (pinned to a58a4436a3)
Solutions
- Open both input files and locate the duplicate tag entry by the name printed in tags["..."] and fix its JSON so it is a valid, canonicalizable object
- Validate each input document with a JSON linter/parser before running openapiv3-merge
- Regenerate the offending spec from its source generator instead of hand-editing
- If the error persists, reduce the inputs to a minimal pair and file a bug with the two tag payloads
Example fix
// before (input spec A, tags section)
{"name":"pets","x-meta":{"broken":}}
// after
{"name":"pets","x-meta":{"broken":null}} Defensive patterns
Strategy: validation
Validate before calling
for _, f := range files {
var doc struct {
Tags []json.RawMessage `json:"tags"`
}
b, err := os.ReadFile(f)
if err != nil { return err }
if err := json.Unmarshal(b, &doc); err != nil { return err }
for _, t := range doc.Tags {
var probe map[string]json.RawMessage
if err := json.Unmarshal(t, &probe); err != nil {
return fmt.Errorf("%s: tag not a valid object: %w", f, err)
}
}
} Type guard
func isValidTagObject(raw json.RawMessage) bool {
var probe map[string]json.RawMessage
return json.Unmarshal(raw, &probe) == nil
} Prevention
- Validate all input specs with a JSON parser/linter before merging
- Never hand-splice JSON fragments into generated specs
- Regenerate specs from source instead of editing raw JSON
When it happens
Trigger: Merging two OpenAPI documents that both declare a top-level tag with the same name, and one of the raw tag JSON payloads cannot be canonicalized by canonicalize() during the comparison (malformed nested JSON reaching the comparison stage).
Common situations: Hand-edited or programmatically spliced tag objects in an input spec with subtly broken JSON structure; a spec generator emitting non-standard JSON values inside a tag object (e.g. NaN-like numbers or invalid raw fragments).
Related errors
- %s: %w
- %s: expected JSON object at top level
- %s: %q: %w
- tags[%q]: %s redefines a tag with different metadata
- security: %w
AI-assisted analysis of grpc-ecosystem/grpc-gateway@a58a4436a3 (2026-09-02).
Data as JSON: /api/errors/4a1317fc7ae81bf0.
Report an issue: GitHub.