engine

package
v0.15.2 Latest Latest
Warning

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

Go to latest
Published: Aug 20, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

Documentation

Overview

Package engine compiles an optic.yaml Config into an immutable, allocation- light rule engine evaluated on every request.

Compilation happens once at startup: path globs are pre-split into segments and method lists become sets, so the per-request hot path is a linear scan of cheap comparisons — no regex, no locks, no allocation beyond the Policy.

Redaction utilities. IMPORTANT ARCHITECTURAL NOTE: redaction applies only to the telemetry OpticTrace records — the proxied request/response bytes are forwarded untouched. Governance is about what we *store*, not about mutating live traffic.

Index

Constants

View Source
const RedactedPlaceholder = "[REDACTED]"

Variables

This section is empty.

Functions

func FirstString added in v0.9.0

func FirstString(node any, path []string) (string, bool)

FirstString finds the first string-ish value at a JSON path, using the same path grammar as redaction and metering — including "**" recursive descent, so `$.**.source` finds the field wherever a wrapper puts it.

Numbers and booleans are rendered rather than skipped: a partner id is as likely to arrive as 4471 as "4471", and a label that silently vanished because the payload used a number would be a confusing thing to debug.

func MatchSegments added in v0.8.0

func MatchSegments(pattern, segs []string) bool

MatchSegments reports whether a split glob matches split path segments.

func NormalizeRoute

func NormalizeRoute(urlPath string) string

NormalizeRoute collapses identifier-looking path segments (numbers, UUIDs, long hex tokens) into ":id" so unmatched routes still produce bounded metric cardinality: /api/v1/users/42 -> /api/v1/users/:id.

func SplitJSONPath added in v0.9.0

func SplitJSONPath(p string) []string

SplitJSONPath turns "$.lead.source" into ["lead","source"].

func SplitPath added in v0.8.0

func SplitPath(s string) []string

SplitPath and MatchSegments expose the glob matcher so other packages — notably the interceptor's GraphQL path check — use the same semantics as rule matching rather than a second, subtly different implementation.

func ValueAtJSONPath added in v0.9.0

func ValueAtJSONPath(body []byte, path []string) (string, bool)

ValueAtJSONPath parses a raw body once and pulls one path out of it.

Types

type Attrs added in v0.9.0

type Attrs struct {
	Method  string
	Path    string
	Headers http.Header
	Query   url.Values
	// Operation is the GraphQL operation name, when known.
	Operation string
	// Body is the GOVERNED request body, already parsed. Supplied only once
	// it is available; a rule with body criteria does not match until then.
	//
	// Governed, not raw, on purpose: a criterion or label reading a redacted
	// field would otherwise route straight around the rule redacting it.
	Body any
	// BodyKnown distinguishes "parsed, and it had no such field" from
	// "not read yet". Without it a rule could match on absence before the
	// body had even been looked at.
	BodyKnown bool
}

Evaluate resolves the effective Policy for a method + URL path. Rules merge in declaration order: restrictions only ever narrow capture, redactions and labels accumulate. Attrs is everything a rule can match on. Callers pass what they have: the proxy has a full request, `review` and `suggest` have a stored record, `ruletest` has a synthetic case.

A rule whose criteria cannot be decided from the supplied Attrs does not match. That is the fail-safe direction — the same stance graphql_operation takes — because the alternative is a redaction rule silently applying to traffic it was never scoped to.

func AttrsOf added in v0.9.0

func AttrsOf(r *http.Request) Attrs

AttrsOf builds Attrs from a live request.

type Engine

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

Engine is safe for concurrent use after New returns.

func New

func New(cfg *config.Config) *Engine

New compiles a validated Config. Config must have passed Validate().

func (*Engine) BodyRulePaths added in v0.9.0

func (e *Engine) BodyRulePaths() [][]string

BodyRulePaths returns the path globs of rules that need a request body — either a body criterion or a json label source. The interceptor buffers only these routes, so a config without body rules pays nothing.

Response-body labels are reported separately by NeedsResponseBody, since the response buffer already exists whenever metering or capture is on.

func (*Engine) Evaluate

func (e *Engine) Evaluate(method, urlPath string) Policy

Evaluate resolves policy from the request line alone. Rules constrained by graphql_operation, match.headers or match.query are skipped here — there is nothing to decide them against — and applied by EvaluateAttrs.

func (*Engine) EvaluateAttrs added in v0.9.0

func (e *Engine) EvaluateAttrs(a Attrs) Policy

EvaluateAttrs resolves policy against everything the caller knows.

func (*Engine) EvaluateOp added in v0.8.0

func (e *Engine) EvaluateOp(method, urlPath, operation string) Policy

EvaluateOp resolves policy including rules that target a GraphQL operation. Called after the request body has been parsed.

Late binding is sound because OpticTrace governs telemetry, never live traffic: redaction, labels and the route pattern are all applied when the record is built, so learning the operation mid-request is not too late for any of them.

func (*Engine) HasCriteriaRules added in v0.9.0

func (e *Engine) HasCriteriaRules() bool

HasCriteriaRules reports whether any rule matches on headers or query parameters, so a caller can tell whether passing Attrs would change the answer it already has.

func (*Engine) HasGraphQLRules added in v0.8.0

func (e *Engine) HasGraphQLRules() bool

HasGraphQLRules reports whether any rule constrains a GraphQL operation, so the interceptor can skip the extra work entirely when none do.

func (*Engine) LabelKeys

func (e *Engine) LabelKeys() []string

LabelKeys returns the sorted union of custom label names across all rules. Prometheus requires a fixed label schema per metric, so the collector is built once from this set; requests missing a label export "".

func (*Engine) NeedsResponseBody added in v0.9.0

func (e *Engine) NeedsResponseBody() bool

NeedsResponseBody reports whether any rule labels from a response body.

type LabelSource

type LabelSource = config.LabelSource

LabelSource is re-exported from config: this grammar is part of the schema, and one parser means validation and runtime cannot disagree about it — a rule that validates but never fires is the worst failure mode for a config-driven product.

func ParseLabelSource added in v0.9.0

func ParseLabelSource(src string) (LabelSource, error)

ParseLabelSource compiles a label source expression. See config.

type Policy

type Policy struct {
	CaptureRequestBody  bool
	CaptureResponseBody bool
	CaptureHeaders      bool
	CaptureQuery        bool
	CaptureLimitBytes   int64

	// RedactHeaders holds canonical header names whose values are masked
	// in captured telemetry.
	RedactHeaders map[string]struct{}
	// RedactQuery holds lower-cased query parameter names to mask.
	RedactQuery map[string]struct{}
	// RedactJSONPaths holds pre-split dotted paths ($.a.b -> ["a","b"]).
	RedactJSONPaths [][]string
	Labels          map[string]LabelSource

	// MatchedRules records which rule names fired (for log transparency).
	MatchedRules []string

	// RoutePattern is the glob of the last matched rule — a stable,
	// low-cardinality identifier for metrics ("/api/v1/payments/**" instead
	// of one series per payment ID). Empty when no rule matched.
	RoutePattern string

	// SampleRate is the fraction of matched requests whose bodies are
	// captured (1.0 = all). Metrics and metadata ignore sampling.
	SampleRate float64

	// KeepErrors and KeepSlowerThan are tail-based sampling: they rescue
	// requests that the uniform SampleRate draw would have discarded.
	// Because the outcome is only known after the response, a policy with
	// either set buffers bodies for every request and decides at the end.
	KeepErrors     bool
	KeepSlowerThan time.Duration

	// Meters maps meter names to pre-split response-body JSON paths whose
	// numeric values are extracted for usage/cost attribution. Metering is
	// independent of capture restriction and sampling.
	Meters map[string][][]string
}

Policy is the fully-resolved governance decision for one request: the merge of the defaults and every matching rule, in order.

func (*Policy) CapturesAnything

func (p *Policy) CapturesAnything() bool

CapturesAnything reports whether any telemetry channel is open.

func (*Policy) ExtractMeters

func (p *Policy) ExtractMeters(body []byte) map[string]float64

ExtractMeters pulls numeric usage values out of a JSON response body per the policy's meter paths. Every numeric match on a path is summed (arrays traverse implicitly), so "$.usage.total_tokens" works for single objects and "$.items.tokens" sums across list responses.

func (*Policy) KeepBody

func (p *Policy) KeepBody(drew bool, status int, elapsed time.Duration) bool

KeepBody decides whether a request's captured bodies are retained. drew is the uniform SampleRate draw made at request start; status and elapsed are the outcome. Tail rules can only ever rescue a request that the draw discarded — they never suppress one it kept.

func (*Policy) RedactJSONBody

func (p *Policy) RedactJSONBody(body []byte) (redacted []byte, ok bool)

RedactJSONBody parses body as JSON, masks every field addressed by the policy's JSON paths, and re-serializes. Non-JSON input is returned as-is with ok=false so callers can decide how to represent it.

func (*Policy) SanitizeHeaders

func (p *Policy) SanitizeHeaders(h http.Header) map[string]string

SanitizeHeaders returns a flattened, telemetry-safe copy of h with any policy-listed header values masked. Multi-value headers are joined.

func (*Policy) SanitizeQuery

func (p *Policy) SanitizeQuery(raw string) string

SanitizeQuery re-encodes a raw query string with policy-listed parameter values masked. Parameter names are matched case-insensitively. Values that fail to parse are dropped rather than stored raw — an unparseable query is exactly where a stray credential would hide.

func (*Policy) TailSampled

func (p *Policy) TailSampled() bool

TailSampled reports whether tail-based rules are in play, meaning the keep/discard decision must be deferred until the response is complete.

Jump to

Keyboard shortcuts

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