// FILE: ./internal/normalizers/common/json.go package common import ( "encoding/json" "fmt" "time" "gitea.maximumdirect.net/ejr/feedkit/event" ) // DecodeJSONPayload extracts the event payload as bytes and unmarshals it into T. // // This is the shared "spine" used by many normalizers: // - sources emit raw JSON payloads (typically json.RawMessage) // - normalizers decode into provider structs // // Errors include a small amount of stage context ("extract payload", "decode raw payload"). // Callers typically wrap these with a provider/kind label. func DecodeJSONPayload[T any](in event.Event) (T, error) { var zero T b, err := PayloadBytes(in) if err != nil { return zero, fmt.Errorf("extract payload: %w", err) } var parsed T if err := json.Unmarshal(b, &parsed); err != nil { return zero, fmt.Errorf("decode raw payload: %w", err) } return parsed, nil } // NormalizeJSON is a convenience wrapper for the common JSON-normalizer pattern: // // 1. Decode raw JSON payload into provider struct T // 2. Map T into canonical payload P (plus an EffectiveAt timestamp) // 3. Finalize the event envelope (schema/payload/effectiveAt) + Validate // // label should be short and specific, e.g. "openweather observation". // outSchema should be the canonical schema constant. // build should contain ONLY provider/domain mapping logic. // // Error policy: // - NormalizeJSON wraps ALL failures with consistent context: "