contractgen

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jun 7, 2026 License: MIT Imports: 27 Imported by: 0

Documentation

Overview

Package contractgen renders typed Go scaffolding from a single contract.yaml + its referenced JSON schemas. It is the codegen pass behind `gocell generate contract`.

Pipeline

buildContractSpec parses the contract metadata + schemaRefs into a *ContractGenSpec (spec.go); the spec is then handed to one or more templates registered in templates/ via render.go. Each output file lives under generated/contracts/<segments>/ where the segments mirror the dot-separated contract id (with every "internal" segment rewritten to "internalapi" to keep the generated package importable from cells/ and examples/ — Go's internal package rule otherwise blocks cross-tree imports; see pkg/contractpath).

Artifact matrix

The generator emits a fixed set of files per contract kind. The authoritative matrix is contractArtifacts in generator.go (the single source consumed by both generateOneContract and RenderContractArtifacts); this table mirrors it. Every file carries the "Code generated by gocell generate contract. DO NOT EDIT." header so editors and lint suites recognize them as derived artifacts.

Columns are file stems; each emitted file is <stem>_gen.go.

           | types | iface | handler | spec | subscription | projection | saga | command
-----------+-------+-------+---------+------+--------------+------------+------+---------
http       |   ✔   |   ✔   |    ✔    |   —  |      —       |     —      |   —  |    —
event      |   ✔   |   ✔   |    —    |   ✔  |      ✔       |     ✔      |   —  |    —
command    |   ✔   |   —   |    —    |   —  |      —       |     —      |   —  |    ✔
projection |   ✔   |   ✔   |    —    |   —  |      —       |     —      |   —  |    —
grpc       |   —   |   —   |    —    |   —  |      —       |     —      |   —  |    —
saga       |   ✔   |  ✔ *  |    —    |   —  |      —       |     —      |   ✔  |    —
webhook    |   —   |   —   |    —    |   —  |      —       |     —      |   —  |    —

* saga's iface_gen.go is an empty package clause (no Service interface); the typed business interface is Impl in saga_gen.go. See the iface_gen.go section.

webhook and grpc emit no contractgen artifacts at all: webhook registration derives via cellgen from slice.yaml; grpc's server contract is buf's generated pb.<Svc>Server interface, so contractgen emits no Go for kind=grpc (#1688).

types_gen.go (all kinds except webhook and grpc)

Renders Request / Response / Payload / Headers DTOs from the schemaRefs. For HTTP contracts, additionally renders the typed-response-envelope set: one {HandlerMethod}ResponseObject interface plus N concrete typed structs, one per status declared in contract.yaml http.responses[] union with http.successStatus. The generator's Responses IR slice is the single source of truth — CH-06 governance enforces the contract.yaml ↔ types_gen.go bijection (see kernel/governance/rules_http.go).

Naming convention for typed response structs:

{HandlerMethod}{Status}JSONResponse        — success body-bearing (200/201/etc.)
{HandlerMethod}{Status}NoContentResponse   — success 204 marker (empty struct)
{HandlerMethod}{Status}ErrorResponse       — declared 4xx/5xx, Body errcode.Error

Each typed struct implements an unexported visit{HandlerMethod}Response method, closing the implementation set to types declared in this package. Service code constructs one of these structs to return; the generated handler dispatches via the Visit method, which writes the pinned status + body (errors go through pkg/httputil.WriteErrorWithStatus to share the 4xx/ 5xx redaction policy with the framework fallback path).

iface_gen.go (http/event/projection/saga — not command/grpc/webhook)

Renders the Service interface that the cell-side handler must implement. For HTTP contracts the signature is `Method(ctx, *Request) ({HandlerMethod}ResponseObject, error)`: the typed response carries declared 2xx/4xx/5xx outcomes; the Go error return is reserved for un-declared framework 5xx (panic recover, infrastructure faults), in which case the generated handler falls back to pkg/httputil.WriteError(err) and derives the response status from errcode.Kind. For event contracts the signature is `Handle{Topic}(ctx, *Payload) error`. For kind=saga, iface_gen.go is intentionally empty (just the package clause) — the typed business interface is the Impl interface emitted in saga_gen.go, not a Service here. command contracts emit no iface_gen.go — the typed Handler interface lives in command_gen.go (a Service interface there would collide); grpc and webhook contracts emit no contractgen package at all: grpc's server contract is buf's generated pb.<Svc>Server interface (proto-derived, declares every RPC, carries the forward-compat mustEmbedUnimplemented marker), so a parallel contractgen interface would be a register-incompatible, package-colliding duplicate (#1688); webhook registration derives via cellgen from slice.yaml.

handler_gen.go (kind=http only)

Renders the http.Handler that decodes the request, dispatches to Service, and writes the typed response. Wires auth.Mount with the contract's auth.Public / auth.Bootstrap / auth.PasswordResetExempt flags as declared in contract.yaml. Pagination endpoints — every GET that declares cursor + limit, regardless of additional path or filter query params — route through pkg/httputil.ParsePageParams so the limit error envelope is uniform across the entire HTTP surface; this is byte-pinned by the http_order_list_v1 handler_gen.go golden in render_test.go — any reintroduction of inline strconv.ParseInt("limit") in the template diffs the golden output. (funnel-first; see docs/plans/202605070431-pr403-funnel-fix-roadmap.md §7.)

spec_gen.go (kind=event only)

Renders the cell-facing contractspec.ContractSpec literal so cells can reference one canonical declaration for the topic, transport, delivery semantics, and idempotency policy. Single source for the contract id stamped into every consumer's reg.Subscribe call.

subscription_gen.go (kind=event only)

Renders the typed Subscription registration helper. Cells call `{Topic}.Subscribe(reg, handler, consumerGroup)`; the helper resolves the canonical ContractSpec from spec_gen.go and asserts the consumerGroup is a non-empty string at registration time.

projection_gen.go (kind=event only)

Renders the NewProjectionRequest mount-helper. cellgen is the only production caller (via reg.RegisterProjection, funnel-locked by PROJECTION-REGISTER-FUNNEL-01). The function references the package-private spec var so callers receive a fully-formed cell.ProjectionRequest without the spec value escaping as a bare contractspec.ContractSpec literal.

saga_gen.go (kind=saga only)

Renders the typed saga scaffolding from the contract.yaml saga block:

  • DefinitionID — the saga.Definition.ID const (= contract id).
  • <Step>Output DTOs (in types_gen.go) — one per step's output schemaRef.
  • Impl — the typed business interface. Step 0 is Run<Name>(ctx, inst); each later step is Run<Name>(ctx, inst, <PrevStep>Output) (the runtime feeds the previous step's output as prevState; step 0 gets nil). A step gets a Compensate<Name>(ctx, inst, <Step>Output) method only when it declares compensate (default true).
  • BuildDefinition(impl) *saga.Definition — wraps the typed methods into the untyped saga.StepFunc / saga.CompensateFunc via JSON (un)marshal.
  • Register(impl) (*saga.InMemoryRegistry, error) — single-saga convenience; for multi-saga registries call BuildDefinition and compose via saga.NewInMemoryRegistry.

Cell attribution constraint

Generated *Handler MUST be mounted from a cell-owned RouteGroup (typically via the cellgen-generated cell_gen.go reg.RouteGroup wiring). http_requests_total `cell` label depends on this:

  • Mounted via reg.RouteGroup → CellAttribution middleware injects kernel/ctxkeys.CellID = <cellID>; metrics labeled cell=<cellID>.
  • Mounted standalone (e.g. http.Handle) → no CellID context, metrics fall back to the "_runtime" sentinel — silent observability bug.

Reference: .claude/rules/gocell/observability.md "HTTP Metrics `cell` Label".

Workflow: adding a new declared status code

  1. Edit contracts/<path>/contract.yaml — add the new status code under endpoints.http.responses[] with a schemaRef pointing to the JSON schema for that status's response body (or use the shared error envelope ref for 4xx/5xx declared error responses).

2. Regenerate all typed structs:

	go run ./cmd/gocell generate contract --all

   This reruns types.tmpl for every HTTP contract, emitting one new
   {HandlerMethod}{Status}JSONResponse / NoContentResponse / ErrorResponse
   struct for the added status, and updating the {HandlerMethod}ResponseObject
   interface to include it.

3. Validate governance:

	go run ./cmd/gocell check contract-health   # CH-04 + CH-06 must pass
	golangci-lint run ./...                      # 0 issues

   CH-06 (rules_http.go) verifies that the generated struct
   set exactly matches contract.yaml SuccessStatus ∪ responses[]; CH-04
   (rules_http.go) verifies the handler-emitted status set
   is a subset of the declared set. Both must be green before committing.

Cross-tooling references

  • pkg/contractpath — contract id → generated package path (single source of truth for the internal→internalapi rewrite, shared with kernel/governance CH-04/05/06 and archtest).
  • tools/codegen/cellgen — cell scaffolding generator (peer pass; shares the codegen.Render plumbing).
  • tools/archtest/handler_inline_limit_parse_test.go — HANDLER-NO-INLINE- LIMIT-PARSE-01, regression gate against per-param limit parsing.
  • tools/archtest/codegen_http_handler_funnel_test.go — CODEGEN-BUILDHTTPENDPOINTSPEC-SOLE-CALLER-01: HTTP codegen funnel integrity (sealed httpEndpointSpec type + sole constructor/caller + handler.tmpl http.Handler emit-uniqueness across tools/codegen/**).
  • kernel/governance/rules_http.go — CH-04 (handler-emitted status ⊂ contract.yaml.responses[] ∪ auth.responses), CH-05 (uuid path-param parse-call presence), CH-06 (contract.yaml.responses[] ∪ successStatus = generated typed-response struct set).

ref: oapi-codegen pkg/codegen/templates/strict — typed-response-envelope strict-server pattern this package adapts. ref: go-zero/tools/goctl — multi-template per-artifact emission with a single in-memory Spec model. ref: sqlc-gen-go — generator IR + table-driven template selection.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ContractIDsForCell

func ContractIDsForCell(p *metadata.ProjectMeta, cellID string) []string

ContractIDsForCell returns all Codegen=true contract IDs owned by cellID (server or publisher). The returned slice is sorted for deterministic output. Thin exported wrapper around selectByCellID for cross-package callers (notably cellgen stage_render.go). No OS calls — safe in depguard scaffold-os-ban scope. Has no fallible path; returns a plain slice.

Types

type CodegenArtifact

type CodegenArtifact struct {
	// Path is the repo-relative target path,
	// e.g. "generated/contracts/http/order/create/v1/types_gen.go".
	Path string
	// Content is the rendered, formatted, goimports-processed bytes.
	Content []byte
}

CodegenArtifact is one rendered file (in-memory).

func RenderContractArtifacts

func RenderContractArtifacts(root string, p *metadata.ProjectMeta, contractID, modulePath string) ([]CodegenArtifact, error)

RenderContractArtifacts renders a single contract to in-memory artifacts. Used by manifest projection / verify pipelines (mirrors cellgen.RenderCellArtifacts). Returns (nil, nil) when the contract is not opted in (Codegen=false).

modulePath is the consuming repo's module path, threaded to the formatter (#1083). It is required and supplied by the caller — callers resolve it from the real repo's go.mod (the render root may be a staging dir without a go.mod, e.g. scaffold staging in cellgen/stage_render.go).

The kind × artifact matrix is driven by artifactsForKind (single source, shared with generateOneContract).

type CommandSpec

type CommandSpec struct {
	// DispatchID is the contract id used as the command.Registry key const,
	// e.g. "command.devicecommand.enqueue.v1".
	DispatchID string
	// HandlerMethod is the method name on the generated Handler interface:
	// "Handle" + goPascalCase(domainLastSegment(id)).
	// Example: for "command.synth.do.v1" → "HandleDo".
	HandlerMethod string
	// RequestGoType is the Go type name for the command request DTO.
	// Always "Request" (the generated DTO from types_gen.go); held as a field
	// so command.tmpl has a single source rather than a hardcoded literal.
	RequestGoType string
	// ResponseGoType is the Go type name for the command response DTO.
	// Always "Response".
	ResponseGoType string
}

CommandSpec holds command-specific generation data (Kind=="command" only). It drives command.tmpl, which emits the typed Handler interface + Register + Dispatch that provide a sealed typed funnel over runtime/command.Registry.

The Handler interface + Register + Dispatch exist ONLY in generated code — a hand-written typed command handler is unexpressible, mirroring saga's Impl/BuildDefinition pattern. See ADR docs/architecture/ for the command-bus ADR (#1044).

type ContractGenSpec

type ContractGenSpec struct {
	// PackageName is the Go package name derived from the last path segment
	// of PackagePath (e.g. "create", "get", "ordercreated").
	PackageName string
	// PackagePath is the module-relative path for the generated package,
	// e.g. "generated/contracts/http/order/create/v1".
	PackagePath string
	// ContractID is the full contract id, e.g. "http.order.create.v1".
	ContractID string
	// Kind is one of the closed set: "http", "event", "command", "projection",
	// "webhook", "grpc", "saga".
	Kind string
	// Transports is the non-empty, parser-derived set of sanctioned wire
	// transports for this contract (#1389). The generated ContractSpec.Transport
	// is the primary, Transports[0]; templates render it uniformly across kinds
	// (spec.tmpl for events, handler.tmpl for http) so "generated Transport ==
	// transports[0]" has no kind special-case. For a multi-transport contract
	// (len > 1, e.g. an event over [amqp, mqtt]) spec.tmpl additionally emits an
	// exported `Transports` var so out-of-band subscribers reference the contract
	// truth source instead of hand-writing a transport string.
	Transports []string
	// ConsistencyLevel is the contract's declared consistency level ("L0".."L4").
	// Consumed by types.tmpl for two kinds, each driving a compile-time level
	// guard `const _ = uint(cellvocab.<ConsistencyLevel> - cellvocab.<floor>)`
	// that fails to compile when the level is below the floor:
	//   - kind=projection → floor L3 (PROJECTION-CONSISTENCY-01, gh #960)
	//   - kind=command    → floor L1 (COMMAND-CONTRACT-CONSISTENCY-LEVEL-01, #1668)
	// buildContractSpec validates that the value parses (cellvocab.ParseLevel)
	// before it reaches the template (validateProjectionLevel / validateCommandLevel).
	ConsistencyLevel string
	// SourceFile is the repo-relative path of the contract.yaml that drove
	// generation, e.g. "examples/todoorder/contracts/http/order/create/v1/contract.yaml".
	SourceFile string
	// DTOs holds the flattened list of Go struct definitions (nested types
	// expanded to top-level entries). Template iterates this slice directly.
	DTOs []DTOSpec
	// Endpoint is non-nil when Kind == "http". Its type is the unexported
	// httpEndpointSpec (sealed): no out-of-package code can construct a non-nil
	// Endpoint. The whole ContractGenSpec is also never handed to another
	// package as a mutable value — its sole constructor buildContractSpec and
	// every render wrapper are package-private (only Generate /
	// RenderContractArtifacts are exported, and those return rendered []byte,
	// never the spec). So neither constructing nor mutating an
	// FMT-34-unvalidated Endpoint to drive handler.tmpl is expressible from
	// another package. See httpEndpointSpec's godoc.
	Endpoint *httpEndpointSpec
	// Event is non-nil when Kind == "event".
	Event *EventEndpointSpec
	// Saga is non-nil when Kind == "saga". It drives saga.tmpl (the typed
	// Impl interface + BuildDefinition/Register); the step output DTOs it
	// references are emitted into DTOs (rendered by types.tmpl) like any kind.
	Saga *SagaSpec
	// Command is non-nil when Kind == "command". It drives command.tmpl (the
	// typed Handler interface + Register + Dispatch). Request/Response DTOs
	// are generated into DTOs (rendered by types.tmpl) like any kind.
	// iface_gen.go is NOT emitted for command (Handler lives in command_gen.go).
	Command *CommandSpec
	// (No grpc field: kind=grpc emits zero contractgen artifacts since #1688 —
	// buf's generated pb.<Svc>Server is the sole server contract.)
	// RequestSchemaJSON is the raw JSON content of the request schema file,
	// compacted to a single line (no extra whitespace).
	// Non-empty only when Kind=="http" and the contract declares schemaRefs.request.
	// The generated handler embeds this as a Go string literal to compile the
	// validator at construction time — no runtime file I/O, no embed.FS.
	// Empty string means no schema validation is emitted.
	RequestSchemaJSON string

	// PanicReasonPolicyNil is the kebab-case reason literal passed to
	// panicregister.Approved for the "policy must not be nil" panic site.
	// Pre-computed from ContractID (dots replaced by dashes) + "-policy-nil".
	// Example: "http-order-create-v1-policy-nil".
	PanicReasonPolicyNil string
	// PanicReasonBootstrapAuthNil is the kebab-case reason literal passed to
	// panicregister.Approved for the "bootstrapAuth must not be nil" panic site.
	// Pre-computed from ContractID + "-bootstrap-auth-nil".
	// Example: "http-sample-bootstrap-v1-bootstrap-auth-nil".
	PanicReasonBootstrapAuthNil string
	// PanicReasonPublicSchemaCompileFailed is the kebab-case reason literal passed
	// to panicregister.Approved for the schema compile failed panic in NewPublicHandler.
	// Pre-computed from ContractID + "-public-schema-compile-failed".
	// Example: "http-order-create-v1-public-schema-compile-failed".
	PanicReasonPublicSchemaCompileFailed string
	// PanicReasonBootstrapSchemaCompileFailed is the kebab-case reason literal passed
	// to panicregister.Approved for the schema compile failed panic in NewBootstrapHandler.
	// Pre-computed from ContractID + "-bootstrap-schema-compile-failed".
	// Example: "http-auth-setup-admin-v1-bootstrap-schema-compile-failed".
	PanicReasonBootstrapSchemaCompileFailed string
	// PanicReasonClientsOnlySchemaCompileFailed is the kebab-case reason literal passed
	// to panicregister.Approved for the schema compile failed panic in NewClientsOnlyHandler.
	// Pre-computed from ContractID + "-clients-only-schema-compile-failed".
	// Example: "http-config-flags-evaluate-v1-clients-only-schema-compile-failed".
	PanicReasonClientsOnlySchemaCompileFailed string
	// PanicReasonServiceOwnedSchemaCompileFailed is the kebab-case reason literal passed
	// to panicregister.Approved for the schema compile failed panic in NewServiceOwnedHandler.
	// Pre-computed from ContractID + "-service-owned-schema-compile-failed".
	// Example: "http-auth-session-delete-v1-service-owned-schema-compile-failed".
	PanicReasonServiceOwnedSchemaCompileFailed string
	// PanicReasonStandardSchemaCompileFailed is the kebab-case reason literal passed
	// to panicregister.Approved for the schema compile failed panic in the standard
	// NewHandler (with policy parameter).
	// Pre-computed from ContractID + "-standard-schema-compile-failed".
	// Example: "http-order-create-v1-standard-schema-compile-failed".
	PanicReasonStandardSchemaCompileFailed string
}

ContractGenSpec is the top-level template input for one contract.

type DTOField

type DTOField struct {
	// Name is the PascalCase Go field name.
	Name string
	// JSONTag is the JSON tag value, e.g. "item,omitempty".
	JSONTag string
	// GoType is the Go type expression, e.g. "string", "int64", "*ResponseData".
	GoType string
	// Required indicates whether the field is in the schema's required list.
	Required bool
	// Doc is an optional comment, used for format hints (uuid, date-time).
	Doc string
	// Source identifies where this field originates: "body", "path", "query", or
	// "header". Empty means body (legacy/default). Only body fields receive schema
	// validation in the generated handler; path/query fields are validated at parse
	// time. "header" fields are populate-only (read from r.Header.Get, no gate) and
	// carry JSONTag "-" so the request body can never spoof them.
	Source string
	// MinLength constrains string body fields (minimum character length).
	MinLength *int
	// MaxLength constrains string body fields (maximum character length).
	MaxLength *int
	// Minimum constrains integer body fields (inclusive lower bound).
	Minimum *int64
	// Maximum constrains integer body fields (inclusive upper bound).
	Maximum *int64
}

DTOField describes a single struct field.

type DTOSpec

type DTOSpec struct {
	// Name is the PascalCase struct name, e.g. Request, Response, Payload.
	Name string
	// Doc is the human-readable description (from schema.title).
	Doc string
	// Fields lists the struct fields in source-declared order.
	Fields []DTOField
	// Nested holds intermediate nested-object types discovered during schema
	// traversal. Callers of buildContractSpec see an empty slice — the builder
	// promotes nested types to ContractGenSpec.DTOs and clears this field.
	Nested []DTOSpec
}

DTOSpec is one Go struct definition. Nested is used only as an intermediate representation during builder flattening; by the time ContractGenSpec.DTOs is populated, all nested types have been promoted to top-level and Nested is empty.

type EventEndpointSpec

type EventEndpointSpec struct {
	// Topic is the broker topic name (contract id with version suffix stripped),
	// e.g. "event.order-created".
	Topic string
	// HandlerMethod is the PascalCase handler method name,
	// e.g. "HandleOrderCreated".
	HandlerMethod string
	// Replayable indicates whether this event supports replay.
	Replayable bool
	// DeliverySemantics is the declared delivery guarantee, e.g. "at-least-once".
	DeliverySemantics string
}

EventEndpointSpec holds event-specific endpoint information.

type Options

type Options struct {
	// DryRun emits ActionWouldWrite without filesystem mutation.
	DryRun bool
	// Verify diffs the rendered content against disk and reports drift.
	// Mutually exclusive with DryRun at the CLI layer; combining them here
	// is harmless — Verify dominates (no write either way).
	Verify bool
	// Scope controls which contracts are processed. When nil, Generate returns
	// an error (fail-fast). Use ScopeAll{} for the default "all Codegen=true"
	// behavior, ScopeContracts for a specific ID list, or ScopeCell to restrict
	// to one cell's contracts.
	Scope Scope
	// ModulePath is the consuming repo's Go module path (from its go.mod),
	// threaded to the formatter so generated files group module-local imports
	// the way the target repo's golangci-lint gate expects (#1083). Required:
	// Generate rejects an empty ModulePath. The CLI resolves it via
	// resolveModule (flag-or-go.mod); RenderContractArtifacts resolves it from
	// root itself.
	ModulePath string
}

Options controls Generate behavior. Mirrors cellgen.Options.

type PaginationShape

type PaginationShape struct {
	HasCursor        bool
	HasLimit         bool
	ExtraQueryParams []ParamSpec
}

PaginationShape captures the structural facts the handler template needs to emit the right query-parsing code for a paginated endpoint:

  • HasCursor / HasLimit indicate which canonical params are present (always both true after Batch 1; Batch 2 keeps the same invariant while relaxing endpoint-shape detection).
  • ExtraQueryParams carries any additional query parameters declared on the same endpoint. Today the builder rejects mixed pagination+filter endpoints in detectPagination; Batch 2 lifts that restriction and these params are routed through per-param parsing while cursor+limit keep going through pkg/httputil.ParsePageParams (single error envelope).

type ParamSpec

type ParamSpec struct {
	// Name is the parameter name as declared in contract.yaml, e.g. "id", "cursor".
	Name string
	// GoName is the PascalCase Go field name used in the Request struct.
	GoName string
	// GoType is the Go scalar type: "string", "int64", "float64", or "bool".
	GoType string
	// Required is true for path params (always) or explicitly-required query params.
	Required bool
	// Doc is an optional hint comment.
	Doc string
	// Format is the json-schema format for this param, e.g. "uuid", "date-time".
	// Used by handler.tmpl to emit httputil.ParseUUIDPathParam for uuid-format path params.
	Format string
	// MinLength applies to string params.
	MinLength *int
	// MaxLength applies to string params.
	MaxLength *int
	// Minimum applies to numeric params.
	Minimum *int64
	// Maximum applies to numeric params.
	Maximum *int64
}

ParamSpec describes a single HTTP path or query parameter.

type ProtoMethodInfo

type ProtoMethodInfo struct {
	// Name is the rpc method name, e.g. "IssueCommand".
	Name string
	// RequestType is the request message simple name, e.g. "IssueCommandRequest".
	RequestType string
	// ResponseType is the response message simple name, e.g. "IssueCommandResponse".
	ResponseType string
}

ProtoMethodInfo is the name + request/response type for one RPC.

type ProtoServiceInfo

type ProtoServiceInfo struct {
	// ProtoPackage is the proto `package` declaration, e.g. "device.command.v1".
	ProtoPackage string
	// ImportPath is the go_package import path (the part before ';'),
	// e.g. "github.com/ghbvf/gocell/generated/contracts/grpc/device/command/v1".
	ImportPath string
	// Alias is the go_package import alias (the part after ';'), e.g. "commandv1".
	Alias string
	// Methods lists every unary RPC in the service block; streaming RPCs and
	// unexported-identifier method names are rejected (see parseAllRPCMethods).
	Methods []ProtoMethodInfo
}

ProtoServiceInfo is the proto service identity resolved from a .proto file: the Go import binding (from the file's go_package option) plus every RPC method declared in the service block. Used by contractgen's service-level codegen path (#1655) where one contract owns a whole proto service.

func ReadProtoServiceInfo

func ReadProtoServiceInfo(protoAbsPath, service string) (ProtoServiceInfo, error)

ReadProtoServiceInfo reads protoAbsPath and returns all unary RPC methods for the named service (fully-qualified, e.g. "device.command.v1.DeviceCommandService"). It enumerates ALL methods in the service block; the proto is the single source of truth for the method set (#1655).

Usage: callers must pass an absolute proto path that already exists on disk. Contract-context errors should be wrapped by the caller.

type ResponseSpec

type ResponseSpec struct {
	// Status is the HTTP status code, e.g. 200, 204, 401, 503.
	Status int
	// Description mirrors the contract.yaml response description (free form).
	// Empty for the success entry (the success body schema documents itself).
	Description string
	// SchemaRef is the contract-relative path to the JSON schema that
	// describes this response body. Empty for the success entry (success
	// body lives in schemaRefs.response, not in responses[]) and for the
	// 204 NoContent entry.
	SchemaRef string
	// IsError is true for status >= 400; the success entry is false.
	IsError bool
	// IsNoContent is true for the 204 NoContent success entry — the
	// generated typed struct is an empty marker (`struct{}`) and the visit
	// method writes only the status header. Templates branch on this flag
	// rather than reverse-derive the suffix from GoTypeName, keeping the
	// IR the single source for the JSON-vs-NoContent distinction.
	IsNoContent bool
	// GoTypeName is the Go identifier for the typed response struct that
	// implements the per-endpoint XxxResponseObject interface. The naming
	// convention is {HandlerMethod}{Status}{Suffix}: 200 → 200JSONResponse,
	// 204 → 204NoContentResponse, 4xx/5xx → {Status}ErrorResponse.
	GoTypeName string
}

ResponseSpec describes a single declared HTTP response from contract.yaml. One ResponseSpec is emitted per declared status (success status from the HTTP transport metadata + every entry in http.responses[]), sorted by Status ascending. The slice is the single source of truth for typed response envelope generation and CH-04 governance.

type Result

type Result struct {
	// Generated lists files that were written, would-have-been-written
	// (DryRun), or remain unchanged (Unchanged).
	Generated []string
	// Drifted lists files whose disk content differs from the freshly
	// rendered content (Verify mode only).
	Drifted []string
}

Result reports the outcome of Generate.

func Generate

func Generate(root string, p *metadata.ProjectMeta, opts Options) (Result, error)

Generate orchestrates buildContractSpec → render → write for one or all opted-in metadata. root is the repository root (absolute path; from which go.mod is read for module path).

opts.Scope must be non-nil. Use ScopeAll{} for the default "all Codegen=true" behavior, ScopeContracts for a specific ID list, or ScopeCell to restrict to one cell's contracts. A nil Scope is rejected with an error.

func (Result) DriftedFiles

func (r Result) DriftedFiles() []string

DriftedFiles satisfies the cmd/gocell/app.CodegenResult interface.

func (Result) GeneratedFiles

func (r Result) GeneratedFiles() []string

GeneratedFiles satisfies the cmd/gocell/app.CodegenResult interface.

type RetryPolicySpec

type RetryPolicySpec struct {
	MaxAttempts      int
	BaseIntervalExpr string
	MaxIntervalExpr  string
}

RetryPolicySpec is the generation form of kernel/saga.RetryPolicy. Empty interval exprs / zero MaxAttempts are omitted from the emitted literal so the zero value (inherit) is preserved.

type SagaSpec

type SagaSpec struct {
	// DefinitionID is the contract id, used as the saga.Definition.ID const,
	// e.g. "saga.orderfulfillment.v1".
	DefinitionID string
	// TimeoutExpr is the Go duration expression for the saga-wide Timeout, e.g.
	// "30 * time.Second". Empty means the field is omitted (zero => no ceiling).
	TimeoutExpr string
	// RetryPolicy is the saga-wide default retry policy; nil means omit.
	RetryPolicy *RetryPolicySpec
	// CompensationOrder is the validated compensation walk order; always
	// "reverse" today (informational — the runtime owns the reverse walk).
	CompensationOrder string
	// NeedsTime reports whether any duration expr is present, so saga.tmpl
	// imports the time package only when used.
	NeedsTime bool
	// Steps are the forward steps in execution order.
	Steps []SagaStepSpec
}

SagaSpec holds saga-specific generation data (Kind=="saga" only). It drives saga.tmpl, which emits the typed Impl interface + BuildDefinition/Register that adapt impl methods to kernel/saga's untyped StepFunc/CompensateFunc.

type SagaStepSpec

type SagaStepSpec struct {
	// Name is the raw SafeID step name literal, e.g. "reserveInventory".
	Name string
	// GoName is goPascalCase(Name), e.g. "ReserveInventory".
	GoName string
	// OutputGoType is the typed output struct name, goPascalCase(Name)+"Output".
	OutputGoType string
	// InputGoType is the previous step's OutputGoType; empty when IsFirst.
	InputGoType string
	// IsFirst marks step 0 (its Run takes no typed input).
	IsFirst bool
	// HasCompensate is true when the step declares compensation (default true);
	// only then is a Compensate method emitted on Impl + a non-nil
	// saga.Step.Compensate wired.
	HasCompensate bool
	// TimeoutExpr is the Go duration expression for the per-step Timeout; empty
	// means omit (inherit Definition.Timeout).
	TimeoutExpr string
	// RetryPolicy overrides the saga-wide policy for this step; nil means omit.
	RetryPolicy *RetryPolicySpec
}

SagaStepSpec is one generated saga step. InputGoType is the previous step's OutputGoType ("" when IsFirst — the first step takes no typed input because the runtime feeds nil prevState to step 0).

type Schema

type Schema struct {
	Type                 string             // "string" | "integer" | "number" | "boolean" | "object" | "array"
	Format               string             // "uuid" | "date-time" | "int64" | ""
	Properties           map[string]*Schema // type=object
	PropertyOrder        []string           // source order of property keys
	Required             []string           // type=object
	Items                *Schema            // type=array
	Ref                  string             // "$ref" original value (preserved for traceability)
	AdditionalProperties bool               // bool only; schema form is unsupported (true = JSON Schema default)
	Title                string
	MinLength            *int   // type=string; pointer distinguishes 0 from unset
	MaxLength            *int   // type=string
	Minimum              *int64 // numeric types
	Maximum              *int64
	SourcePath           string // file path + JSON pointer for error messages
}

Schema represents the minimal subset of JSON Schema draft 2020-12 used by contractgen. PropertyOrder preserves the source order of properties keys for stable diffs.

func Parse

func Parse(rootDir, refPath string) (*Schema, error)

Parse loads and parses a single JSON Schema file, recursively resolving $ref.

  • rootDir: absolute path to the project root; all resolved $ref paths must remain within this directory (path traversal guard).
  • refPath: path relative to rootDir (e.g. "examples/todoorder/contracts/.../request.schema.json")

Supported $ref forms:

  • same-file: "#/$defs/<name>"
  • relative sibling: "../shared/foo.json" etc.

Unsupported keywords cause an immediate error containing the file path and JSON pointer.

type Scope

type Scope interface {
	// contains filtered or unexported methods
}

Scope is a sealed interface that controls which contracts Generate processes. Use ScopeAll, ScopeContracts, or ScopeCell to construct a value. Passing nil as Scope to Generate will return an error.

RED stub: Generate does not yet read Scope. GREEN phase will integrate it.

type ScopeAll

type ScopeAll struct{}

ScopeAll instructs Generate to process all contracts with Codegen=true. This is the current default behavior (equivalent to omitting OnlyContract).

type ScopeCell

type ScopeCell string

ScopeCell restricts Generate to contracts owned by (server/publisher) the given cell ID. Useful for per-cell codegen invocations.

type ScopeContracts

type ScopeContracts []string

ScopeContracts restricts Generate to the given list of contract IDs. Each ID must exist and have Codegen=true.

Jump to

Keyboard shortcuts

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