JuliusBrussee/caveman · error

catalog

Error message

catalog %s: %w

What it means

load wraps any DecodeAndValidate failure from a candidate catalog file with the 'catalog %s: %w' prefix so callers know which path failed validation. It is a wrapping error; the root cause (unknown fields, duplicates, bad prices, etc.) is chained inside. On final failure the loader returns an empty entry list rather than partially trusted data.

Solutions

  1. Read the wrapped cause after 'catalog <path>:' — it names the exact rule that failed.
  2. Fix the offending entry in the YAML at that path per the cause (required fields, currency, prices, timestamps).
  3. Regenerate the catalog from the canonical source rather than hand-editing if available.
  4. Run DecodeAndValidate or the test suite on the file before deploying.
Defensive patterns

Strategy: try-catch

Validate before calling

if _, err := catalog.DecodeAndValidate(raw); err != nil {
	log.Printf("catalog %s invalid: %v", path, err)
}

Try / catch

entries, err := load(path)
if err != nil {
	var wrapped *fmt.wrapError
	if errors.As(err, &wrapped) {
		log.Printf("root cause for %s: %v", path, wrapped.Unwrap())
	}
	return fallbackEntries()
}

Prevention

When it happens

Trigger: Calling load (and transitively loadCurrentCatalog / snapshotDrift) with a catalog YAML file that fails DecodeAndValidate for any reason — the path-specific wrapper is applied to the underlying error.

Common situations: A hand-edited or tool-generated catalog YAML with a typo in a field, a duplicate provider/model/region row, or an invalid URL/timestamp; pointing the loader at a stale schema file after a format version change.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/145b54ca791cccf1. Report an issue: GitHub.

Appendix: source

Thrown at shared/platform/catalog/catalog.go:294

		return verifiedAt
	}
	return "unknown"
}

func load() {
	for _, path := range catalogCandidates() {
		raw, err := os.ReadFile(path)
		if err != nil {
			loadErr = err
			continue
		}
		decoded, err := DecodeAndValidate(raw)
		if err == nil {
			entries = decoded
			loadErr = nil
			return
		}
		loadErr = fmt.Errorf("catalog %s: %w", path, err)
	}
	entries = []Entry{}
}

// DecodeAndValidate strictly parses a catalog. Unknown YAML fields, duplicate
// provider/model/region rows, invalid URLs/timestamps, and unsafe prices reject
// the entire file; runtime must never load a plausible partial catalog.
func DecodeAndValidate(raw []byte) ([]Entry, error) {
	dec := yaml.NewDecoder(bytes.NewReader(raw))
	dec.KnownFields(true)
	var decoded []Entry
	if err := dec.Decode(&decoded); err != nil {
		return nil, err
	}
	var trailing any
	if err := dec.Decode(&trailing); err != io.EOF {
		if err == nil {
			return nil, fmt.Errorf("multiple YAML documents are not allowed")

View on GitHub (pinned to 3ee70a1026)