contractkit

package
v0.0.0-...-3e91960 Latest Latest
Warning

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

Go to latest
Published: Jun 22, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package contractkit provides runtime helpers used by the per-package mock_gen.go file forge generates from contract.go.

Scope

As of the observe.* migration, contractkit's surface is the mock-side helpers (Recorder + MockNotSet) only. The middleware/tracing/metrics helpers (LogCall, TraceStart, Metrics, Record) historically lived here to support the per-method middleware_gen.go / tracing_gen.go / metrics_gen.go wrappers; those wrappers were removed in favour of Connect interceptors + opt-in helpers in forge/pkg/observe. The helpers themselves remain in this package for backward compatibility with any user code that imported them directly.

The mock side

  • Recorder is embedded by every Mock<Iface> struct. Tests assert against captured calls via m.CallCount(...) / m.Calls(...).
  • MockNotSet returns the canonical "<MockName>.<Method>Func not set" error string that fallthrough mock methods emit when the user hasn't set the corresponding XxxFunc field. The format is locked by TestMockNotSet_FingerprintLocked because dogfood projects assert on the substring.

Where the other helpers went

The non-mock helpers — LogCallErr / LogCall / TraceStart / RecordSpanError / NewMetrics / RecordCall / RecordDuration / RecordError — are still present here for compile-time stability. New code should prefer the equivalent in forge/pkg/observe:

  • observe.LogCall replaces contractkit.LogCallErr / LogCall.
  • observe.TraceCall replaces contractkit.TraceStart + RecordSpanError.
  • observe.NewCallMetrics + (*CallMetrics).RecordCall replaces contractkit.NewMetrics + the trio of Record* methods.

observe.* is also where the Connect interceptors live, which is usually the right level of granularity for new projects.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func LogCall

func LogCall(logger *slog.Logger, method string, start time.Time)

LogCall emits a single slog.Info record summarising a wrapped method call that does NOT return an error. start should be captured immediately before the inner call.

The record is identical in shape to the previous generated per-method log line for void methods:

logger.Info("<method>", "duration", time.Since(start))

func LogCallErr

func LogCallErr(logger *slog.Logger, method string, start time.Time, err error)

LogCallErr emits a single slog.Info record summarising a wrapped method call that returns an error. start should be captured immediately before the inner call; err is the method's terminal error result (which may be nil — the attribute is always emitted to match the previous generated behaviour).

The record is identical in shape to the previous generated per-method log line:

logger.Info("<method>", "duration", time.Since(start), "error", err)

Always-emit-error semantics are part of the locked behavioural fingerprint (see TestLogCallErr_FingerprintLocked).

func MockNotSet

func MockNotSet(mockName, method string) error

MockNotSet returns the canonical "func field not set" error used by every Forge-generated mock when a method is invoked but the corresponding XxxFunc field has not been assigned by the test.

The exact format string — "<MockName>.<Method>Func not set" — is part of the public surface. Tests in dogfood projects assert on this substring; the format is locked by TestMockNotSet_FingerprintLocked.

Example (generated):

func (m *MockService) Send(ctx context.Context, args SendArgs) (SendResult, error) {
    m.Record("Send", ctx, args)
    if m.SendFunc != nil {
        return m.SendFunc(ctx, args)
    }
    return SendResult{}, contractkit.MockNotSet("MockService", "Send")
}

func RecordSpanError

func RecordSpanError(span trace.Span, err error)

RecordSpanError records err on span if err is non-nil and sets the span status to codes.Error with err.Error() as the description.

This is identical in behaviour to the previous generated code's per-method block:

if err != nil {
    span.RecordError(err)
    span.SetStatus(codes.Error, err.Error())
}

nil-safe on err. Does nothing for a nil span.

func TraceStart

func TraceStart(ctx context.Context, tracer trace.Tracer, name string) (context.Context, trace.Span)

TraceStart starts a span on tracer with the given operation name. If ctx is nil, context.Background() is used (matching the previous generated code's fallback for context-less methods).

The returned context carries the span and should be propagated to the inner method call (or held as the call's stand-in context for methods without a context parameter).

Callers are responsible for calling span.End(); the canonical pattern is:

ctx, span := contractkit.TraceStart(ctx, tracer, "Service.Send")
defer span.End()
err := inner.Send(ctx, ...)
contractkit.RecordSpanError(span, err)
return err

Types

type Call

type Call struct {
	Args []any
	Time time.Time
}

Call captures a single recorded invocation of a mock method.

Args is a snapshot of the positional arguments passed to the mock at the moment Record was called. Time is the wall-clock time of the call (UTC monotonic via time.Now), useful for ordering assertions across methods.

type Metrics

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

Metrics groups the three OpenTelemetry instruments used by every metric_gen.go wrapper: a call-count counter, an error-count counter, and a duration histogram (in seconds).

The instruments are named according to the canonical "<package>.calls", "<package>.errors", "<package>.duration" pattern that the previous generated wrapper emitted. Callers construct a Metrics via NewMetrics and embed it in the per-interface generated wrapper.

func NewMetrics

func NewMetrics(meter metric.Meter, packageName string) *Metrics

NewMetrics constructs a Metrics by creating three instruments on meter using the canonical "<packageName>.{calls,errors,duration}" names and the same descriptions/units that the previous generated code used.

Errors from meter.Int64Counter / Float64Histogram are silently dropped (mirroring the previous generated code, which discarded them via _, _ assignments). This preserves the existing behavioural fingerprint.

func (*Metrics) RecordCall

func (m *Metrics) RecordCall(ctx context.Context, method string)

RecordCall registers a method invocation on the call-count counter with a "method" attribute. Should be called immediately before invoking the inner method (matching the previous generated code's ordering: increment count, then call inner, then record duration).

nil-safe on the receiver.

func (*Metrics) RecordDuration

func (m *Metrics) RecordDuration(ctx context.Context, method string, start time.Time)

RecordDuration registers the elapsed duration since start on the duration histogram with a "method" attribute. Should be called immediately after the inner method returns.

nil-safe on the receiver.

func (*Metrics) RecordError

func (m *Metrics) RecordError(ctx context.Context, method string, err error)

RecordError increments the error counter with a "method" attribute when err is non-nil. nil-safe on err and on the receiver.

type Recorder

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

Recorder is a thread-safe per-method call log embedded in every mock produced by forge's mock_gen.go. Tests use it to assert that a mock method was invoked the expected number of times with the expected arguments.

The zero value is ready to use.

func (*Recorder) CallCount

func (r *Recorder) CallCount(method string) int

CallCount returns the number of recorded calls for method. Returns 0 if the method was never called. Cheap (no allocation).

func (*Recorder) Calls

func (r *Recorder) Calls(method string) []Call

Calls returns the ordered list of recorded calls for method. Returns nil if Record has never been called for that method. The returned slice (and each Call's Args) is a deep copy — callers may mutate it freely without affecting the recorder's state.

func (*Recorder) Record

func (r *Recorder) Record(method string, args ...any)

Record appends a single call entry for the named method. args is stored by reference; callers should not mutate the slice's contents after the call returns. nil-safe — calling on a nil receiver is a no-op so generated shims can use a value receiver without risk.

func (*Recorder) Reset

func (r *Recorder) Reset()

Reset clears the recorded call history for all methods. Useful in table-driven tests that share a single mock across rows.

Jump to

Keyboard shortcuts

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