events

package
v0.3.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 3, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package events implements a unified event-contract core: a typed Envelope, a runtime Kind registry, table-driven dispatch, a generic config-driven producer engine, and a dependency-free JSONPath subset.

The design is deliberately schema-additive: event kinds are registered at runtime (mirroring verifier_profiles) rather than enumerated in Go, so new event types can be added without touching this package.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func JSONPath

func JSONPath(root any, expr string) (any, error)

JSONPath evaluates a small JSONPath subset against a value decoded from JSON (map[string]any / []any / scalars). Supported syntax:

  • dotted traversal: ".a.b"
  • array index: ".items[0]"
  • one equality filter: ".checks[?(@.conclusion=='FAILURE')]"

A leading "$" is optional. The filter selects the first array element whose field equals the quoted literal. No other predicates are supported.

Types

type DefaultFetcher

type DefaultFetcher struct {
	// Client is used for http fetches; nil falls back to http.DefaultClient.
	Client *http.Client
}

DefaultFetcher implements Fetcher with real exec and http I/O. It is the production seam; tests substitute a fake.

func (DefaultFetcher) Fetch

func (f DefaultFetcher) Fetch(ctx context.Context, spec FetchSpec) ([]byte, error)

Fetch dispatches to exec or http based on the spec.

type Dispatcher

type Dispatcher struct {
	// contains filtered or unexported fields
}

Dispatcher routes Envelopes to Handlers using the Registry to resolve disposition. Routing is table-driven: there is no switch on the event type.

func NewDispatcher

func NewDispatcher(registry *Registry, logw io.Writer) *Dispatcher

NewDispatcher builds a Dispatcher over the given registry. logw receives the warning lines emitted for soft, unregistered types; pass io.Discard to mute.

func (*Dispatcher) Dispatch

func (d *Dispatcher) Dispatch(e Envelope) error

Dispatch validates and routes an Envelope. The resolution order is:

  • invalid envelope -> error
  • explicit per-type handler -> invoke it
  • reject disposition -> loud fail-fast error
  • soft disposition -> warn + generic handler (no-op if unset)

func (*Dispatcher) On

func (d *Dispatcher) On(typ string, h Handler) error

On registers a per-type handler. The type need not be a registered kind, but emit-time validation still applies the namespace disposition.

func (*Dispatcher) SetSoftHandler

func (d *Dispatcher) SetSoftHandler(h Handler)

SetSoftHandler installs the generic handler used for soft-disposition types that have no explicit per-type handler.

type Disposition

type Disposition int

Disposition controls what happens when an Envelope whose Type is not registered is encountered. It is resolved by the most specific matching namespace; an exact kind registration always wins.

const (
	// DispositionReject is the fail-fast default: unregistered types in a
	// reject namespace are loud errors at emit time.
	DispositionReject Disposition = iota
	// DispositionSoft routes unregistered types to a generic handler and logs
	// a warning instead of failing.
	DispositionSoft
)

func (Disposition) String

func (d Disposition) String() string

String renders a Disposition for diagnostics and error messages.

type Envelope

type Envelope struct {
	Type           string          `json:"type"`
	Source         string          `json:"source"`
	OccurredAt     time.Time       `json:"occurred_at"`
	IdempotencyKey string          `json:"idempotency_key"`
	Payload        json.RawMessage `json:"payload"`
}

Envelope is the canonical wire shape for every event flowing through the system. Payload is left as raw JSON so the core never needs to know the concrete shape of any particular kind.

func NewEnvelope

func NewEnvelope(typ, source, idempotencyKey string, occurredAt time.Time, payload json.RawMessage) (Envelope, error)

NewEnvelope constructs an Envelope and validates it. OccurredAt defaults to the current time when the caller passes the zero value.

func (Envelope) Namespace

func (e Envelope) Namespace() string

Namespace returns the dotted prefix of the event type up to (but excluding) the final segment — e.g. "event.pr.merged" -> "event.pr". When the type has no separating dot the whole type is treated as the namespace.

func (Envelope) Validate

func (e Envelope) Validate() error

Validate enforces the invariants required of every Envelope. It is cheap and has no side effects so it can be called freely at construction and emit time.

type FetchSpec

type FetchSpec struct {
	Argv    []string          // exec: command and arguments
	URL     string            // http: request URL
	Method  string            // http: defaults to GET
	Headers map[string]string // http: request headers
}

FetchSpec describes how the producer obtains the raw JSON document for a cycle. Exactly one of Argv (exec) or URL (http) must be set.

type Fetcher

type Fetcher interface {
	Fetch(ctx context.Context, spec FetchSpec) ([]byte, error)
}

Fetcher is the seam isolating exec/http I/O so tests stay hermetic.

type Handler

type Handler interface {
	Handle(Envelope) error
}

Handler processes a single Envelope. Implementations must be safe to call from the Dispatcher and should treat the Envelope as read-only.

type HandlerFunc

type HandlerFunc func(Envelope) error

HandlerFunc adapts an ordinary function to the Handler interface.

func (HandlerFunc) Handle

func (f HandlerFunc) Handle(e Envelope) error

Handle calls the underlying function.

type Kind

type Kind struct {
	Name        string
	Disposition Disposition
	Producer    ProducerFactory
}

Kind is a registered event kind. Producer is optional and only set when a producer factory was registered for the kind's name.

type Producer

type Producer struct {
	// contains filtered or unexported fields
}

Producer is a stateful event source: each Cycle fetches, maps, diffs against the previous snapshot, and returns Envelopes for changed items.

func NewProducer

func NewProducer(cfg ProducerConfig, fetcher Fetcher) (*Producer, error)

NewProducer builds a Producer. A nil fetcher defaults to the real exec/http fetcher; tests inject a fake.

func (*Producer) Cycle

func (p *Producer) Cycle(ctx context.Context) ([]Envelope, error)

Cycle runs one fetch/diff cycle and returns Envelopes for new or changed items. The first cycle treats every item as new.

type ProducerConfig

type ProducerConfig struct {
	Type   string            // event type emitted for each change
	Source string            // envelope source
	Fetch  FetchSpec         // how to obtain the document
	Each   string            // jsonpath to the list of items
	Map    map[string]string // canonical field -> jsonpath (relative to item)
	KeyBy  string            // canonical field used as the snapshot/idempotency key
}

ProducerConfig drives the generic producer engine. There are deliberately no per-platform fields: a GitHub PR producer and a metrics producer differ only by config.

type ProducerFactory

type ProducerFactory func() (*Producer, error)

ProducerFactory builds a Producer for a registered kind. It is defined here (rather than producer.go) because the registry owns the binding from name to factory; producer.go supplies the concrete engine.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry is a runtime, thread-safe table of event kinds keyed by name plus a set of namespace-level dispositions. There is intentionally no Go enum of kind names — everything is registered at runtime.

func NewRegistry

func NewRegistry() *Registry

NewRegistry returns an empty Registry whose namespaces default to reject.

func (*Registry) DispositionFor

func (r *Registry) DispositionFor(typ string) Disposition

DispositionFor resolves the disposition for a type: an exact kind wins, then the longest matching namespace prefix, then the registry default.

func (*Registry) Lookup

func (r *Registry) Lookup(name string) (Kind, bool)

Lookup returns the registered kind for an exact name.

func (*Registry) Names

func (r *Registry) Names() []string

Names returns the registered kind names sorted for deterministic output.

func (*Registry) Register

func (r *Registry) Register(name string, disp Disposition) error

Register adds (or replaces) a kind by exact name with the given disposition.

func (*Registry) RegisterProducer

func (r *Registry) RegisterProducer(name string, factory ProducerFactory) error

RegisterProducer binds a producer factory to a kind name, creating the kind with the default disposition if it was not registered yet.

func (*Registry) SetNamespaceDisposition

func (r *Registry) SetNamespaceDisposition(namespace string, disp Disposition) error

SetNamespaceDisposition declares the disposition for a whole namespace (e.g. "event.metric"). Unregistered types in that namespace inherit it.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL