tapesoapi

package
v0.41.0 Latest Latest
Warning

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

Go to latest
Published: Aug 31, 2026 License: Apache-2.0, MIT Imports: 22 Imported by: 0

README

tapesoapi

OpenAPI v3 parsing, aggregation, and compilation for tapes.

These are implementation notes: what the pieces are, and why they are shaped the way they are. The API itself is documented in the Go doc comments — go doc ./pkg/tapesoapi — and this file does not repeat them.

Why this package exists

tapes has to publish one description of an HTTP origin that is assembled from two sources nothing off the shelf treats alike:

  • Live Fiber routes. The read API and the ingest server describe themselves as they mount, in the same call that mounts them.
  • Documents fetched over HTTP. A cassette publishes its own OpenAPI at its own /openapi; core fetches it, moves its paths onto the public surface, and reverse-proxies it.

A generator handles the first and a parser handles the second, and gluing a generator's output to a parser's input means two models of the same thing that disagree at the seams. Here both are one thing — a Fragment, a partial contribution tagged with a Provenance — so a single merge → validate → render pipeline serves both. KindRoute, KindDocument, KindManual, and KindReflect are the four kinds in use; Kind is a string rather than an enum so a caller can name its own.

The contract is compiled, never stored

There is no checked-in contract file, no embedded one, and no generator step. Each server compiles its document from its own route registrations and serves it at GET /openapi. There is nothing to regenerate, which is the point: a committed contract describes the routes as of the last time someone remembered to regenerate it, and every check you add for that staleness is a check for a problem this design does not have. Compiling per request is also the only way /openapi can describe cassettes that were mounted at runtime, which no build-time artifact could.

The cost is per-field prose. Field descriptions come from ordinary Go doc comments (never from struct tags), and a deployed binary has no source tree to read them out of. So a running server's document carries route and operation prose but not per-field prose; tapes dev openapi [api|ingest] compiles the fully documented version from a checkout for consumers that want it (it defaults to --docs-root .; pass --docs-root '' for shapes only). pkg/tapesoapi/gosource is the go/ast reader behind that, and it is generator-only by design.

Pipeline

Add* ──(all I/O)──▶ fragments ──▶ Compile ──▶ CompiledDoc
                                     │
       snapshot ▶ merge ▶ parser defaults ▶ version compat
              ▶ resolve refs ▶ render ▶ structure ▶ lint ▶ freeze

Parser is a mutable accumulator behind a mutex — the Fiber adapter contributes a fragment per route registration, and nothing orders those against a concurrent Compile. Everything that can fail slowly (reading a file, fetching a document, parsing YAML) happens at Add time.

Compile is a pure function of the fragments held. It never reads a file or opens a socket, so it is safe on a request path — which is exactly what /openapi does. Compiling the same fragments twice is byte-identical, so Fingerprint() (sha256 of the rendered JSON) is a usable ETag and generated contracts are diffable in CI. Determinism survives concurrent registration because merge order comes from Provenance.sortKey(), not from arrival order.

Freeze marks a parser as closed; further Adds return ErrFrozen. It is for the case where a parser is handed out for reading after its owner is done contributing.

Versions: neutral model, versioned render

The IR stores the union of 3.0 and 3.1 semantics. render() is the only place target Version is consulted, and three keywords carry nearly the whole 3.0-vs-3.1 story:

Concept 3.0 3.1
Nullability nullable: true type: ["string", "null"]
Exclusive bounds exclusiveMinimum: true alongside minimum numeric exclusiveMinimum
Single fixed value enum: [x] const: x

Plus: webhooks and info.license.identifier render only under 3.1, and examples (plural) only under 3.1.

V30 is 3.0.3 and is the default. Not because 3.1 is unfinished — both render — but because the Rust client generator tapes publishes for reads (progenitor) only accepts 3.0.x. A 3.1-only construct reaching a 3.0 render is lost information, so checkVersionCompatibility refuses it and names what would be lost; WithDowngradeLossy() says to approximate instead. The cassette aggregate passes that option, because approximating a cassette's 3.1 construct beats refusing to describe a surface clients can already reach.

ParseVersion maps the whole 3.0.x line to V30 and the whole 3.1.x line to V31. Swagger 2.x is rejected with instructions rather than half-converted.

Three kinds of check, kept separate

  • structure.go — spec MUST rules. A path that does not start with /, an operation with no responses object, a minLength above maxLength, a security requirement naming an undeclared scheme. Violating one of these means the document is not OpenAPI.
  • validate.goLintRules. Legal but unwise, and therefore switchable per call via WithLint: OperationIDPresent, OperationIDUnique, ResponsesDeclared, NoOrphanComponents. These exist because a document that omits an operationId or declares no responses is valid OpenAPI and useless to a code generator, and being generated from is the whole purpose of the published document.
  • checkVersionCompatibility — downgrade loss. Not a defect in the document; a mismatch between the document and the requested target.

Structure runs before lint, so the message a caller sees describes the worse problem first.

Why our own validator instead of importing one

The long-form reasoning is at the top of structure.go; the summary is:

  1. The failing test would be the wrong test. Round-tripping the rendered bytes through a third-party parser catches rendering failures, which render tests already pin. It misses the failure that actually happens: an ingested document, or a route description written by hand, that says something the spec forbids. Catching that well needs the provenance — "cassette summary", "route registered at api/sessions_handlers.go:88" — and provenance is present in the IR and gone from the bytes.
  2. A published contract should not be able to fail to build because someone else's validator changed its mind. /openapi compiles on the request path.
  3. Depending on a second OpenAPI library to check the first one is an anti-pattern even when the dependency is validation-only. A separate, heavier-weight conformance harness is a reasonable thing to want, and it would live outside tapes.

Aggregation

Merging independently authored documents is where the interesting decisions are. ConflictPolicy is PolicyError by default, and ConflictError collects all conflicts rather than returning the first — someone aggregating a fleet wants the list. PolicyFirstWins / PolicyLastWins are for callers that must keep answering.

Per-ingest options rewrite a document into a shape that can coexist with others. applyDocOptions applies them in a fixed order — filter, namespace, name, then prefix:

  • WithComponentNamespace — OpenAPI's component space is flat and two cassettes may each define a Row. Namespacing rewrites the definitions and every $ref that reaches them.
  • WithOperationIDPrefix — the operation-level counterpart. Two cassettes may each have named an operation read, and an id must be unique across the document it appears in.
  • WithPathPrefix — mounts a document's paths under a prefix.
  • WithoutInfo / WithoutServers / WithoutRootExtensions — a cassette's info, servers, and x-tapes-cassette manifest describe the cassette, not the origin. Carrying its servers through would point clients at a listener they cannot reach.

Naming before prefixing is the one ordering a caller can observe: an id synthesized for an anonymous operation comes from the path as the document declared it. A caller that wants the mounted path in the id — because that is the path a generated client calls — rewrites the paths before ingesting instead of passing WithPathPrefix. The cassette runner does exactly that.

SynthesizeOperationID is exported so the Fiber adapter and the aggregate cannot disagree about what one route is called (GET /v1/sessions/{id}getV1SessionsId).

The reflector holds the component registry

parser.Schema(SessionItem{}) reflects a Go type, claims a component name, and returns a $ref to it. The definition lives in the reflector, not in any fragment; Compile folds reflector.Components() in as a synthetic fragment at the end.

The consequence matters for aggregation: compiling another parser's fragments against a fresh reflector produces a document full of refs to schemas it does not define. An aggregate has to borrow the source parser's reflector — WithSchemaReflector(base.Reflector()). Borrowing is read-only; the registry hands out clones under its own lock.

Schema also cannot fail. A type it cannot reflect degrades to &Schema{Description: "schema unavailable: …"}, because the call site is a route registration that has nowhere to put an error.

Document is not the IR

Document (document.go) is a generic, order-preserving tree over a parsed OpenAPI file, with Extension, Paths, Version, RewritePrefix, Marshal, and Fragment. It is deliberately not the IR.

A cassette document is republished — tapes serves it back at /v1/cassettes/{name}/openapi.json — and round-tripping it through the IR would silently drop every field this package does not model, x-tapes-cassette included. So republication is a tree rewrite, and only the merge path goes through the IR. Parse rejects duplicate keys rather than letting the last one win.

Subpackages

  • oasfiber — the Fiber adapter, so the core has no web-framework dependency. Wrap returns a Router that mounts and describes a route in one call; openAPIPath converts :id{id}; callerLocation records the registration site for provenance. Undocumented chooses what an undescribed route means (Stub, Skip, Fail), errors are collected and checked once via Err(), and Server caches the compiled document by fingerprint with an Invalidate hook for runtime mounts.
  • gosource — go/ast doc-comment extraction. Generator-only; never linked into a served path.
  • v3.0 / v3.1 — embedded fixture documents (petstore, nullable-and-bounds, components-and-refs, vendor-extensions, discriminated-union, conflict pairs, webhooks-and-const). Each is described where it is declared, on the principle that a fixture no test explains is a fixture nobody dares change.

Tests are Ginkgo/Gomega throughout, per AGENTS.md.

How tapes consumes it

  • apiNewOpenAPIParser builds the parser every route registers into; api/openapi_routes.go is the registration surface. GET /openapi compiles and serves; GET /swagger serves a Scalar viewer pointed at /openapi, and nothing else serves a spec.

  • ingest — the same pattern for the write surface, publishing its own contract at its own /openapi. It compiles once behind a sync.Once, because every input to that compile is compiled in; the API's aggregate cannot, because cassettes mount at runtime.

  • api/cassetterunnerDocument compiles core's fragments plus every fetched cassette document into the canonical origin contract: component namespace, operationId prefix, PolicyLastWins, borrowed reflector, structural validation, WithLint(OperationIDPresent, OperationIDUnique, ResponsesDeclared), and WithDowngradeLossy().

    Validating that aggregate opened a failure mode, and publishable closes it: /openapi is compiled as a contract rather than as a best-effort catalogue, so a single malformed cassette document could otherwise fail the whole endpoint and take every healthy cassette's surface down with it. Each document is therefore compiled alone at admission time — inside republish, after the path rewrite, so what passes is byte-for-byte what the aggregate merges — and a document that cannot be compiled is rejected against the source that served it.

  • internal/openapicheck — asserts that everything served is described and nothing described is unserved. Now that the contract is compiled from the registrations, staleness is gone as a way for the two to disagree; what is left is routes mounted directly on the *fiber.App (metrics, the viewer, /openapi itself), and the check is what keeps that exemption list deliberate.

Documentation

Overview

Package tapesoapi parses, aggregates, and compiles OpenAPI v3 documents.

It exists because tapes assembles its API description from two sources that nothing off the shelf treats alike: its own live Fiber routes, and the OpenAPI documents cassettes publish over HTTP for reverse-proxy mounting. Both are handled here as the same thing — a Fragment, a partial contribution tagged with where it came from — so one merge, validate, and render pipeline serves both instead of two that drift.

The shape of a use is always the same:

parser := tapesoapi.NewParser(tapesoapi.WithInfo(tapesoapi.Info{
	Title: "tapes", Version: "v1",
}))
if err := parser.AddDocument(ctx, cassetteSpec,
	tapesoapi.WithComponentNamespace("hello_world_")); err != nil {
	return err
}
compiled, err := parser.Compile(ctx)

The Parser is a mutable accumulator guarded by a mutex; Compile is a pure function of the fragments it holds. Compiling the same fragments twice yields byte-identical output, which is what makes the generated contracts diffable in CI and cacheable behind an ETag.

All I/O happens at Add time. Compile never reads a file or opens a socket, so it is safe to call on a request path — which /openapi does.

Versions

The internal model is version-neutral: it stores the union of 3.0 and 3.1 semantics, and the version decision happens once, at render time. Both V30 and V31 render. V30 is the default because the Rust client generator tapes publishes for reads only accepts 3.0.x; a 3.1-only construct reaching a 3.0 render is a documented loss, refused unless WithDowngradeLossy says to approximate it. See version.go.

Fiber

The core has no web-framework dependency. The route-registration wrapper lives in the oasfiber subpackage, against the same Source interface any other adapter would implement.

Index

Constants

View Source
const (
	KindDocument = "document"
	KindRoute    = "route"
	KindManual   = "manual"
	KindReflect  = "reflect"
)

Kind names where a fragment came from. It is a string rather than an enum so a caller implementing Source can name its own kind without patching this package.

Variables

View Source
var ErrFrozen = errors.New("parser is frozen; no further contributions accepted")

ErrFrozen is returned by every Add method once the parser is frozen.

Functions

func Content

func Content(mediaType string, schema *Schema) map[string]*MediaType

Content returns a single-entry content map for an arbitrary media type.

func JSON

func JSON(schema *Schema) map[string]*MediaType

JSON returns a single-entry application/json content map over schema. It is the shorthand almost every operation in a JSON API needs.

func NormalizePath added in v0.34.0

func NormalizePath(path string) (string, error)

NormalizePath canonicalizes a path so two spellings of the same route cannot both appear in one document.

Trailing slashes are trimmed (except on the root) and the path must be absolute. Parameter names are left alone: `{id}` and `{userId}` are different paths to OpenAPI even when they route identically, and silently unifying them would publish an operation nobody wrote.

func PathParams

func PathParams(path string) []string

PathParams returns the template parameter names in a path, in order.

func SynthesizeOperationID

func SynthesizeOperationID(method, openAPIPath string) string

SynthesizeOperationID derives an operationId from a method and an OpenAPI path.

It is exported because two callers need to agree on the answer. The Fiber adapter names an undocumented route with it, and an aggregate names an ingested operation that arrived without an id with it — and if those two ever disagreed, the same route would be called one thing in core's own contract and another in the aggregate that republishes it.

Deterministic, so a compiled document does not churn between builds: same method and path, same id, always.

GET /v1/sessions/{id} → getV1SessionsId

func Text

func Text(schema *Schema) map[string]*MediaType

Text returns a text/plain content map.

Types

type CompileOption

type CompileOption func(*compileOptions)

CompileOption adjusts one compile.

func WithDowngradeLossy

func WithDowngradeLossy() CompileOption

WithDowngradeLossy permits rendering 3.1-only constructs to a 3.0 target by approximating them, instead of failing. Without it, a downgrade that would drop meaning is an error naming the construct and the document it came from.

func WithLint

func WithLint(rules ...LintRule) CompileOption

WithLint replaces the lint rules run after validation.

func WithTarget

func WithTarget(version Version) CompileOption

WithTarget selects the version to render. The default is V30.

func WithoutValidation

func WithoutValidation() CompileOption

WithoutValidation skips structural validation. It is an escape hatch for serving a known-imperfect upstream document rather than failing the request, not a way to land one in a generated contract.

type CompiledDoc

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

CompiledDoc is an immutable compiled OpenAPI document.

It is safe to share across goroutines and cheap to serve repeatedly: the rendered bytes and the fingerprint are computed once, at compile time.

func (*CompiledDoc) ComponentSchema

func (d *CompiledDoc) ComponentSchema(name string) (*Schema, bool)

ComponentSchema returns a compiled component schema by bare name.

The returned schema is a copy, so a caller cannot reach into the compiled document through it.

func (*CompiledDoc) ComponentSchemas

func (d *CompiledDoc) ComponentSchemas() []string

ComponentSchemas returns the names of every component schema, sorted.

func (*CompiledDoc) Fingerprint

func (d *CompiledDoc) Fingerprint() string

Fingerprint is a content hash of the rendered document, for ETags and change detection.

func (*CompiledDoc) JSON

func (d *CompiledDoc) JSON() []byte

JSON returns the rendered JSON bytes.

func (*CompiledDoc) MarshalJSON

func (d *CompiledDoc) MarshalJSON() ([]byte, error)

MarshalJSON returns the document as indented JSON.

func (*CompiledDoc) Operations

func (d *CompiledDoc) Operations() map[string][]string

Operations returns the methods this document describes per path, uppercased and sorted.

It is the accessor for the callers that need the served surface as a set rather than as a document: a coverage check comparing a router's route table against what got published. The alternative is walking CompiledDoc.Tree and re-deriving which keys under a path item are methods and which are metadata (`summary`, `parameters`, `$ref`), and a consumer that got that list wrong would report a phantom operation or miss a real one.

func (*CompiledDoc) Paths

func (d *CompiledDoc) Paths() []string

Paths returns the document's paths in sorted order.

func (*CompiledDoc) Tree

func (d *CompiledDoc) Tree() map[string]any

Tree returns the rendered document as a generic tree. It is the escape hatch for callers that need to post-process the output, and it returns a copy so they cannot mutate the compiled document.

func (*CompiledDoc) ValidateInstance

func (d *CompiledDoc) ValidateInstance(schemaName string, value any) error

ValidateInstance checks a decoded JSON value against one of this document's component schemas.

value is what encoding/json produced — maps, slices, strings, bools, float64 or json.Number, nil. Both number representations are accepted because a caller that decoded with UseNumber did so to keep the digits it was given, and losing that here would defeat the point.

References inside the schema resolve against this document's components, so a composite response validates all the way down without the caller flattening anything first.

func (*CompiledDoc) Version

func (d *CompiledDoc) Version() Version

Version reports which OpenAPI version this document was rendered to.

func (*CompiledDoc) Warnings

func (d *CompiledDoc) Warnings() []string

Warnings reports non-fatal merge outcomes — the conflicts a first-wins or last-wins policy resolved by picking. Empty under the default policy, which fails instead of picking.

func (*CompiledDoc) YAML

func (d *CompiledDoc) YAML() ([]byte, error)

YAML renders the document as YAML. Keys sort the same way as in JSON, so the two encodings describe the same document in the same order.

type Components

type Components struct {
	Schemas         map[string]*Schema
	Responses       map[string]*Response
	Parameters      map[string]*Parameter
	RequestBodies   map[string]*RequestBody
	Headers         map[string]*Header
	Examples        map[string]any
	SecuritySchemes map[string]*SecurityScheme
}

Components is the reusable-object section of a document.

func (*Components) IsEmpty

func (c *Components) IsEmpty() bool

IsEmpty reports whether there is nothing to render.

type Conflict

type Conflict struct {
	// Kind is what collided: "path", "component", or "info".
	Kind string

	// Key names the collision — "GET /users/{id}", "schemas/User".
	Key string

	// Sources are every contributor, in merge order.
	Sources []Provenance
}

Conflict is one key contributed by more than one fragment.

func (Conflict) String

func (c Conflict) String() string

type ConflictError

type ConflictError struct {
	Conflicts []Conflict
}

ConflictError reports every collision at once.

Collect-all rather than fail-fast is deliberate. Someone aggregating a fleet of documents wants the whole list so they can fix it in one pass; failing on the first conflict turns that into one recompile per collision.

func (*ConflictError) Error

func (e *ConflictError) Error() string

type ConflictPolicy

type ConflictPolicy int

ConflictPolicy decides what happens when two fragments contribute the same key.

const (
	// PolicyError collects every conflict and fails the compile. It is the
	// default: an aggregate whose contents depend on which document loaded
	// first is worse than one that refuses to build.
	PolicyError ConflictPolicy = iota

	// PolicyFirstWins keeps the earlier contribution in merge order.
	PolicyFirstWins

	// PolicyLastWins keeps the later contribution in merge order.
	PolicyLastWins
)

The available conflict policies.

func (ConflictPolicy) String

func (p ConflictPolicy) String() string

String names the policy for error messages.

type Contact

type Contact struct {
	Name  string
	URL   string
	Email string
}

Contact is the API's contact information.

type Discriminator

type Discriminator struct {
	PropertyName string
	Mapping      map[string]string
}

Discriminator selects an implementing schema from a payload field.

type DocOption

type DocOption func(*docOptions)

DocOption adjusts how one ingested document is decomposed.

func WithAuthoritativeInfo

func WithAuthoritativeInfo() DocOption

WithAuthoritativeInfo marks this document's Info as the one that wins, rather than colliding with another document's.

func WithComponentNamespace

func WithComponentNamespace(namespace string) DocOption

WithComponentNamespace prefixes every component name, and rewrites every document-local reference to match.

Namespacing pre-empts collisions rather than resolving them. Two cassettes that each define a `Row` schema are not describing the same type, and merging them under one name would publish a schema neither of them wrote.

func WithOperationIDPrefix

func WithOperationIDPrefix(prefix string) DocOption

WithOperationIDPrefix prefixes every operationId in the document, giving one to any operation that arrived without one.

This is the operation-level counterpart to WithComponentNamespace, and it exists for the same reason: an operationId has to be unique across the whole document, and two independently authored inputs are perfectly free to have both named an operation `read`. Namespacing pre-empts that; the alternative is an aggregate that cannot be published as a valid contract.

It is a real edit to a document's contract, so it belongs to aggregation and not to republication. The document a client fetches for one input alone is served verbatim, ids untouched — see the per-cassette endpoint in api/cassetterunner. The prefixed ids exist only in the merged document, where the unprefixed ones could not have coexisted anyway.

func WithPathPrefix

func WithPathPrefix(prefix string) DocOption

WithPathPrefix mounts every path in the document under a prefix.

This and WithComponentNamespace are the aggregation workhorses: together they are what lets three independently authored documents compose into one gateway description without colliding.

func WithProvenance

func WithProvenance(provenance Provenance) DocOption

WithProvenance names the ingested document in conflict errors. Ingestion sets a sensible default (the file path or URL); this overrides it.

func WithTagFilter

func WithTagFilter(keep ...string) DocOption

WithTagFilter ingests only the operations carrying one of the given tags.

func WithoutInfo

func WithoutInfo() DocOption

WithoutInfo drops the document's Info, for merging a document into an aggregate that already has one.

func WithoutRootExtensions

func WithoutRootExtensions() DocOption

WithoutRootExtensions drops the document's root `x-` keys. It is how an aggregate avoids inheriting a per-document extension — a cassette manifest, say — that describes only one of its inputs.

func WithoutServers

func WithoutServers() DocOption

WithoutServers drops the document's servers.

A document being merged into an aggregate usually describes an origin the aggregate does not serve — a cassette's own listener, which clients reach only through core's proxy — and carrying that origin through would send them somewhere they cannot go.

type Document

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

Document is a parsed OpenAPI document held as a generic tree.

It is deliberately *not* the IR. A document fetched from a cassette is republished to clients close to verbatim, and round-tripping it through a typed model would silently drop every field this package does not know about — including the parts of a future OpenAPI revision. The generic tree is what lets core rewrite exactly the paths it must and leave everything else alone.

Use Document.Fragment to move a document into the IR for merging.

func Parse

func Parse(data []byte) (*Document, error)

Parse decodes exactly one JSON object, preserving JSON numbers.

Duplicate keys are rejected rather than last-one-wins. A document that declares the same path twice is ambiguous, and picking a winner would make core's published surface depend on Go's map iteration.

func ParseYAML

func ParseYAML(data []byte) (*Document, error)

ParseYAML decodes a YAML or JSON document. JSON is valid YAML, so this accepts both; Parse is the stricter reader used for documents arriving over the wire, where duplicate-key ambiguity is a security-relevant surprise rather than a typo in a checked-in file.

func (*Document) Extension

func (document *Document) Extension(key string) ([]byte, bool, error)

Extension returns a root extension encoded as JSON.

func (*Document) Fragment

func (document *Document) Fragment(provenance Provenance) (Fragment, error)

Fragment decomposes the document into the version-neutral IR.

Decomposing at ingest — rather than holding loaded documents and merging them at the end — is deliberate: it forces every source through one normalization, and it means a malformed document fails at the Add call that supplied it, where the file name is still in hand, instead of at a Compile that cannot say which of its inputs was wrong.

func (*Document) Marshal

func (document *Document) Marshal() ([]byte, error)

Marshal returns stable indented JSON.

func (*Document) Paths

func (document *Document) Paths() ([]string, error)

Paths returns the declared path keys in stable order.

func (*Document) RewritePrefix

func (document *Document) RewritePrefix(sourcePrefix, targetPrefix string) (*Document, error)

func (*Document) StandaloneRequestSchema added in v0.34.0

func (document *Document) StandaloneRequestSchema(method, path, mediaType string) (map[string]any, error)

StandaloneRequestSchema returns one operation request schema as JSON Schema 2020-12, with every reachable OpenAPI component bundled under $defs. The raw document tree is used so JSON Schema keywords that OpenAPI itself does not interpret are preserved for the standalone consumer.

func (*Document) Version

func (document *Document) Version() (Version, error)

Version reports the version the document declares.

type Encoding

type Encoding struct {
	ContentType string
	Style       string
	Explode     *bool
	Headers     map[string]*Header
}

Encoding describes how a request-body property is serialized.

type ExternalDocs

type ExternalDocs struct {
	Description string
	URL         string
}

ExternalDocs points at documentation outside the spec.

type Fragment

type Fragment struct {
	Provenance Provenance

	// Version is the version the fragment was authored against, empty for
	// fragments built programmatically (which are version-neutral by
	// construction). It is what lets a compile refuse to silently downgrade a
	// 3.1 document.
	Version Version

	Info       *Info
	Servers    []Server
	Tags       []Tag
	Security   []SecurityRequirement
	Paths      map[string]*PathItem
	Components *Components

	// Webhooks are accepted from 3.1 documents and rendered only for 3.1
	// targets. Holding them in the IR now means adding 3.1 output later does
	// not change this type.
	Webhooks map[string]*PathItem

	// Authoritative marks this fragment's Info as the one that wins. Without
	// it, two fragments both setting Info is a conflict rather than a
	// last-one-loaded race.
	Authoritative bool

	Extensions map[string]any
}

Fragment is a partial OpenAPI contribution plus where it came from.

An external YAML file, a Fiber route registration, and a hand-built operation are all the same thing to the parser. Collapsing them into one type is what lets ingestion and live route registration share a single merge, validate, and render pipeline instead of two that drift.

type Header struct {
	Ref         string
	Description string
	Required    bool
	Deprecated  bool
	Schema      *Schema
}

Header is a response header.

type Info

type Info struct {
	Title          string
	Description    string
	TermsOfService string
	Version        string
	Contact        *Contact
	License        *License
	Extensions     map[string]any
}

Info describes the API as a whole.

type InstanceError

type InstanceError struct {
	// Schema names the schema the value was checked against.
	Schema string

	// Violations are the findings, in document order.
	Violations []InstanceViolation
}

InstanceError reports every violation found in one value.

Collect-all, like the other errors here: someone checking a captured payload against a contract wants the whole disagreement, not its first line.

func (*InstanceError) Error

func (e *InstanceError) Error() string

type InstanceViolation

type InstanceViolation struct {
	// Pointer is an RFC 6901 JSON Pointer to the offending value, so a finding
	// in a nested array names the element rather than the document.
	Pointer string

	// Message says what was expected and what was there.
	Message string
}

InstanceViolation is one place a value disagreed with its schema.

func (InstanceViolation) String

func (v InstanceViolation) String() string

type License

type License struct {
	Name string
	URL  string

	// Identifier is an SPDX expression. It is 3.1-only; rendering to 3.0 drops
	// it in favour of Name, which 3.0 requires anyway.
	Identifier string
}

License is the API's license.

type LintError

type LintError struct {
	Findings []string
}

LintError reports every lint finding at once.

func (*LintError) Error

func (e *LintError) Error() string

type LintRule

type LintRule interface {
	// Name identifies the rule in error output.
	Name() string

	// Check returns one finding per problem, empty when the document passes.
	Check(document *LintTarget) []string
}

LintRule is one check over a merged document. Rules run alongside structural validation, so they can assume the document is well formed and concern themselves with whether it is *good*.

func DefaultLintRules

func DefaultLintRules() []LintRule

DefaultLintRules are the rules a compile runs unless WithLint replaces them.

They encode what the published tapes contracts actually need: every operation carries a unique operationId (progenitor panics without one, and a duplicate silently collapses two client methods into one), and every operation documents at least one outcome.

type LintTarget

type LintTarget struct {
	Info       *Info
	Paths      map[string]*PathItem
	Components *Components
}

LintTarget is the read-only view of a merged document that lint rules see. It is a distinct type from the internal merge state so adding a rule never requires reaching into compile internals.

type MediaType

type MediaType struct {
	Schema     *Schema
	Example    any
	Examples   map[string]any
	Encoding   map[string]*Encoding
	Extensions map[string]any
}

MediaType is one content-type entry of a body.

type NoOrphanComponents

type NoOrphanComponents struct{}

NoOrphanComponents reports component schemas nothing references.

An orphan is usually the residue of a deleted operation, and left in place it makes a generated client carry a type no endpoint produces.

func (NoOrphanComponents) Check

func (NoOrphanComponents) Check(document *LintTarget) []string

Check implements LintRule.

func (NoOrphanComponents) Name

func (NoOrphanComponents) Name() string

Name implements LintRule.

type Operation

type Operation struct {
	OperationID  string
	Summary      string
	Description  string
	Tags         []string
	Deprecated   bool
	Parameters   []*Parameter
	RequestBody  *RequestBody
	Responses    map[string]*Response
	Security     []SecurityRequirement
	Servers      []Server
	ExternalDocs *ExternalDocs
	Extensions   map[string]any
	// contains filtered or unexported fields
}

Operation is one method on one path.

type OperationBuilder

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

OperationBuilder describes one operation fluently.

It exists because the alternative — writing struct literals four levels deep to say "this returns a User" — is what pushed the previous generation of Go OpenAPI tooling into doc comments. A builder keeps the description in Go, where the compiler checks the types being described.

op := tapesoapi.NewOperation("getUser").
	Summary("Fetch a user by ID").
	Tag("users").
	PathParam("id", tapesoapi.String(tapesoapi.Format("uuid"))).
	QueryParam("expand", tapesoapi.String()).
	JSONResponse(200, "the user", userSchema)

func NewOperation

func NewOperation(operationID string) *OperationBuilder

NewOperation starts an operation with the given operationId.

The id is required rather than optional because downstream generators need one — progenitor, which builds paper's Rust client from the compiled contract, hard-fails without it — and a synthesized id changes whenever the path does, silently renaming a client method.

func (*OperationBuilder) Build

func (b *OperationBuilder) Build() *Operation

Build returns the described operation.

func (*OperationBuilder) ContentResponse

func (b *OperationBuilder) ContentResponse(status int, description, mediaType string, schema *Schema) *OperationBuilder

ContentResponse records an outcome with an arbitrary media type.

func (*OperationBuilder) CookieParam

func (b *OperationBuilder) CookieParam(name string, schema *Schema, opts ...ParamOption) *OperationBuilder

CookieParam declares a cookie parameter.

func (*OperationBuilder) Deprecated

func (b *OperationBuilder) Deprecated() *OperationBuilder

Deprecated marks the operation deprecated.

func (*OperationBuilder) Description

func (b *OperationBuilder) Description(text string) *OperationBuilder

Description sets the long description.

func (*OperationBuilder) EmptyResponse

func (b *OperationBuilder) EmptyResponse(status int, description string) *OperationBuilder

EmptyResponse records an outcome with no body, such as a 204.

func (*OperationBuilder) Extension

func (b *OperationBuilder) Extension(key string, value any) *OperationBuilder

Extension sets a vendor extension on the operation.

func (*OperationBuilder) HeaderParam

func (b *OperationBuilder) HeaderParam(name string, schema *Schema, opts ...ParamOption) *OperationBuilder

HeaderParam declares a request-header parameter.

func (*OperationBuilder) JSONBody

func (b *OperationBuilder) JSONBody(description string, schema *Schema) *OperationBuilder

JSONBody sets a required application/json request body.

func (*OperationBuilder) JSONResponse

func (b *OperationBuilder) JSONResponse(status int, description string, schema *Schema) *OperationBuilder

JSONResponse records an application/json outcome.

func (*OperationBuilder) OptionalJSONBody

func (b *OperationBuilder) OptionalJSONBody(description string, schema *Schema) *OperationBuilder

OptionalJSONBody sets an optional application/json request body.

func (*OperationBuilder) PathParam

func (b *OperationBuilder) PathParam(name string, schema *Schema, opts ...ParamOption) *OperationBuilder

PathParam declares a path template parameter.

func (*OperationBuilder) QueryParam

func (b *OperationBuilder) QueryParam(name string, schema *Schema, opts ...ParamOption) *OperationBuilder

QueryParam declares a query-string parameter. Optional unless ParamRequired is passed.

func (*OperationBuilder) RequestBody

func (b *OperationBuilder) RequestBody(description string, required bool, content map[string]*MediaType) *OperationBuilder

RequestBody sets the request body from a content map.

func (*OperationBuilder) Response

func (b *OperationBuilder) Response(status int, response *Response) *OperationBuilder

Response records an outcome under a status key.

func (*OperationBuilder) ResponseHeader

func (b *OperationBuilder) ResponseHeader(status int, name string, header *Header) *OperationBuilder

ResponseHeader attaches a header to an already-recorded response.

func (*OperationBuilder) ResponseKey

func (b *OperationBuilder) ResponseKey(key string, response *Response) *OperationBuilder

ResponseKey records an outcome under an arbitrary key, for "default" and the `4XX` wildcard forms a numeric status cannot express.

func (*OperationBuilder) Security

func (b *OperationBuilder) Security(requirement SecurityRequirement) *OperationBuilder

Security adds a security requirement. Repeated calls are alternatives: any one of them satisfies the operation, which is how OpenAPI reads a list.

func (*OperationBuilder) Summary

func (b *OperationBuilder) Summary(text string) *OperationBuilder

Summary sets the short description.

func (*OperationBuilder) Tag

func (b *OperationBuilder) Tag(tags ...string) *OperationBuilder

Tag adds one or more tags.

type OperationIDPresent

type OperationIDPresent struct{}

OperationIDPresent requires an operationId on every operation.

func (OperationIDPresent) Check

func (OperationIDPresent) Check(document *LintTarget) []string

Check implements LintRule.

func (OperationIDPresent) Name

func (OperationIDPresent) Name() string

Name implements LintRule.

type OperationIDUnique

type OperationIDUnique struct{}

OperationIDUnique requires operationIds to be distinct.

func (OperationIDUnique) Check

func (OperationIDUnique) Check(document *LintTarget) []string

Check implements LintRule.

func (OperationIDUnique) Name

func (OperationIDUnique) Name() string

Name implements LintRule.

type Option

type Option func(*parserOptions)

Option configures a Parser.

func WithConflictPolicy

func WithConflictPolicy(policy ConflictPolicy) Option

WithConflictPolicy sets how colliding contributions are resolved. The default is PolicyError, which reports every collision at once rather than picking a winner — an aggregate whose contents depend on load order is worse than one that refuses to build.

func WithInfo

func WithInfo(info Info) Option

WithInfo sets the authoritative document Info. A parser given one is immune to Info conflicts between ingested documents: the aggregate is this API, and the documents merged into it describe parts of it.

func WithSchemaReflector

func WithSchemaReflector(reflector Reflector) Option

WithSchemaReflector replaces the Go-type-to-schema reflector.

func WithServer

func WithServer(url string, description ...string) Option

WithServer appends a server to the compiled document.

type ParamOption

type ParamOption func(*Parameter)

ParamOption adjusts a parameter after its schema is set.

func ParamDeprecated

func ParamDeprecated() ParamOption

ParamDeprecated marks a parameter deprecated.

func ParamDescription

func ParamDescription(text string) ParamOption

ParamDescription documents a parameter.

func ParamExample

func ParamExample(value any) ParamOption

ParamExample sets an example value for a parameter.

func ParamRequired

func ParamRequired() ParamOption

ParamRequired marks a parameter required. Path parameters are required implicitly; this is for the query and header ones that are not.

func ParamStyle

func ParamStyle(style string, explode bool) ParamOption

ParamStyle sets the serialization style, for the array and object parameters where the default is ambiguous.

type Parameter

type Parameter struct {
	// Ref makes this a reference to a component parameter; the other fields
	// are ignored when it is set.
	Ref string

	Name        string
	In          ParameterIn
	Description string
	Required    bool
	Deprecated  bool
	Schema      *Schema
	Example     any
	Style       string
	Explode     *bool
	Extensions  map[string]any
}

Parameter is one operation input.

type ParameterIn

type ParameterIn string

ParameterIn is where a parameter is carried.

const (
	InPath   ParameterIn = "path"
	InQuery  ParameterIn = "query"
	InHeader ParameterIn = "header"
	InCookie ParameterIn = "cookie"
)

The parameter locations OpenAPI defines.

type Parser

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

Parser accumulates fragments from any number of sources.

The zero value is not usable; call NewParser. A Parser is safe for concurrent use, because the Fiber adapter contributes a fragment per route registration and nothing orders those against a concurrent Compile.

func NewParser

func NewParser(options ...Option) *Parser

NewParser returns an empty parser.

func (*Parser) AddComponentSchema

func (p *Parser) AddComponentSchema(name string, schema *Schema, provenance Provenance) error

AddComponentSchema registers one reusable schema under a bare component name.

func (*Parser) AddDocument

func (p *Parser) AddDocument(ctx context.Context, data []byte, options ...DocOption) error

AddDocument ingests one OpenAPI document from bytes. JSON and YAML are both accepted.

func (*Parser) AddDocumentFS

func (p *Parser) AddDocumentFS(ctx context.Context, fsys fs.FS, path string, options ...DocOption) error

AddDocumentFS ingests one OpenAPI document from an fs.FS, which is how an embedded contract is loaded without touching the filesystem at runtime.

func (*Parser) AddDocumentFile

func (p *Parser) AddDocumentFile(ctx context.Context, path string, options ...DocOption) error

AddDocumentFile ingests one OpenAPI document from disk.

func (*Parser) AddDocumentGlob

func (p *Parser) AddDocumentGlob(ctx context.Context, pattern string, options ...DocOption) error

AddDocumentGlob ingests every document matching a shell pattern, in sorted order so a directory of specs compiles the same way on every machine.

func (*Parser) AddDocumentReader

func (p *Parser) AddDocumentReader(ctx context.Context, reader io.Reader, options ...DocOption) error

AddDocumentReader ingests one OpenAPI document from a reader.

func (*Parser) AddFragment

func (p *Parser) AddFragment(fragment Fragment) error

AddFragment records one contribution.

func (*Parser) AddOperation

func (p *Parser) AddOperation(method, path string, operation *Operation, provenance Provenance) error

AddOperation records one programmatically described operation.

path is in OpenAPI form ("/users/{id}"); the Fiber adapter converts from framework syntax before calling this.

func (*Parser) AddParsedDocument

func (p *Parser) AddParsedDocument(document *Document, options ...DocOption) error

AddParsedDocument ingests an already-parsed document, for callers that hold one for another reason — the cassette runner reads a manifest out of the same document it publishes, and re-parsing it would be a second chance to disagree.

func (*Parser) AddSource

func (p *Parser) AddSource(ctx context.Context, source Source) error

AddSource records every fragment a source produces.

func (*Parser) Compile

func (p *Parser) Compile(ctx context.Context, options ...CompileOption) (*CompiledDoc, error)

Compile merges every fragment into one validated document.

The pipeline is snapshot, merge, resolve, render, validate, freeze — and it performs no I/O, so it is safe to call on a request path. Compiling the same fragments twice produces byte-identical output.

func (*Parser) Fragments

func (p *Parser) Fragments() []Fragment

Fragments returns a snapshot of what the parser holds, for inspection and tests. The returned fragments are copies.

func (*Parser) Freeze

func (p *Parser) Freeze()

Freeze makes the parser read-only.

The intended lifecycle is register-everything-then-compile, and a route registered after startup silently changes a document already served. Freezing turns that into a loud error at the registration site.

func (*Parser) Frozen

func (p *Parser) Frozen() bool

Frozen reports whether the parser is read-only.

func (*Parser) Reflector

func (p *Parser) Reflector() Reflector

Reflector returns the parser's Go-type schema reflector, so an adapter can reflect a handler's types against the same registry the parser will compile.

func (*Parser) Schema

func (p *Parser) Schema(value any) *Schema

Schema derives the schema for a Go value's type, registering named struct types as components of this parser's compiled document.

It is the shorthand a route declaration reaches for:

Response(200, "the session", parser.Schema(SessionDetailResponse{}))

A type that cannot be described — a channel, a func — yields a schema that carries the reason, so one bad field degrades that one property instead of failing a registration that has no way to report an error.

type PathItem

type PathItem struct {
	Ref         string
	Summary     string
	Description string
	Servers     []Server

	// Parameters apply to every operation on this path.
	Parameters []*Parameter

	// Operations is keyed by uppercase HTTP method.
	Operations map[string]*Operation

	Extensions map[string]any
}

PathItem is every operation on one path, plus what they share.

func (*PathItem) Methods

func (p *PathItem) Methods() []string

Methods returns this item's methods in a stable order.

type Provenance

type Provenance struct {
	// Kind is the class of contributor: KindDocument, KindRoute, KindManual, or
	// a caller-defined kind.
	Kind string

	// Name identifies the contributor within its kind — a file path, a URL, or
	// a route pattern such as "GET /users/:id".
	Name string

	// Detail is optional extra location information, typically a file:line.
	Detail string
}

Provenance records where a contribution came from, precisely enough that a conflict error can point a reader at the two places to go look.

This is load-bearing rather than logging: aggregation only stays usable at scale if every error names its sources. "GET /users/{id} is defined twice" is a puzzle; "defined by both specs/users.yaml and route users.go:41" is a fix.

func (Provenance) String

func (p Provenance) String() string

String renders a provenance for an error message.

type Reflector

type Reflector interface {
	// Reflect returns the schema for a value's type. Named struct types are
	// registered as components and referenced, so a type used by ten operations
	// is described once.
	Reflect(value any) (*Schema, error)

	// ReflectType is Reflect for a type with no value in hand.
	ReflectType(t reflect.Type) (*Schema, error)

	// Components returns every registered component schema, keyed by bare name.
	Components() map[string]*Schema
}

Reflector turns Go types into schemas and accumulates the named ones as reusable components.

It is an interface so a caller with unusual types can substitute its own derivation without forking the package; NewReflector is the default.

func NewReflector

func NewReflector(options ...ReflectorOption) Reflector

NewReflector returns the default Go-type-to-schema reflector.

type ReflectorOption

type ReflectorOption func(*reflector)

ReflectorOption configures the default reflector.

func WithDocs

func WithDocs(docs TypeDocs) ReflectorOption

WithDocs attaches doc comments to reflected schemas.

func WithPointersNullable

func WithPointersNullable() ReflectorOption

WithPointersNullable marks pointer fields nullable.

Off by default. A Go pointer usually means "optional in the payload", which OpenAPI already expresses by leaving the field out of `required`; rendering every pointer as `nullable: true` would tell a client generator to wrap types in an option *and* admit an explicit null, which is not what most of these handlers do.

func WithTypeNamer

func WithTypeNamer(namer func(reflect.Type) string) ReflectorOption

WithTypeNamer overrides how a Go type becomes a component name.

type RequestBody

type RequestBody struct {
	Ref         string
	Description string
	Required    bool
	Content     map[string]*MediaType
	Extensions  map[string]any
}

RequestBody is an operation's input payload.

type Response

type Response struct {
	Ref string

	// Description is required by the spec for every response object. An empty
	// one is filled in at compile time rather than failing, because a missing
	// description is a documentation gap, not a structural defect.
	Description string
	Content     map[string]*MediaType
	Headers     map[string]*Header
	Extensions  map[string]any
}

Response is one operation outcome.

type ResponsesDeclared

type ResponsesDeclared struct{}

ResponsesDeclared requires at least one documented outcome per operation.

func (ResponsesDeclared) Check

func (ResponsesDeclared) Check(document *LintTarget) []string

Check implements LintRule.

func (ResponsesDeclared) Name

func (ResponsesDeclared) Name() string

Name implements LintRule.

type Schema

type Schema struct {
	// Ref is a document-local reference such as "#/components/schemas/User".
	Ref string

	Type   SchemaType
	Format string

	Title       string
	Description string

	// Nullable widens the type to admit null. Held as a flag rather than as a
	// type union so the IR does not have to commit to a version's spelling.
	Nullable bool

	Default  any
	Example  any
	Examples []any
	Enum     []any

	// Const is 3.1-only. Compiling a document that uses it to V30 is an error
	// unless the compile lowers it, which it does by emitting a single-member
	// enum — the closest 3.0 equivalent.
	Const    any
	HasConst bool

	// Numeric constraints.
	Minimum          *float64
	Maximum          *float64
	ExclusiveMinimum *float64
	ExclusiveMaximum *float64
	MultipleOf       *float64

	// String constraints.
	MinLength *uint64
	MaxLength *uint64
	Pattern   string

	// Array constraints.
	Items       *Schema
	MinItems    *uint64
	MaxItems    *uint64
	UniqueItems bool

	// Object constraints.
	Properties    map[string]*Schema
	Required      []string
	MinProperties *uint64
	MaxProperties *uint64

	// AdditionalProperties is the schema extra properties must satisfy.
	AdditionalProperties *Schema

	// AdditionalPropertiesAllowed is the boolean form of the same keyword. A
	// nil value leaves the keyword unset, which is not the same as `true`:
	// unset lets a consumer apply its own default, and false forbids extras.
	AdditionalPropertiesAllowed *bool

	// Composition.
	OneOf         []*Schema
	AnyOf         []*Schema
	AllOf         []*Schema
	Not           *Schema
	Discriminator *Discriminator

	ReadOnly   bool
	WriteOnly  bool
	Deprecated bool

	// Extensions are `x-` vendor keys rendered verbatim.
	Extensions map[string]any
}

Schema is the version-neutral schema IR.

It stores the union of 3.0 and 3.1 semantics and renders down to whichever version is targeted. Two fields carry the whole version story:

  • Nullable renders as `nullable: true` in 3.0 and as a `"null"` member of the type union in 3.1.
  • ExclusiveMinimum/ExclusiveMaximum are held in 3.1's numeric form, because it is the lossless one: 3.0's boolean form is derivable from it (emit the bound as `minimum` and the flag as `exclusiveMinimum`), while the reverse needs the sibling bound to reconstruct.

A Schema with Ref set is a reference and every other field is ignored, which mirrors how a `$ref` behaves in 3.0.

func AllOf

func AllOf(members ...*Schema) *Schema

AllOf returns a schema satisfied by all of the members, which is how this package expresses composition over a referenced component.

func AnyOf

func AnyOf(alternatives ...*Schema) *Schema

AnyOf returns a schema satisfied by at least one of the alternatives.

func AnyValue

func AnyValue() *Schema

AnyValue returns a schema that constrains nothing — the "any JSON value" schema, rendered as an empty object.

func Array

func Array(items *Schema, opts ...SchemaOption) *Schema

Array returns an array schema over items.

func Boolean

func Boolean(opts ...SchemaOption) *Schema

Boolean returns a boolean schema.

func Integer

func Integer(opts ...SchemaOption) *Schema

Integer returns an integer schema.

func MapOf

func MapOf(value *Schema) *Schema

MapOf returns an object schema whose values all satisfy value — the shape a Go map reflects to.

func Number

func Number(opts ...SchemaOption) *Schema

Number returns a number schema.

func Object

func Object(properties map[string]*Schema, opts ...SchemaOption) *Schema

Object returns an object schema with the given properties.

func OneOf

func OneOf(alternatives ...*Schema) *Schema

OneOf returns a schema satisfied by exactly one of the alternatives.

func Ref

func Ref(ref string) *Schema

Ref returns a schema that is a reference to a document-local component.

func SchemaRef

func SchemaRef(name string) *Schema

SchemaRef returns a reference to a component schema by bare name.

func String

func String(opts ...SchemaOption) *Schema

String returns a string schema.

type SchemaOption

type SchemaOption func(*Schema)

SchemaOption mutates a schema under construction. It is the shared vocabulary of the primitive constructors, so `String(Format("uuid"))` and `Integer(Minimum(0))` read the same way.

func AdditionalProperties

func AdditionalProperties(schema *Schema) SchemaOption

AdditionalProperties constrains extra properties to a schema, which is how a free-form map is described.

func Default

func Default(value any) SchemaOption

Default sets the default value.

func Deprecated

func Deprecated() SchemaOption

Deprecated marks the schema deprecated.

func Description

func Description(text string) SchemaOption

Description sets the schema description.

func Enum

func Enum(values ...any) SchemaOption

Enum restricts the schema to a fixed set of values.

func Example

func Example(value any) SchemaOption

Example sets an example value.

func ExclusiveMaximum

func ExclusiveMaximum(v float64) SchemaOption

ExclusiveMaximum sets an exclusive upper bound.

func ExclusiveMinimum

func ExclusiveMinimum(v float64) SchemaOption

ExclusiveMinimum sets an exclusive lower bound.

func Extension

func Extension(key string, value any) SchemaOption

Extension sets a vendor extension on the schema. The key is prefixed with `x-` if it is not already.

func Format

func Format(format string) SchemaOption

Format sets the format annotation.

func MaxItems

func MaxItems(v uint64) SchemaOption

MaxItems sets the maximum array length.

func MaxLength

func MaxLength(v uint64) SchemaOption

MaxLength sets the maximum string length.

func Maximum

func Maximum(v float64) SchemaOption

Maximum sets an inclusive upper bound.

func MinItems

func MinItems(v uint64) SchemaOption

MinItems sets the minimum array length.

func MinLength

func MinLength(v uint64) SchemaOption

MinLength sets the minimum string length.

func Minimum

func Minimum(v float64) SchemaOption

Minimum sets an inclusive lower bound.

func MultipleOf

func MultipleOf(v float64) SchemaOption

MultipleOf constrains the value to multiples of v.

func NoAdditionalProperties

func NoAdditionalProperties() SchemaOption

NoAdditionalProperties forbids properties beyond those declared.

func Nullable

func Nullable() SchemaOption

Nullable widens the schema to admit null.

func Pattern

func Pattern(expr string) SchemaOption

Pattern sets a regular expression the string must match.

func Property

func Property(name string, schema *Schema) SchemaOption

Property adds one object property.

func ReadOnly

func ReadOnly() SchemaOption

ReadOnly marks the schema as response-only.

func Required

func Required(names ...string) SchemaOption

Required marks object properties as required.

func Title

func Title(text string) SchemaOption

Title sets the schema title.

func UniqueItems

func UniqueItems() SchemaOption

UniqueItems requires array members to be distinct.

func WriteOnly

func WriteOnly() SchemaOption

WriteOnly marks the schema as request-only.

type SchemaType

type SchemaType string

SchemaType is a JSON Schema primitive type.

const (
	TypeString  SchemaType = "string"
	TypeNumber  SchemaType = "number"
	TypeInteger SchemaType = "integer"
	TypeBoolean SchemaType = "boolean"
	TypeArray   SchemaType = "array"
	TypeObject  SchemaType = "object"
	TypeNull    SchemaType = "null"
)

The JSON Schema primitive types. Null is only nameable as a type of its own in 3.1; in 3.0 the IR's Nullable flag carries the same meaning.

type SecurityRequirement

type SecurityRequirement map[string][]string

SecurityRequirement names schemes an operation requires, with their scopes. The map is a disjunction of conjunctions exactly as OpenAPI defines it.

type SecurityScheme

type SecurityScheme struct {
	Type             string
	Description      string
	Name             string
	In               string
	Scheme           string
	BearerFormat     string
	OpenIDConnectURL string
	Flows            map[string]any
	Extensions       map[string]any
}

SecurityScheme declares an authentication mechanism.

type Server

type Server struct {
	URL         string
	Description string
	Variables   map[string]*ServerVariable
}

Server is one base URL the API is served from.

type ServerVariable

type ServerVariable struct {
	Default     string
	Enum        []string
	Description string
}

ServerVariable is a substitution in a server URL template.

type Source

type Source interface {
	Fragments(ctx context.Context) ([]Fragment, error)
}

Source is anything that can contribute fragments.

Document ingestion and the Fiber adapter are both just implementations, and a caller can add its own — pulling specs from a service registry, say — without this package shipping support for it.

type SourceFunc

type SourceFunc func(ctx context.Context) ([]Fragment, error)

SourceFunc adapts a function to Source.

func (SourceFunc) Fragments

func (f SourceFunc) Fragments(ctx context.Context) ([]Fragment, error)

Fragments implements Source.

type Status

type Status string

Status reports how current a cached document is.

const (
	Fresh   Status = "fresh"
	Stale   Status = "stale"
	Missing Status = "missing"
)

The states a cached document can be in.

type StructureError

type StructureError struct {
	// Version is the target the document was checked against, because some
	// rules only apply to one of them.
	Version Version

	// Violations are the findings, sorted and deduplicated.
	Violations []string
}

StructureError reports every structural violation at once, so one compile names the whole list rather than the first item on it.

func (*StructureError) Error

func (e *StructureError) Error() string

type Tag

type Tag struct {
	Name         string
	Description  string
	ExternalDocs *ExternalDocs
}

Tag groups operations.

type TypeDocs

type TypeDocs interface {
	// TypeDoc returns the doc comment for a named type.
	TypeDoc(pkgPath, typeName string) string

	// FieldDoc returns the doc comment for one field of a named type.
	FieldDoc(pkgPath, typeName, fieldName string) string
}

TypeDocs supplies prose that reflection cannot see.

Go's runtime carries no doc comments, so a purely reflective schema is structurally complete and completely undocumented. The generator reads the comments out of the source with [gosource.Load] and hands them here, which keeps documentation next to the field it describes rather than duplicated into a struct tag.

type Version

type Version string

Version is an OpenAPI specification version this package can render.

The internal model is version-neutral: it stores the union of what 3.0 and 3.1 can express, and the version decision happens once, at render time. That is what keeps 3.1 support additive rather than a second parser.

const (
	// V30 renders OpenAPI 3.0.3. It is the default because it is what the
	// published tapes contracts are consumed as — progenitor, which generates
	// paper's Rust client, is 3.0.x-only.
	V30 Version = "3.0.3"

	// V31 renders OpenAPI 3.1.0.
	V31 Version = "3.1.0"
)

func ParseVersion

func ParseVersion(declared string) (Version, error)

ParseVersion maps a document's `openapi` field onto a render target.

Patch releases of a minor version are all rendered the same way — 3.0.0 and 3.0.3 differ in wording, not in what a document may contain — so the whole 3.0.x line maps to V30 and the whole 3.1.x line to V31.

func (Version) String

func (v Version) String() string

String returns the version string written to the document's `openapi` field.

func (Version) Valid

func (v Version) Valid() bool

Valid reports whether v is a version this package renders.

Directories

Path Synopsis
Package gosource reads doc comments out of Go source so reflected schemas can carry prose.
Package gosource reads doc comments out of Go source so reflected schemas can carry prose.
Package oasfiber populates a tapesoapi parser as Fiber routes are registered.
Package oasfiber populates a tapesoapi parser as Fiber routes are registered.
Package v30 holds the OpenAPI 3.0 reference documents this module is tested against.
Package v30 holds the OpenAPI 3.0 reference documents this module is tested against.
Package v31 holds the OpenAPI 3.1 reference documents this module is tested against.
Package v31 holds the OpenAPI 3.1 reference documents this module is tested against.

Jump to

Keyboard shortcuts

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