grpc-ecosystem/grpc-gateway · error

%s: %w

Error message

%s: %w

What it means

This error wraps a low-level JSON decoding failure encountered while reading the very first token of an input document in openapiv3-merge's parse step (merge.go:185). The library streams each input with encoding/json's token decoder, so any syntax error before the first token (e.g. malformed bytes, BOM issues, encoding problems) surfaces here, wrapped with the input's name via %w so the underlying json error is preserved for errors.Is/As. It means the library never got far enough to even see whether the document is an object.

Source

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

}

// parse decodes one input into a document, separating known top-level
// fields from extras. The token-based parser is used instead of
// json.Unmarshal because we need to capture first-occurrence order of
// unknown keys and the insertion order of `paths`/`webhooks` entries.
func parse(in Input) (*document, error) {
	d := &document{
		name:       in.Name,
		Paths:      newOrderedObject(),
		Webhooks:   newOrderedObject(),
		Components: &components{},
		extras:     newOrderedObject(),
	}
	dec := json.NewDecoder(bytes.NewReader(in.Data))
	dec.UseNumber()
	tok, err := dec.Token()
	if err != nil {
		return nil, fmt.Errorf("%s: %w", in.Name, err)
	}
	if delim, ok := tok.(json.Delim); !ok || delim != '{' {
		return nil, fmt.Errorf("%s: expected JSON object at top level", in.Name)
	}
	for dec.More() {
		tok, err := dec.Token()
		if err != nil {
			return nil, fmt.Errorf("%s: %w", in.Name, err)
		}
		key, ok := tok.(string)
		if !ok {
			return nil, fmt.Errorf("%s: unexpected token %v", in.Name, tok)
		}
		var raw json.RawMessage
		if err := dec.Decode(&raw); err != nil {
			return nil, fmt.Errorf("%s: %q: %w", in.Name, key, err)
		}
		switch key {

View on GitHub (pinned to a58a4436a3)

Solutions

  1. Validate the input with json.Valid(in.Data) before calling Merge and fail fast with a clear filename-scoped message.
  2. Convert YAML (or other non-JSON) specs to JSON first (e.g. gopkg.in/yaml.v3 + json.Marshal) before passing them to Merge.
  3. Strip any UTF-8 BOM: bytes.TrimPrefix(data, []byte{0xEF,0xBB,0xBF}).
  4. If the spec came from a network fetch, check the response status and content type and log the first ~100 bytes to spot HTML/empty payloads.
  5. Inspect the wrapped underlying error (errors.Unwrap / %v of the message) — it pinpoints the exact byte offset of the syntax error.

Example fix

// before
merged, err := merge.Merge([]merge.Input{{Name: path, Data: raw}})
// after
if !json.Valid(raw) {
    return fmt.Errorf("%s is not valid JSON", path)
}
raw = bytes.TrimPrefix(raw, []byte{0xEF, 0xBB, 0xBF})
merged, err := merge.Merge([]merge.Input{{Name: path, Data: raw}})
Defensive patterns

Strategy: validation

Validate before calling

func ensureJSONObject(name string, data []byte) error {
    data = bytes.TrimPrefix(data, []byte{0xEF, 0xBB, 0xBF})
    if len(bytes.TrimSpace(data)) == 0 {
        return fmt.Errorf("%s: empty input", name)
    }
    if !json.Valid(data) {
        return fmt.Errorf("%s: not valid JSON", name)
    }
    return nil
}

Type guard

func isJSONObject(data []byte) bool {
    dec := json.NewDecoder(bytes.NewReader(bytes.TrimPrefix(data, []byte{0xEF, 0xBB, 0xBF})))
    tok, err := dec.Token()
    if err != nil { return false }
    d, ok := tok.(json.Delim)
    return ok && d == '{'
}

Prevention

When it happens

Trigger: Calling Merge with an Input whose Data is not valid JSON at all — e.g. empty bytes, a YAML file passed as-is, a truncated download, or a file starting with a UTF-8 BOM or stray character — causes dec.Token() to fail immediately at merge.go:183-185.

Common situations: Passing a .yaml/.yml OpenAPI spec to a JSON-only merger; CI fetching a spec that returned an HTML error page or empty body; an editor saving the spec with a BOM; a partially written or truncated spec file in a build pipeline.

Related errors


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