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

  1. 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
  2. Validate each input document with a JSON linter/parser before running openapiv3-merge
  3. Regenerate the offending spec from its source generator instead of hand-editing
  4. 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

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


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