Documentation
¶
Overview ¶
Package stats defines the Observer interface for codec and adapter lifecycle events.
go-codex exposes two levels of observability:
Codec-level (ValidationObserver) ¶
Use ValidationObserver when you call codecs directly without an adapter — for example, validating config files, parsing binary protocols, or any non-HTTP/MQTT use case. Implement just ValidationObserver.RecordValidationError and call ReportErrors after each codex.Codec.Decode:
type MyObserver struct{}
func (o *MyObserver) RecordValidationError(location, constraint, field string) {
// increment Prometheus counter, emit log, etc.
}
val, err := appConfigCodec.Decode(rawData)
stats.ReportErrors(&MyObserver{}, "config", err)
Adapter-level (Observer) ¶
Use the full Observer interface when wiring to an adapter. It embeds ValidationObserver and adds transport-specific hooks for HTTP and MQTT:
nethttp.Register(mux, route, handler, nethttp.Options{Observer: obs})
adaptermqtt.SubscribeHandler(ctx, ch, fn, adaptermqtt.SubscribeOptions{Observer: obs})
adaptermqtt.Publish(ctx, client, ch, qos, retained, msg, vars,
adaptermqtt.PublishOptions{Observer: obs})
NoopObserver is a zero-cost default used when no observer is configured.
Index ¶
- func ConstraintName(err error) string
- func ReportErrors(obs ValidationObserver, location string, err error)
- type NoopObserver
- func (NoopObserver) RecordApply(_, _ string, _ bool, _ time.Duration)
- func (NoopObserver) RecordPublish(_ string, _ bool, _ time.Duration)
- func (NoopObserver) RecordRequest(_, _ string, _ int, _ time.Duration)
- func (NoopObserver) RecordSecurityRejection(_, _ string)
- func (NoopObserver) RecordSubscribe(_ string, _ bool, _ time.Duration)
- func (NoopObserver) RecordValidationError(_, _, _ string)
- type Observer
- type PipelineObserver
- type SecurityObserver
- type ValidationObserver
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ConstraintName ¶
ConstraintName extracts the constraint identifier from a field-level error:
- codex.ConstraintError.Name when the error is a constraint failure
- "type-mismatch" for codex.TypeMismatchError
- "required" for codex.ErrMissingField
- "" for any other error type
func ReportErrors ¶
func ReportErrors(obs ValidationObserver, location string, err error)
ReportErrors walks err and calls obs.RecordValidationError for every codec validation failure it finds. location identifies the data source (e.g. "body", "query", "payload", "config"). A no-op if err is nil or contains no recognisable validation errors.
Handled error types:
- codex.ValidationErrors — each entry reports its field and constraint.
- codex.KeyError — reports the failing map key as the field.
- codex.ElementError — reports the slice index as the field (e.g. "[2]").
For all three, the walker recurses into the wrapped cause so nested errors (e.g. a KeyError whose cause is a ValidationErrors) are fully reported. Any other wrapped error is unwrapped and recursed into silently.
Example ¶
package main
import (
"fmt"
"github.com/DaniDeer/go-codex/codex"
"github.com/DaniDeer/go-codex/stats"
"github.com/DaniDeer/go-codex/validate"
)
func main() {
// ValidationObserver: implement to receive per-field validation events.
type simpleObserver struct{ count int }
obs := &simpleObserver{}
recordFn := func(location, constraintName, field string) {
obs.count++
}
// Wrap in a ValidationObserver for ReportErrors.
type wrapObs struct{ fn func(string, string, string) }
w := &wrapObs{fn: recordFn}
// Example: decode a struct with two failing fields.
type Config struct {
Port int
Level string
}
portCodec := codex.Int().Refine(validate.RangeInt(1, 65535))
levelCodec := codex.String().Refine(validate.OneOf("debug", "info", "warn", "error"))
configCodec := codex.Struct[Config](
codex.RequiredField("port", portCodec,
func(c Config) int { return c.Port },
func(c *Config, v int) { c.Port = v },
),
codex.OptionalField("level", levelCodec,
func(c Config) string { return c.Level },
func(c *Config, v string) { c.Level = v },
),
)
_, err := configCodec.Decode(map[string]any{"port": float64(99999), "level": "verbose"})
_ = w // suppress unused warning
stats.ReportErrors(stats.NoopObserver{}, "config", err)
// err is a structured codex.ValidationErrors — all fields collected at once.
fmt.Println(err != nil)
}
Output: true
Types ¶
type NoopObserver ¶
type NoopObserver struct{}
NoopObserver discards all events. It satisfies Observer, ValidationObserver, PipelineObserver, and SecurityObserver and is the zero-cost default used when no observer is configured.
func (NoopObserver) RecordApply ¶
func (NoopObserver) RecordApply(_, _ string, _ bool, _ time.Duration)
func (NoopObserver) RecordPublish ¶
func (NoopObserver) RecordPublish(_ string, _ bool, _ time.Duration)
func (NoopObserver) RecordRequest ¶
func (NoopObserver) RecordRequest(_, _ string, _ int, _ time.Duration)
func (NoopObserver) RecordSecurityRejection ¶
func (NoopObserver) RecordSecurityRejection(_, _ string)
func (NoopObserver) RecordSubscribe ¶
func (NoopObserver) RecordSubscribe(_ string, _ bool, _ time.Duration)
func (NoopObserver) RecordValidationError ¶
func (NoopObserver) RecordValidationError(_, _, _ string)
type Observer ¶
type Observer interface {
ValidationObserver
// RecordRequest is called after every HTTP request completes.
// method is uppercase (GET, POST, …), path is the route pattern (e.g.
// "/users/{id}", not the concrete URL), statusCode is the HTTP status
// written, duration is the total handler time including encode/decode.
RecordRequest(method, path string, statusCode int, duration time.Duration)
// RecordSubscribe is called after every MQTT message is fully processed
// by a SubscribeHandler. topic is the concrete incoming topic (not a
// template), success is false when decode or the application handler
// failed, duration is total processing time.
RecordSubscribe(topic string, success bool, duration time.Duration)
// RecordPublish is called after every Publish call completes.
// topic is the resolved publish topic (after BuildTopic if vars were
// provided), success is false when encode failed, the broker returned an
// error, or the context was cancelled. duration covers encode + broker
// acknowledgement wait.
// Note: RecordPublish is not called when BuildTopic itself fails (the
// message never reached the encode/publish stage).
RecordPublish(topic string, success bool, duration time.Duration)
}
Observer receives lifecycle events emitted by codec adapters. It embeds ValidationObserver for per-field validation errors and adds transport-specific hooks for HTTP and MQTT. All methods must be safe for concurrent use.
type PipelineObserver ¶
type PipelineObserver interface {
// RecordApply is called after every forge.Function*.Apply completes.
// name is the function name, version is its version string, success is false
// when any validation or computation step returned an error, duration is the
// total Apply time (input validation + computation + output validation).
RecordApply(name, version string, success bool, duration time.Duration)
}
PipelineObserver receives lifecycle events from forge.Function*.Apply calls. It is a separate interface from Observer because forge is a domain computation layer, not a transport adapter. Implement alongside Observer when you want telemetry for both transport and KPI computation in the same process.
type SecurityObserver ¶
type SecurityObserver interface {
// RecordSecurityRejection is called when a security check rejects a request
// or message. location is the route path (HTTP) or topic (MQTT). scheme is
// the first declared security scheme name for the operation.
RecordSecurityRejection(location, scheme string)
}
SecurityObserver is an optional extension to Observer for security rejection events. Adapters type-assert the configured Observer to SecurityObserver before calling RecordSecurityRejection, so implementing this interface is purely additive — existing Observer implementations need not change.
type MyObserver struct{ ... }
func (o *MyObserver) RecordSecurityRejection(location, scheme string) {
// increment a Prometheus counter, emit a log line, etc.
}
type ValidationObserver ¶
type ValidationObserver interface {
// RecordValidationError is called for each field that fails codec
// validation. location identifies the data source (e.g. "body", "query",
// "payload", "config"), constraintName is the constraint identifier (e.g.
// "minLen(3)", "non-negative-int", "email", "type-mismatch", "required"),
// field is the field or parameter name from the structured error.
RecordValidationError(location, constraintName, field string)
}
ValidationObserver is the codec-level observability hook. Implement this interface when using codecs directly (without an adapter) to receive per-field validation error events. Use ReportErrors to extract codex.ValidationErrors from a decode error and call [RecordValidationError] for each failing field.
Observer embeds ValidationObserver for use with adapters.