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
- func FirstString(node any, path []string) (string, bool)
- func MatchSegments(pattern, segs []string) bool
- func NormalizeRoute(urlPath string) string
- func SplitJSONPath(p string) []string
- func SplitPath(s string) []string
- func ValueAtJSONPath(body []byte, path []string) (string, bool)
- type Attrs
- type Engine
- func (e *Engine) BodyRulePaths() [][]string
- func (e *Engine) Evaluate(method, urlPath string) Policy
- func (e *Engine) EvaluateAttrs(a Attrs) Policy
- func (e *Engine) EvaluateOp(method, urlPath, operation string) Policy
- func (e *Engine) HasCriteriaRules() bool
- func (e *Engine) HasGraphQLRules() bool
- func (e *Engine) LabelKeys() []string
- func (e *Engine) NeedsResponseBody() bool
- type LabelSource
- type Policy
- func (p *Policy) CapturesAnything() bool
- func (p *Policy) ExtractMeters(body []byte) map[string]float64
- func (p *Policy) KeepBody(drew bool, status int, elapsed time.Duration) bool
- func (p *Policy) RedactJSONBody(body []byte) (redacted []byte, ok bool)
- func (p *Policy) SanitizeHeaders(h http.Header) map[string]string
- func (p *Policy) SanitizeQuery(raw string) string
- func (p *Policy) TailSampled() bool
Constants ¶
const RedactedPlaceholder = "[REDACTED]"
Variables ¶
This section is empty.
Functions ¶
func FirstString ¶ added in v0.9.0
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
MatchSegments reports whether a split glob matches split path segments.
func NormalizeRoute ¶
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
SplitJSONPath turns "$.lead.source" into ["lead","source"].
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.
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine is safe for concurrent use after New returns.
func (*Engine) BodyRulePaths ¶ added in v0.9.0
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 ¶
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
EvaluateAttrs resolves policy against everything the caller knows.
func (*Engine) EvaluateOp ¶ added in v0.8.0
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
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
HasGraphQLRules reports whether any rule constrains a GraphQL operation, so the interceptor can skip the extra work entirely when none do.
func (*Engine) LabelKeys ¶
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
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 ¶
CapturesAnything reports whether any telemetry channel is open.
func (*Policy) ExtractMeters ¶
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 ¶
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 ¶
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 ¶
SanitizeHeaders returns a flattened, telemetry-safe copy of h with any policy-listed header values masked. Multi-value headers are joined.
func (*Policy) SanitizeQuery ¶
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 ¶
TailSampled reports whether tail-based rules are in play, meaning the keep/discard decision must be deferred until the response is complete.