From 1f8ba05e1915a3fef345b61be901d908ec384524 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Wed, 14 Jan 2026 12:00:48 -0600 Subject: [PATCH] Removed redundant event.go (we use feedkit's upstream implementation). --- internal/model/event.go | 212 ---------------------------------------- 1 file changed, 212 deletions(-) delete mode 100644 internal/model/event.go diff --git a/internal/model/event.go b/internal/model/event.go deleted file mode 100644 index 0531348..0000000 --- a/internal/model/event.go +++ /dev/null @@ -1,212 +0,0 @@ -package model - -import ( - "errors" - "fmt" - "strings" - "time" -) - -// ErrInvalidEvent is a sentinel error used for errors.Is checks. -var ErrInvalidEvent = errors.New("invalid event") - -// EventValidationError reports one or more problems with an Event. -// -// We keep this structured because it makes debugging faster than a single -// "invalid event" string; you get all issues in one pass. -type EventValidationError struct { - Problems []string -} - -func (e *EventValidationError) Error() string { - if e == nil || len(e.Problems) == 0 { - return "invalid event" - } - var b strings.Builder - b.WriteString("invalid event:\n") - for _, p := range e.Problems { - b.WriteString(" - ") - b.WriteString(p) - b.WriteString("\n") - } - return strings.TrimRight(b.String(), "\n") -} - -// Is lets errors.Is(err, ErrInvalidEvent) work. -func (e *EventValidationError) Is(target error) bool { - return target == ErrInvalidEvent -} - -// Event is the normalized unit your pipeline moves around. -// It wraps exactly one of Observation/Forecast/Alert plus metadata. -type Event struct { - ID string // stable dedupe/storage key (source-defined or computed) - Kind Kind - Source string // configured source name (e.g. "NWSObservationKSTL") - EmittedAt time.Time // when *your* system emitted this event - EffectiveAt *time.Time // optional: “time the event applies” - - // Union payload: EXACTLY ONE must be non-nil. - Observation *WeatherObservation - Forecast *WeatherForecast - Alert *WeatherAlert -} - -// Validate enforces Event invariants. -// -// This is intentionally strict. If an event is invalid, we want to find out -// immediately rather than letting it drift into sinks or storage. -// -// Invariants enforced: -// - ID is non-empty -// - Kind is known -// - Source is non-empty -// - EmittedAt is non-zero -// - Exactly one payload pointer is non-nil -// - Kind matches the non-nil payload -func (e Event) Validate() error { - var problems []string - - if strings.TrimSpace(e.ID) == "" { - problems = append(problems, "ID is required") - } - if !e.Kind.IsKnown() { - problems = append(problems, fmt.Sprintf("Kind %q is not recognized", string(e.Kind))) - } - if strings.TrimSpace(e.Source) == "" { - problems = append(problems, "Source is required") - } - if e.EmittedAt.IsZero() { - problems = append(problems, "EmittedAt must be set (non-zero)") - } - - // Count payloads and ensure Kind matches. - payloadCount := 0 - if e.Observation != nil { - payloadCount++ - if e.Kind != KindObservation { - problems = append(problems, fmt.Sprintf("Observation payload present but Kind=%q", string(e.Kind))) - } - } - if e.Forecast != nil { - payloadCount++ - if e.Kind != KindForecast { - problems = append(problems, fmt.Sprintf("Forecast payload present but Kind=%q", string(e.Kind))) - } - } - if e.Alert != nil { - payloadCount++ - if e.Kind != KindAlert { - problems = append(problems, fmt.Sprintf("Alert payload present but Kind=%q", string(e.Kind))) - } - } - - if payloadCount == 0 { - problems = append(problems, "exactly one payload must be set; all payloads are nil") - } else if payloadCount > 1 { - problems = append(problems, "exactly one payload must be set; multiple payloads are non-nil") - } - - if len(problems) > 0 { - return &EventValidationError{Problems: problems} - } - return nil -} - -// NewObservationEvent constructs a valid observation Event. -// -// If emittedAt is zero, it defaults to time.Now().UTC(). -// effectiveAt is optional (nil allowed). -// -// The returned Event is guaranteed valid (or you get an error). -func NewObservationEvent( - id string, - source string, - emittedAt time.Time, - effectiveAt *time.Time, - obs *WeatherObservation, -) (Event, error) { - if obs == nil { - return Event{}, fmt.Errorf("%w: observation payload is nil", ErrInvalidEvent) - } - - if emittedAt.IsZero() { - emittedAt = time.Now().UTC() - } - - e := Event{ - ID: strings.TrimSpace(id), - Kind: KindObservation, - Source: strings.TrimSpace(source), - EmittedAt: emittedAt, - EffectiveAt: effectiveAt, - Observation: obs, - } - - if err := e.Validate(); err != nil { - return Event{}, err - } - return e, nil -} - -// NewForecastEvent constructs a valid forecast Event. -func NewForecastEvent( - id string, - source string, - emittedAt time.Time, - effectiveAt *time.Time, - fc *WeatherForecast, -) (Event, error) { - if fc == nil { - return Event{}, fmt.Errorf("%w: forecast payload is nil", ErrInvalidEvent) - } - - if emittedAt.IsZero() { - emittedAt = time.Now().UTC() - } - - e := Event{ - ID: strings.TrimSpace(id), - Kind: KindForecast, - Source: strings.TrimSpace(source), - EmittedAt: emittedAt, - EffectiveAt: effectiveAt, - Forecast: fc, - } - - if err := e.Validate(); err != nil { - return Event{}, err - } - return e, nil -} - -// NewAlertEvent constructs a valid alert Event. -func NewAlertEvent( - id string, - source string, - emittedAt time.Time, - effectiveAt *time.Time, - a *WeatherAlert, -) (Event, error) { - if a == nil { - return Event{}, fmt.Errorf("%w: alert payload is nil", ErrInvalidEvent) - } - - if emittedAt.IsZero() { - emittedAt = time.Now().UTC() - } - - e := Event{ - ID: strings.TrimSpace(id), - Kind: KindAlert, - Source: strings.TrimSpace(source), - EmittedAt: emittedAt, - EffectiveAt: effectiveAt, - Alert: a, - } - - if err := e.Validate(); err != nil { - return Event{}, err - } - return e, nil -}