builder

package
v1.39.0 Latest Latest
Warning

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

Go to latest
Published: Jan 5, 2026 License: MIT Imports: 33 Imported by: 0

Documentation

Overview

Package builder provides programmatic construction of OpenAPI Specification documents.

The builder package enables users to construct OAS documents in Go using a fluent API with automatic reflection-based schema generation. Go types are passed directly to the API, and the builder automatically generates OpenAPI-compatible JSON schemas.

Supported Versions

The builder supports both OAS 2.0 (Swagger) and OAS 3.x (3.0.0 through 3.2.0). Use New() with the desired OAS version and the corresponding Build method:

  • New(parser.OASVersion20) + BuildOAS2() → *parser.OAS2Document with schemas in "definitions"
  • New(parser.OASVersion3xx) + BuildOAS3() → *parser.OAS3Document with schemas in "components/schemas"

The $ref paths are automatically adjusted based on the OAS version:

  • OAS 2.0: "#/definitions/", "#/parameters/", "#/responses/"
  • OAS 3.x: "#/components/schemas/", "#/components/parameters/", "#/components/responses/"

Validation

The builder does not perform OAS specification validation. Use the validator package to validate built documents:

result, _ := spec.BuildResult()
valResult, _ := validator.ValidateWithOptions(validator.WithParsed(*result))

Quick Start

Build an OAS 3.x API specification:

spec := builder.New(parser.OASVersion320).
	SetTitle("My API").
	SetVersion("1.0.0")

spec.AddOperation(http.MethodGet, "/users",
	builder.WithOperationID("listUsers"),
	builder.WithResponse(http.StatusOK, []User{}),
)

doc, err := spec.BuildOAS3()
if err != nil {
	log.Fatal(err)
}
// doc is *parser.OAS3Document - no type assertion needed

Build an OAS 2.0 (Swagger) API specification:

spec := builder.New(parser.OASVersion20).
	SetTitle("My API").
	SetVersion("1.0.0")

spec.AddOperation(http.MethodGet, "/users",
	builder.WithOperationID("listUsers"),
	builder.WithResponse(http.StatusOK, []User{}),
)

doc, err := spec.BuildOAS2()
if err != nil {
	log.Fatal(err)
}
// doc is *parser.OAS2Document - no type assertion needed

Modifying Existing Documents

Use FromDocument or FromOAS2Document to create a builder from an existing document:

// For OAS 3.x documents
b := builder.FromDocument(existingOAS3Doc)
b.AddOperation(http.MethodPost, "/users", ...)
newDoc, _ := b.BuildOAS3()

// For OAS 2.0 documents
b := builder.FromOAS2Document(existingSwaggerDoc)
b.AddOperation(http.MethodPost, "/users", ...)
newDoc, _ := b.BuildOAS2()

Webhooks (OAS 3.1+)

For OAS 3.1+ specifications, webhooks can be added using AddWebhook:

spec := builder.New(parser.OASVersion310).
	SetTitle("Webhook API").
	SetVersion("1.0.0").
	AddWebhook("userCreated", http.MethodPost,
		builder.WithRequestBody("application/json", UserEvent{}),
		builder.WithResponse(http.StatusOK, struct{}{}),
	)

External Documentation

Add document-level external documentation using SetExternalDocs:

spec := builder.New(parser.OASVersion320).
	SetTitle("My API").
	SetVersion("1.0.0").
	SetExternalDocs(&parser.ExternalDocs{
		URL:         "https://docs.example.com",
		Description: "API documentation",
	})

Reflection-Based Schema Generation

The core feature is automatic schema generation from Go types via reflection. When you pass a Go type to WithResponse, WithRequestBody, or parameter options, the builder inspects the type structure and generates an OpenAPI-compatible schema.

Type mappings:

  • string → string
  • int, int32 → integer (format: int32)
  • int64 → integer (format: int64)
  • float32 → number (format: float)
  • float64 → number (format: double)
  • bool → boolean
  • []T → array (items from T)
  • map[string]T → object (additionalProperties from T)
  • struct → object (properties from fields)
  • *T → schema of T (nullable)
  • time.Time → string (format: date-time)

Schema Naming

Schemas are named using the Go convention of "package.TypeName" (e.g., "models.User"). This ensures uniqueness and matches Go developers' expectations for type identification. If multiple packages have the same base name (e.g., github.com/foo/models and github.com/bar/models), the full package path is used to disambiguate (e.g., "github.com_foo_models.User"). Anonymous types are named "AnonymousType".

Extensible Schema Naming

The default schema naming uses "package.TypeName" format. For custom naming, use options when creating a Builder:

Built-in strategies:

// PascalCase: ModelsUser
spec := builder.New(parser.OASVersion320,
    builder.WithSchemaNaming(builder.SchemaNamingPascalCase),
)

// Type only (no package prefix): User
spec := builder.New(parser.OASVersion320,
    builder.WithSchemaNaming(builder.SchemaNamingTypeOnly),
)

Available strategies:

  • SchemaNamingDefault: "package.TypeName" (e.g., models.User)
  • SchemaNamingPascalCase: "PackageTypeName" (e.g., ModelsUser)
  • SchemaNamingCamelCase: "packageTypeName" (e.g., modelsUser)
  • SchemaNamingSnakeCase: "package_type_name" (e.g., models_user)
  • SchemaNamingKebabCase: "package-type-name" (e.g., models-user)
  • SchemaNamingTypeOnly: "TypeName" (e.g., User) - may cause conflicts
  • SchemaNamingFullPath: "full_path_TypeName" (e.g., github.com_org_models_User)

Custom templates using Go text/template:

// Custom format: models+User
spec := builder.New(parser.OASVersion320,
    builder.WithSchemaNameTemplate(`{{.Package}}+{{.Type}}`),
)

Available template functions: pascal, camel, snake, kebab, upper, lower, title, sanitize, trimPrefix, trimSuffix, replace, join.

Custom naming function for maximum flexibility:

spec := builder.New(parser.OASVersion320,
    builder.WithSchemaNameFunc(func(ctx builder.SchemaNameContext) string {
        return strings.ToUpper(ctx.Type)
    }),
)

Note: RegisterTypeAs always takes precedence over any naming strategy.

Generic Types

Go 1.18+ generic types are fully supported. The type parameters are included in the schema name but sanitized for URI safety by replacing brackets with underscores:

Response[User] → "builder.Response_User"
Map[string,int] → "builder.Map_string_int"
Response[List[User]] → "builder.Response_List_User"

This ensures $ref URIs are valid and compatible with all OpenAPI tools, which may not handle square brackets properly in schema references.

Generic type naming strategies control how generic types are formatted:

// "Of" separator: Response[User] → ResponseOfUser
spec := builder.New(parser.OASVersion320,
    builder.WithGenericNaming(builder.GenericNamingOf),
)

Available generic strategies:

  • GenericNamingUnderscore: "Response_User_" (default)
  • GenericNamingOf: "ResponseOfUser"
  • GenericNamingFor: "ResponseForUser"
  • GenericNamingAngleBrackets: "Response<User>" (URI-encoded in $ref)
  • GenericNamingFlattened: "ResponseUser"

For fine-grained control over generic naming:

spec := builder.New(parser.OASVersion320,
    builder.WithGenericNamingConfig(builder.GenericNamingConfig{
        Strategy:        builder.GenericNamingOf,
        ParamSeparator:  "And",
        ApplyBaseCasing: true,
    }),
)

Struct Tags

Customize schema generation with struct tags:

type User struct {
	ID    int64  `json:"id" oas:"description=Unique identifier"`
	Name  string `json:"name" oas:"minLength=1,maxLength=100"`
	Email string `json:"email" oas:"format=email"`
	Role  string `json:"role" oas:"enum=admin|user|guest"`
}

Supported oas tag options:

  • description=<text> - Field description
  • format=<format> - Override format (email, uri, uuid, date, date-time, etc.)
  • enum=<val1>|<val2>|... - Enumeration values
  • minimum=<n>, maximum=<n> - Numeric constraints
  • minLength=<n>, maxLength=<n> - String length constraints
  • pattern=<regex> - String pattern
  • minItems=<n>, maxItems=<n> - Array constraints
  • readOnly=true, writeOnly=true - Access modifiers
  • nullable=true - Explicitly nullable
  • deprecated=true - Mark as deprecated

Custom Field Processors

For libraries that need to support custom struct tag conventions alongside the standard oas:"..." tags, use WithSchemaFieldProcessor:

spec := builder.New(parser.OASVersion320,
    builder.WithSchemaFieldProcessor(func(schema *parser.Schema, field reflect.StructField) *parser.Schema {
        if desc := field.Tag.Get("description"); desc != "" {
            schema.Description = desc
        }
        return schema
    }),
)

The processor is called for each struct field after the base schema is generated and after any oas:"..." tags are applied. This enables:

  • Migration support from other OpenAPI libraries with different tag formats
  • Custom validation tag integration (e.g., reading from validator tags)
  • Documentation generators pulling from godoc-style comments
  • Framework integration with existing struct tag conventions

Multiple processors can be composed using ComposeSchemaFieldProcessors:

composed := builder.ComposeSchemaFieldProcessors(descProcessor, enumProcessor)
spec := builder.New(parser.OASVersion320, builder.WithSchemaFieldProcessor(composed))

Required Fields

Required fields are determined by:

  • Non-pointer fields without omitempty are required
  • Fields with oas:"required=true" are explicitly required
  • Fields with oas:"required=false" are explicitly optional

Operation Responses

Note: OpenAPI requires at least one response per operation. If no responses are defined, the resulting spec will fail OAS validation. Always use WithResponse() or WithDefaultResponse() to add responses to operations.

Parameter Constraints

Add validation constraints to parameters using WithParam* options:

spec.AddOperation(http.MethodGet, "/pets",
	builder.WithQueryParam("limit", int32(0),
		builder.WithParamDescription("Maximum number of pets to return"),
		builder.WithParamMinimum(1),
		builder.WithParamMaximum(100),
		builder.WithParamDefault(20),
	),
	builder.WithQueryParam("status", string(""),
		builder.WithParamEnum("available", "pending", "sold"),
	),
	builder.WithQueryParam("name", string(""),
		builder.WithParamMinLength(1),
		builder.WithParamMaxLength(50),
		builder.WithParamPattern("^[a-zA-Z]+$"),
	),
)

Supported parameter constraint options:

  • WithParamMinimum(min float64) - Minimum value for numeric parameters
  • WithParamMaximum(max float64) - Maximum value for numeric parameters
  • WithParamExclusiveMinimum(exclusive bool) - Whether minimum is exclusive
  • WithParamExclusiveMaximum(exclusive bool) - Whether maximum is exclusive
  • WithParamMultipleOf(value float64) - Value must be a multiple of this (must be > 0)
  • WithParamMinLength(min int) - Minimum length for string parameters (must be >= 0)
  • WithParamMaxLength(max int) - Maximum length for string parameters (must be >= 0)
  • WithParamPattern(pattern string) - Regex pattern for string parameters (validated at build time)
  • WithParamMinItems(min int) - Minimum items for array parameters (must be >= 0)
  • WithParamMaxItems(max int) - Maximum items for array parameters (must be >= 0)
  • WithParamUniqueItems(unique bool) - Whether array items must be unique
  • WithParamEnum(values ...any) - Allowed enumeration values
  • WithParamDefault(value any) - Default value for the parameter

Explicit Type and Format Overrides

Override the inferred OpenAPI type or format when the Go type doesn't map directly to your desired schema:

spec.AddOperation(http.MethodGet, "/users/{user_id}",
	builder.WithPathParam("user_id", "",
		builder.WithParamFormat("uuid"),
	),
	builder.WithQueryParam("version", 0,
		builder.WithParamType("integer"),
		builder.WithParamFormat("int64"),
	),
)

Available type/format override options:

  • WithParamType(typeName string) - Override inferred type (string, integer, number, boolean, array, object)
  • WithParamFormat(format string) - Override inferred format (uuid, email, date-time, byte, binary, etc.)
  • WithParamSchema(schema *parser.Schema) - Full schema override (highest precedence)

Precedence rules:

  • WithParamSchema takes highest precedence (complete replacement)
  • WithParamType replaces the inferred type
  • WithParamFormat replaces the inferred format

Constraint validation is performed when building the document. The following rules are enforced:

  • minimum must be <= maximum (if both are set)
  • minLength must be <= maxLength (if both are set)
  • minItems must be <= maxItems (if both are set)
  • minLength, maxLength, minItems, maxItems must be non-negative
  • multipleOf must be greater than 0
  • pattern must be a valid regex

Form Parameters

Form parameters are handled differently across OAS versions. The builder provides a unified WithFormParam method that automatically adapts to the target OAS version:

spec.AddOperation(http.MethodPost, "/login",
	builder.WithFormParam("username", string(""),
		builder.WithParamRequired(true),
		builder.WithParamMinLength(3),
	),
	builder.WithFormParam("password", string(""),
		builder.WithParamRequired(true),
		builder.WithParamMinLength(8),
	),
	builder.WithResponse(http.StatusOK, LoginResponse{}),
)

OAS 2.0: Form parameters are created as parameters with in="formData":

parameters:
  - name: username
    in: formData
    type: string
    required: true
    minLength: 3

OAS 3.x: Form parameters are added to the request body with application/x-www-form-urlencoded:

requestBody:
  content:
    application/x-www-form-urlencoded:
      schema:
        type: object
        properties:
          username:
            type: string
            minLength: 3
        required:
          - username

All parameter constraints and options work with form parameters. If a request body already exists (e.g., for multipart/form-data), form parameters are merged into it with the application/x-www-form-urlencoded content type.

OAS Version Differences for Constraints

The builder handles constraint placement automatically based on the OAS version:

OAS 3.x (3.0.0+): Constraints are applied to the parameter's Schema field. The OAS 3.x specification separates the parameter metadata (name, location, required) from the value schema (type, format, constraints). This is the modern approach:

parameters:
  - name: limit
    in: query
    schema:
      type: integer
      minimum: 1        # Constraint on schema
      maximum: 100      # Constraint on schema

OAS 2.0 (Swagger): Constraints are applied directly to the Parameter object. In OAS 2.0, non-body parameters have type and constraints as top-level fields:

parameters:
  - name: limit
    in: query
    type: integer
    minimum: 1          # Constraint on parameter
    maximum: 100        # Constraint on parameter

The builder abstracts this difference, allowing you to use the same WithParam* options regardless of target OAS version.

Custom Content Types

Use WithResponseContentType to specify content types other than the default "application/json":

spec.AddOperation(http.MethodGet, "/users",
	builder.WithResponse(http.StatusOK, []User{},
		builder.WithResponseContentType("application/xml"),
	),
)

For multiple content types, use WithRequestBodyContentTypes or WithResponseContentTypes:

spec.AddOperation(http.MethodPost, "/users",
	builder.WithRequestBodyContentTypes(
		[]string{"application/json", "application/xml"},
		User{},
	),
	builder.WithResponseContentTypes(http.StatusOK,
		[]string{"application/json", "application/xml"},
		User{},
	),
)

Vendor Extensions

Add vendor extensions (x-* fields) to operations, parameters, responses, and request bodies:

spec.AddOperation(http.MethodGet, "/users",
	builder.WithOperationExtension("x-rate-limit", 100),
	builder.WithQueryParam("limit", int32(0),
		builder.WithParamExtension("x-ui-widget", "slider"),
	),
	builder.WithResponse(http.StatusOK, []User{},
		builder.WithResponseExtension("x-cache-ttl", 3600),
	),
)

spec.AddOperation(http.MethodPost, "/users",
	builder.WithRequestBody("application/json", User{},
		builder.WithRequestBodyExtension("x-codegen-request-body-name", "user"),
	),
)

OAS 2.0 Specific Options

For OAS 2.0 specifications, additional options are available:

spec.AddOperation(http.MethodPost, "/users",
	builder.WithConsumes("application/json", "application/xml"),
	builder.WithProduces("application/json"),
	builder.WithQueryParam("tags", []string{},
		builder.WithParamCollectionFormat("csv"),  // csv, ssv, tsv, pipes, multi
		builder.WithParamAllowEmptyValue(true),
	),
)

See the examples in example_test.go for more patterns.

Semantic Schema Deduplication

The builder can automatically identify and consolidate structurally identical schemas, reducing document size when multiple types converge to the same structure.

Enable via option:

spec := builder.New(parser.OASVersion320,
    builder.WithSemanticDeduplication(true),
)

// Add schemas that happen to be structurally identical
spec.AddOperation(http.MethodGet, "/addresses",
    builder.WithResponse(http.StatusOK, []Address{}),
)
spec.AddOperation(http.MethodGet, "/locations",
    builder.WithResponse(http.StatusOK, []Location{}), // Same structure as Address
)

doc, _ := spec.BuildOAS3()
// Only one schema exists; all $refs point to the canonical (alphabetically first) name

Or call manually before building:

spec.DeduplicateSchemas()
doc, _ := spec.BuildOAS3()

Deduplication compares schemas structurally, ignoring metadata fields (title, description, example, deprecated). When duplicates are found, the alphabetically first name becomes canonical, and all references are automatically rewritten.

Server Builder

The ServerBuilder extends Builder to produce runnable HTTP servers directly from the fluent API. This enables a "code-first" development workflow where developers define API operations and their implementations in a single fluent API.

Create a server builder:

srv := builder.NewServerBuilder(parser.OASVersion320).
	SetTitle("Pet Store API").
	SetVersion("1.0.0")

srv.AddOperation(http.MethodGet, "/pets",
	builder.WithHandler(listPetsHandler),  // Inline handler registration
	builder.WithResponse(http.StatusOK, []Pet{}),
)

result, err := srv.BuildServer()
if err != nil {
	log.Fatal(err)
}
http.ListenAndServe(":8080", result.Handler)

The ServerBuilder provides:

  • Automatic request validation using the httpvalidator package
  • Type-safe response construction with response helpers (JSON, NoContent, Error, Redirect, Stream)
  • Middleware support via the Use method
  • Flexible routing using the stdlib net/http router
  • Recovery middleware for panic handling
  • Request logging middleware

Handler Registration

Register handlers inline using WithHandler in AddOperation:

srv.AddOperation(http.MethodGet, "/pets",
	builder.WithHandler(func(ctx context.Context, req *builder.Request) builder.Response {
		// req.PathParams, req.QueryParams contain validated parameters
		// req.Body contains the unmarshaled request body
		return builder.JSON(http.StatusOK, pets)
	}),
	builder.WithResponse(http.StatusOK, []Pet{}),
)

Or register handlers dynamically using Handle or HandleFunc:

// Using the typed handler signature with method and path
srv.Handle(http.MethodGet, "/pets", listPetsHandler)

// Using standard http.HandlerFunc for simpler operations
srv.HandleFunc(http.MethodGet, "/health", func(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	w.Write([]byte("OK"))
})

Operations without registered handlers return 501 Not Implemented at runtime.

Response Helpers

The builder provides convenient response constructors:

// JSON response
return builder.JSON(http.StatusOK, data)

// No content response
return builder.NoContent()

// Error response
return builder.Error(http.StatusNotFound, "not found")

// Redirect response
return builder.Redirect(http.StatusMovedPermanently, "/new-location")

// Streaming response
return builder.Stream(http.StatusOK, "application/octet-stream", reader)

// Fluent response builder
return builder.NewResponse(http.StatusOK).
	Header("X-Custom", "value").
	JSON(data)

Server Configuration

Configure the server builder with options:

srv := builder.NewServerBuilder(parser.OASVersion320,
	builder.WithoutValidation(),                    // Disable request validation
	builder.WithRecovery(),                         // Enable panic recovery
	builder.WithRequestLogging(loggingFunc),        // Enable request logging
	builder.WithErrorHandler(customErrorHandler),   // Custom error handler
	builder.WithNotFoundHandler(custom404Handler),  // Custom 404 handler
)

Middleware

Add middleware to the server. Middleware is applied in order: first added = outermost.

srv.Use(corsMiddleware, authMiddleware, loggingMiddleware)

Testing

The package provides testing utilities:

result := srv.MustBuildServer()
test := builder.NewServerTest(result)

// Execute requests
rec := test.Execute(builder.NewTestRequest(http.MethodGet, "/pets"))

// JSON helpers
var pets []Pet
rec, err := test.GetJSON("/pets", &pets)
rec, err := test.PostJSON("/pets", newPet, &created)

// Stub handlers for testing
srv.Handle(http.MethodGet, "/pets", builder.StubHandler(builder.JSON(http.StatusOK, mockPets)))

The builder integrates with other oastools packages:

Example

Example demonstrates basic builder usage.

package main

import (
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/erraggy/oastools/builder"
	"github.com/erraggy/oastools/parser"
)

// Pet represents a pet in the store.
type Pet struct {
	ID        int64     `json:"id" oas:"description=Unique pet identifier"`
	Name      string    `json:"name" oas:"minLength=1,description=Pet name"`
	Tag       string    `json:"tag,omitempty" oas:"description=Optional tag"`
	CreatedAt time.Time `json:"created_at" oas:"readOnly=true"`
}

func main() {
	spec := builder.New(parser.OASVersion320).
		SetTitle("Pet Store API").
		SetVersion("1.0.0")

	spec.AddOperation(http.MethodGet, "/pets",
		builder.WithOperationID("listPets"),
		builder.WithResponse(http.StatusOK, []Pet{}),
	)

	// Use BuildOAS3() for type-safe access - no type assertion needed
	doc, err := spec.BuildOAS3()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("OpenAPI: %s\n", doc.OpenAPI)
	fmt.Printf("Title: %s\n", doc.Info.Title)
	fmt.Printf("Paths: %d\n", len(doc.Paths))
}
Output:

OpenAPI: 3.2.0
Title: Pet Store API
Paths: 1
Example (CompleteAPI)

Example_completeAPI demonstrates a complete API specification.

package main

import (
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/erraggy/oastools/builder"
	"github.com/erraggy/oastools/parser"
)

// Pet represents a pet in the store.
type Pet struct {
	ID        int64     `json:"id" oas:"description=Unique pet identifier"`
	Name      string    `json:"name" oas:"minLength=1,description=Pet name"`
	Tag       string    `json:"tag,omitempty" oas:"description=Optional tag"`
	CreatedAt time.Time `json:"created_at" oas:"readOnly=true"`
}

// Error represents an API error.
type Error struct {
	Code    int32  `json:"code" oas:"description=Error code"`
	Message string `json:"message" oas:"description=Error message"`
}

func main() {
	spec := builder.New(parser.OASVersion320).
		SetTitle("Pet Store API").
		SetVersion("1.0.0").
		SetDescription("A sample Pet Store API demonstrating the builder package").
		AddServer("https://api.petstore.example.com/v1",
			builder.WithServerDescription("Production server"),
		).
		AddTag("pets", builder.WithTagDescription("Operations about pets")).
		AddAPIKeySecurityScheme("api_key", "header", "X-API-Key", "API key").
		SetSecurity(builder.SecurityRequirement("api_key"))

	// List pets
	spec.AddOperation(http.MethodGet, "/pets",
		builder.WithOperationID("listPets"),
		builder.WithSummary("List all pets"),
		builder.WithTags("pets"),
		builder.WithQueryParam("limit", int32(0),
			builder.WithParamDescription("Maximum number of pets to return"),
		),
		builder.WithResponse(http.StatusOK, []Pet{},
			builder.WithResponseDescription("A list of pets"),
		),
		builder.WithResponse(http.StatusInternalServerError, Error{},
			builder.WithResponseDescription("Unexpected error"),
		),
	)

	// Get pet by ID
	spec.AddOperation(http.MethodGet, "/pets/{petId}",
		builder.WithOperationID("getPet"),
		builder.WithSummary("Get a pet by ID"),
		builder.WithTags("pets"),
		builder.WithPathParam("petId", int64(0),
			builder.WithParamDescription("The ID of the pet to retrieve"),
		),
		builder.WithResponse(http.StatusOK, Pet{},
			builder.WithResponseDescription("The requested pet"),
		),
		builder.WithResponse(http.StatusNotFound, Error{},
			builder.WithResponseDescription("Pet not found"),
		),
	)

	doc, err := spec.BuildOAS3()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Title: %s\n", doc.Info.Title)
	fmt.Printf("Paths: %d\n", len(doc.Paths))
	fmt.Printf("Tags: %d\n", len(doc.Tags))
	fmt.Printf("Schemas: %d\n", len(doc.Components.Schemas))
}
Output:

Title: Pet Store API
Paths: 2
Tags: 1
Schemas: 2
Example (ComposeSchemaFieldProcessors)

Example_composeSchemaFieldProcessors demonstrates composing multiple field processors. This is useful when you have separate concerns that each need to process struct tags, such as description handling, enum parsing, and numeric constraint validation. Processors execute in order, with each receiving the schema from the previous processor.

package main

import (
	"fmt"
	"log"
	"net/http"
	"reflect"
	"strconv"
	"strings"

	"github.com/erraggy/oastools/builder"
	"github.com/erraggy/oastools/parser"
)

func main() {
	// Define a struct with various legacy tags
	type ServerConfig struct {
		Host    string `json:"host" description:"Server hostname"`
		Port    int    `json:"port" description:"Server port" minimum:"1" maximum:"65535"`
		Timeout int    `json:"timeout" description:"Request timeout in seconds" minimum:"1"`
		Mode    string `json:"mode" description:"Operating mode" enum:"development|staging|production"`
	}

	// Processor for description tags
	descriptionProcessor := func(schema *parser.Schema, field reflect.StructField) *parser.Schema {
		if desc := field.Tag.Get("description"); desc != "" {
			schema.Description = desc
		}
		return schema
	}

	// Processor for enum tags (pipe-separated)
	enumProcessor := func(schema *parser.Schema, field reflect.StructField) *parser.Schema {
		if enumStr := field.Tag.Get("enum"); enumStr != "" {
			values := strings.Split(enumStr, "|")
			schema.Enum = make([]any, len(values))
			for i, v := range values {
				schema.Enum[i] = strings.TrimSpace(v)
			}
		}
		return schema
	}

	// Processor for numeric constraint tags
	numericProcessor := func(schema *parser.Schema, field reflect.StructField) *parser.Schema {
		if minStr := field.Tag.Get("minimum"); minStr != "" {
			if min, err := strconv.ParseFloat(minStr, 64); err == nil {
				schema.Minimum = &min
			}
		}
		if maxStr := field.Tag.Get("maximum"); maxStr != "" {
			if max, err := strconv.ParseFloat(maxStr, 64); err == nil {
				schema.Maximum = &max
			}
		}
		return schema
	}

	// Compose all processors into a single processor
	composedProcessor := builder.ComposeSchemaFieldProcessors(
		descriptionProcessor,
		enumProcessor,
		numericProcessor,
	)

	// Build specification with the composed processor
	spec := builder.New(parser.OASVersion320,
		builder.WithSchemaFieldProcessor(composedProcessor),
	).
		SetTitle("Server Config API").
		SetVersion("1.0.0").
		AddOperation(http.MethodGet, "/config",
			builder.WithResponse(http.StatusOK, ServerConfig{}),
		)

	doc, err := spec.BuildOAS3()
	if err != nil {
		log.Fatal(err)
	}

	// Access the generated schema
	configSchema := doc.Components.Schemas["builder_test.ServerConfig"]
	hostSchema := configSchema.Properties["host"]
	portSchema := configSchema.Properties["port"]
	modeSchema := configSchema.Properties["mode"]

	fmt.Printf("Host description: %s\n", hostSchema.Description)
	fmt.Printf("Port description: %s\n", portSchema.Description)
	fmt.Printf("Port minimum: %.0f\n", *portSchema.Minimum)
	fmt.Printf("Port maximum: %.0f\n", *portSchema.Maximum)
	fmt.Printf("Mode description: %s\n", modeSchema.Description)
	fmt.Printf("Mode enum: %v\n", modeSchema.Enum)
}
Output:

Host description: Server hostname
Port description: Server port
Port minimum: 1
Port maximum: 65535
Mode description: Operating mode
Mode enum: [development staging production]
Example (FromDocument)

Example_fromDocument demonstrates modifying an existing document.

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/erraggy/oastools/builder"
	"github.com/erraggy/oastools/parser"
)

func main() {
	// Create an existing document (in real code, this would be parsed from a file)
	existingDoc := &parser.OAS3Document{
		OpenAPI: "3.0.3",
		Info: &parser.Info{
			Title:   "Existing API",
			Version: "1.0.0",
		},
		Paths: parser.Paths{
			"/existing": &parser.PathItem{
				Get: &parser.Operation{
					OperationID: "existingOperation",
					Responses: &parser.Responses{
						Codes: map[string]*parser.Response{
							"200": {
								Description: "Existing response",
							},
						},
					},
				},
			},
		},
	}

	// Create builder from existing document and add new operations
	spec := builder.FromDocument(existingDoc)

	type HealthResponse struct {
		Status string `json:"status"`
	}

	spec.AddOperation(http.MethodGet, "/health",
		builder.WithOperationID("healthCheck"),
		builder.WithResponse(http.StatusOK, HealthResponse{}),
	)

	doc, err := spec.BuildOAS3()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Paths: %d\n", len(doc.Paths))
	fmt.Printf("Has /existing: %v\n", doc.Paths["/existing"] != nil)
	fmt.Printf("Has /health: %v\n", doc.Paths["/health"] != nil)
}
Output:

Paths: 2
Has /existing: true
Has /health: true
Example (GenericNamingConfig)

Example_genericNamingConfig demonstrates fine-grained generic type naming configuration. Use WithGenericNamingConfig for full control over how generic type parameters are formatted.

package main

import (
	"fmt"
	"net/http"
	"time"

	"github.com/erraggy/oastools/builder"
	"github.com/erraggy/oastools/parser"
)

// Pet represents a pet in the store.
type Pet struct {
	ID        int64     `json:"id" oas:"description=Unique pet identifier"`
	Name      string    `json:"name" oas:"minLength=1,description=Pet name"`
	Tag       string    `json:"tag,omitempty" oas:"description=Optional tag"`
	CreatedAt time.Time `json:"created_at" oas:"readOnly=true"`
}

func main() {
	// Configure generic naming with custom settings.
	// This example uses GenericNamingOf strategy with "And" as the separator
	// between multiple type parameters.
	//
	// For generic types like Response[User], this would produce "ResponseOfUser".
	// For types like Map[string,int], this would produce "MapOfStringAndOfInt".
	spec := builder.New(parser.OASVersion320,
		builder.WithGenericNamingConfig(builder.GenericNamingConfig{
			Strategy:        builder.GenericNamingOf,
			ParamSeparator:  "And",
			ApplyBaseCasing: true,
		}),
	).
		SetTitle("Example API").
		SetVersion("1.0.0").
		AddOperation(http.MethodGet, "/pets",
			builder.WithResponse(http.StatusOK, []Pet{}),
		)

	doc, err := spec.BuildOAS3()
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	fmt.Printf("Title: %s\n", doc.Info.Title)
	fmt.Printf("Schemas: %d\n", len(doc.Components.Schemas))
}
Output:

Title: Example API
Schemas: 1
Example (SchemaGeneration)

Example_schemaGeneration demonstrates automatic schema generation.

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/erraggy/oastools/builder"
	"github.com/erraggy/oastools/parser"
)

func main() {
	type Address struct {
		Street  string `json:"street"`
		City    string `json:"city"`
		Country string `json:"country"`
	}

	type Customer struct {
		ID      int64   `json:"id"`
		Name    string  `json:"name"`
		Email   string  `json:"email" oas:"format=email"`
		Address Address `json:"address"`
	}

	spec := builder.New(parser.OASVersion320).
		SetTitle("Customer API").
		SetVersion("1.0.0").
		AddOperation(http.MethodGet, "/customers/{id}",
			builder.WithOperationID("getCustomer"),
			builder.WithPathParam("id", int64(0)),
			builder.WithResponse(http.StatusOK, Customer{}),
		)

	doc, err := spec.BuildOAS3()
	if err != nil {
		log.Fatal(err)
	}

	// Both Customer and Address schemas are auto-generated with package-qualified names
	_, hasCustomer := doc.Components.Schemas["builder_test.Customer"]
	_, hasAddress := doc.Components.Schemas["builder_test.Address"]
	fmt.Printf("Has Customer schema: %v\n", hasCustomer)
	fmt.Printf("Has Address schema: %v\n", hasAddress)
}
Output:

Has Customer schema: true
Has Address schema: true
Example (SchemaNamingCustomFunc)

Example_schemaNamingCustomFunc demonstrates custom function-based schema naming. Use WithSchemaNameFunc for maximum flexibility when you need programmatic control over schema names based on type metadata.

package main

import (
	"fmt"
	"net/http"

	"github.com/erraggy/oastools/builder"
	"github.com/erraggy/oastools/parser"
)

func main() {
	type Order struct {
		ID     int64   `json:"id"`
		Total  float64 `json:"total"`
		Status string  `json:"status"`
	}

	// Custom naming function that prefixes schemas with API version
	// and converts the type name to uppercase
	apiVersion := "V2"
	customNamer := func(ctx builder.SchemaNameContext) string {
		// Use the type name, converting to uppercase for emphasis
		return apiVersion + "_" + ctx.Type
	}

	spec := builder.New(parser.OASVersion320,
		builder.WithSchemaNameFunc(customNamer),
	).
		SetTitle("Order API").
		SetVersion("2.0.0").
		AddOperation(http.MethodGet, "/orders",
			builder.WithResponse(http.StatusOK, Order{}),
		)

	doc, err := spec.BuildOAS3()
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	// Print schema names
	for name := range doc.Components.Schemas {
		fmt.Println("Schema:", name)
	}
}
Output:

Schema: V2_Order
Example (SchemaNamingPascalCase)

Example_schemaNamingPascalCase demonstrates PascalCase schema naming strategy. With SchemaNamingPascalCase, "package.TypeName" becomes "PackageTypeName".

package main

import (
	"fmt"
	"net/http"

	"github.com/erraggy/oastools/builder"
	"github.com/erraggy/oastools/parser"
)

func main() {
	type User struct {
		ID   int    `json:"id"`
		Name string `json:"name"`
	}

	spec := builder.New(parser.OASVersion320,
		builder.WithSchemaNaming(builder.SchemaNamingPascalCase),
	).
		SetTitle("Example API").
		SetVersion("1.0.0").
		AddOperation(http.MethodGet, "/users",
			builder.WithResponse(http.StatusOK, User{}),
		)

	doc, err := spec.BuildOAS3()
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	// Print schema names
	for name := range doc.Components.Schemas {
		fmt.Println("Schema:", name)
	}
}
Output:

Schema: BuilderTestUser
Example (SchemaNamingTemplate)

Example_schemaNamingTemplate demonstrates custom template-based schema naming. Templates use Go text/template syntax with helper functions like pascal, camel, etc.

package main

import (
	"fmt"
	"net/http"

	"github.com/erraggy/oastools/builder"
	"github.com/erraggy/oastools/parser"
)

func main() {
	type Product struct {
		ID    int     `json:"id"`
		Price float64 `json:"price"`
	}

	// Custom template: prefix with "API" and use pascal case
	spec := builder.New(parser.OASVersion320,
		builder.WithSchemaNameTemplate(`API{{pascal .Type}}`),
	).
		SetTitle("Example API").
		SetVersion("1.0.0").
		AddOperation(http.MethodGet, "/products",
			builder.WithResponse(http.StatusOK, Product{}),
		)

	doc, err := spec.BuildOAS3()
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	// Print schema names
	for name := range doc.Components.Schemas {
		fmt.Println("Schema:", name)
	}
}
Output:

Schema: APIProduct
Example (SemanticDeduplication)

Example_semanticDeduplication demonstrates automatic consolidation of identical schemas. When multiple Go types generate structurally identical schemas, enabling semantic deduplication identifies these duplicates and consolidates them to a single canonical schema.

package main

import (
	"fmt"
	"net/http"

	"github.com/erraggy/oastools/builder"
	"github.com/erraggy/oastools/parser"
)

func main() {
	// Define types that are structurally identical but have different names
	type UserID struct {
		Value int64 `json:"value"`
	}
	type CustomerID struct {
		Value int64 `json:"value"`
	}
	type OrderID struct {
		Value int64 `json:"value"`
	}

	// Build specification with semantic deduplication enabled
	spec := builder.New(parser.OASVersion320,
		builder.WithSemanticDeduplication(true),
	).
		SetTitle("ID API").
		SetVersion("1.0.0").
		AddOperation(http.MethodGet, "/users/{id}",
			builder.WithResponse(http.StatusOK, UserID{}),
		).
		AddOperation(http.MethodGet, "/customers/{id}",
			builder.WithResponse(http.StatusOK, CustomerID{}),
		).
		AddOperation(http.MethodGet, "/orders/{id}",
			builder.WithResponse(http.StatusOK, OrderID{}),
		)

	doc, err := spec.BuildOAS3()
	if err != nil {
		fmt.Println("Error:", err)
		return
	}

	// Without deduplication: 3 schemas (UserID, CustomerID, OrderID)
	// With deduplication: 1 schema (the alphabetically first canonical name)
	fmt.Printf("Title: %s\n", doc.Info.Title)
	fmt.Printf("Schemas: %d\n", len(doc.Components.Schemas))
	fmt.Printf("Operations: %d\n", len(doc.Paths))
}
Output:

Title: ID API
Schemas: 1
Operations: 3
Example (ServerBuilder)

Example_serverBuilder demonstrates building a runnable HTTP server from an OpenAPI spec. ServerBuilder extends Builder to create an http.Handler with automatic routing, request validation, and typed request/response handling.

package main

import (
	"context"
	"fmt"
	"log"
	"net/http"

	"github.com/erraggy/oastools/builder"
	"github.com/erraggy/oastools/parser"
)

func main() {
	type Message struct {
		Text string `json:"text"`
	}

	// Create a server builder (extends Builder with server capabilities)
	srv := builder.NewServerBuilder(parser.OASVersion320, builder.WithoutValidation()).
		SetTitle("Message API").
		SetVersion("1.0.0")

	// Add operation and register handler
	srv.AddOperation(http.MethodGet, "/message",
		builder.WithOperationID("getMessage"),
		builder.WithResponse(http.StatusOK, Message{}),
	)

	srv.Handle(http.MethodGet, "/message", func(_ context.Context, _ *builder.Request) builder.Response {
		return builder.JSON(http.StatusOK, Message{Text: "Hello, World!"})
	})

	// Build the server
	result, err := srv.BuildServer()
	if err != nil {
		log.Fatal(err)
	}

	// result.Handler is a standard http.Handler ready to serve
	// result.Spec contains the generated OpenAPI document
	fmt.Printf("Handler type: %T\n", result.Handler)
	fmt.Printf("Has spec: %v\n", result.Spec != nil)
}
Output:

Handler type: http.HandlerFunc
Has spec: true
Example (ServerBuilderCRUD)

Example_serverBuilderCRUD demonstrates a complete CRUD API with ServerBuilder. This shows the typical pattern of defining operations, registering handlers, and building a production-ready HTTP server.

package main

import (
	"context"
	"fmt"
	"net/http"
	"time"

	"github.com/erraggy/oastools/builder"
	"github.com/erraggy/oastools/parser"
)

// Pet represents a pet in the store.
type Pet struct {
	ID        int64     `json:"id" oas:"description=Unique pet identifier"`
	Name      string    `json:"name" oas:"minLength=1,description=Pet name"`
	Tag       string    `json:"tag,omitempty" oas:"description=Optional tag"`
	CreatedAt time.Time `json:"created_at" oas:"readOnly=true"`
}

func main() {
	// Create server with validation disabled for this example
	srv := builder.NewServerBuilder(parser.OASVersion320, builder.WithoutValidation()).
		SetTitle("Pet Store API").
		SetVersion("1.0.0")

	// Define CRUD operations
	srv.AddOperation(http.MethodGet, "/pets",
		builder.WithOperationID("listPets"),
		builder.WithResponse(http.StatusOK, []Pet{}),
	)

	srv.AddOperation(http.MethodPost, "/pets",
		builder.WithOperationID("createPet"),
		builder.WithRequestBody("application/json", Pet{}),
		builder.WithResponse(http.StatusCreated, Pet{}),
	)

	srv.AddOperation(http.MethodGet, "/pets/{petId}",
		builder.WithOperationID("getPet"),
		builder.WithPathParam("petId", int64(0)),
		builder.WithResponse(http.StatusOK, Pet{}),
	)

	srv.AddOperation(http.MethodDelete, "/pets/{petId}",
		builder.WithOperationID("deletePet"),
		builder.WithPathParam("petId", int64(0)),
		builder.WithResponse(http.StatusNoContent, nil),
	)

	// Register handlers for each operation
	srv.Handle(http.MethodGet, "/pets", func(_ context.Context, _ *builder.Request) builder.Response {
		return builder.JSON(http.StatusOK, []Pet{{ID: 1, Name: "Fluffy"}})
	})

	srv.Handle(http.MethodPost, "/pets", func(_ context.Context, req *builder.Request) builder.Response {
		// req.Body contains the parsed request body
		return builder.JSON(http.StatusCreated, req.Body)
	})

	srv.Handle(http.MethodGet, "/pets/{petId}", func(_ context.Context, req *builder.Request) builder.Response {
		// req.PathParams contains typed path parameters
		_ = req.PathParams["petId"]
		return builder.JSON(http.StatusOK, Pet{ID: 1, Name: "Fluffy"})
	})

	srv.Handle(http.MethodDelete, "/pets/{petId}", func(_ context.Context, _ *builder.Request) builder.Response {
		return builder.NoContent()
	})

	result := srv.MustBuildServer()

	// Verify the spec was generated correctly
	doc := result.Spec.(*parser.OAS3Document)
	fmt.Printf("Paths: %d\n", len(doc.Paths))
	fmt.Printf("Operations: listPets=%v, createPet=%v, getPet=%v, deletePet=%v\n",
		doc.Paths["/pets"].Get != nil,
		doc.Paths["/pets"].Post != nil,
		doc.Paths["/pets/{petId}"].Get != nil,
		doc.Paths["/pets/{petId}"].Delete != nil,
	)
}
Output:

Paths: 2
Operations: listPets=true, createPet=true, getPet=true, deletePet=true
Example (ServerBuilderFromBuilder)

Example_serverBuilderFromBuilder demonstrates creating a ServerBuilder from an existing Builder. This allows converting a specification into a runnable server.

package main

import (
	"context"
	"fmt"
	"net/http"

	"github.com/erraggy/oastools/builder"
	"github.com/erraggy/oastools/parser"
)

func main() {
	// First create a specification with Builder
	spec := builder.New(parser.OASVersion320).
		SetTitle("Converted API").
		SetVersion("1.0.0")

	spec.AddOperation(http.MethodGet, "/status",
		builder.WithOperationID("getStatus"),
		builder.WithResponse(http.StatusOK, map[string]string{}),
	)

	// Convert to ServerBuilder to add handlers
	srv := builder.FromBuilder(spec, builder.WithoutValidation())

	srv.Handle(http.MethodGet, "/status", func(_ context.Context, _ *builder.Request) builder.Response {
		return builder.JSON(http.StatusOK, map[string]string{"status": "ok"})
	})

	result := srv.MustBuildServer()
	doc := result.Spec.(*parser.OAS3Document)

	fmt.Printf("Title: %s\n", doc.Info.Title)
	fmt.Printf("Has handler: %v\n", result.Handler != nil)
}
Output:

Title: Converted API
Has handler: true
Example (ServerBuilderMiddleware)

Example_serverBuilderMiddleware demonstrates adding middleware to the server. Middleware is applied in order: first added = outermost (executes first).

package main

import (
	"context"
	"fmt"
	"net/http"

	"github.com/erraggy/oastools/builder"
	"github.com/erraggy/oastools/parser"
)

func main() {
	srv := builder.NewServerBuilder(parser.OASVersion320, builder.WithoutValidation()).
		SetTitle("Middleware Demo API").
		SetVersion("1.0.0")

	srv.AddOperation(http.MethodGet, "/hello",
		builder.WithOperationID("hello"),
		builder.WithResponse(http.StatusOK, map[string]string{}),
	)

	srv.Handle(http.MethodGet, "/hello", func(_ context.Context, _ *builder.Request) builder.Response {
		return builder.JSON(http.StatusOK, map[string]string{"message": "hello"})
	})

	// Add logging middleware
	srv.Use(func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			// Log before handling (would use real logger in production)
			_ = fmt.Sprintf("Request: %s %s", r.Method, r.URL.Path)
			next.ServeHTTP(w, r)
		})
	})

	// Add CORS middleware
	srv.Use(func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.Header().Set("Access-Control-Allow-Origin", "*")
			next.ServeHTTP(w, r)
		})
	})

	result := srv.MustBuildServer()
	fmt.Printf("Server with middleware: %v\n", result.Handler != nil)
}
Output:

Server with middleware: true
Example (ServerBuilderResponseBuilder)

Example_serverBuilderResponseBuilder demonstrates the fluent ResponseBuilder for constructing complex responses with headers and custom content types.

package main

import (
	"context"
	"fmt"
	"net/http"

	"github.com/erraggy/oastools/builder"
	"github.com/erraggy/oastools/parser"
)

func main() {
	srv := builder.NewServerBuilder(parser.OASVersion320, builder.WithoutValidation()).
		SetTitle("Custom Response API").
		SetVersion("1.0.0")

	srv.AddOperation(http.MethodGet, "/custom",
		builder.WithOperationID("customResponse"),
		builder.WithResponse(http.StatusOK, nil),
	)

	// Use ResponseBuilder for complex responses
	srv.Handle(http.MethodGet, "/custom", func(_ context.Context, _ *builder.Request) builder.Response {
		return builder.NewResponse(http.StatusOK).
			Header("X-Custom-Header", "custom-value").
			Header("X-Request-ID", "12345").
			JSON(map[string]string{"message": "hello"})
	})

	result := srv.MustBuildServer()
	fmt.Printf("Server built: %v\n", result.Handler != nil)
}
Output:

Server built: true
Example (ServerBuilderResponses)

Example_serverBuilderResponses demonstrates the various response helpers available. ServerBuilder provides convenience functions for common HTTP response patterns.

package main

import (
	"context"
	"fmt"
	"net/http"

	"github.com/erraggy/oastools/builder"
	"github.com/erraggy/oastools/parser"
)

func main() {
	srv := builder.NewServerBuilder(parser.OASVersion320, builder.WithoutValidation()).
		SetTitle("Response Demo API").
		SetVersion("1.0.0")

	srv.AddOperation(http.MethodGet, "/json",
		builder.WithOperationID("jsonResponse"),
		builder.WithResponse(http.StatusOK, map[string]string{}),
	)
	srv.AddOperation(http.MethodGet, "/error",
		builder.WithOperationID("errorResponse"),
		builder.WithResponse(http.StatusBadRequest, map[string]string{}),
	)
	srv.AddOperation(http.MethodGet, "/redirect",
		builder.WithOperationID("redirectResponse"),
		builder.WithResponse(http.StatusFound, nil),
	)
	srv.AddOperation(http.MethodDelete, "/resource",
		builder.WithOperationID("noContent"),
		builder.WithResponse(http.StatusNoContent, nil),
	)

	// JSON response with status code
	srv.Handle(http.MethodGet, "/json", func(_ context.Context, _ *builder.Request) builder.Response {
		return builder.JSON(http.StatusOK, map[string]string{"status": "ok"})
	})

	// Error response with message
	srv.Handle(http.MethodGet, "/error", func(_ context.Context, _ *builder.Request) builder.Response {
		return builder.Error(http.StatusBadRequest, "invalid request")
	})

	// Redirect response
	srv.Handle(http.MethodGet, "/redirect", func(_ context.Context, _ *builder.Request) builder.Response {
		return builder.Redirect(http.StatusFound, "/new-location")
	})

	// No content response (204)
	srv.Handle(http.MethodDelete, "/resource", func(_ context.Context, _ *builder.Request) builder.Response {
		return builder.NoContent()
	})

	result := srv.MustBuildServer()
	doc := result.Spec.(*parser.OAS3Document)
	fmt.Printf("Operations defined: %d\n", len(doc.Paths))
}
Output:

Operations defined: 4
Example (ServerBuilderTesting)

Example_serverBuilderTesting demonstrates the testing utilities for ServerBuilder. These helpers simplify writing tests for API handlers without starting a real server.

package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/erraggy/oastools/builder"
	"github.com/erraggy/oastools/parser"
)

// Pet represents a pet in the store.
type Pet struct {
	ID        int64     `json:"id" oas:"description=Unique pet identifier"`
	Name      string    `json:"name" oas:"minLength=1,description=Pet name"`
	Tag       string    `json:"tag,omitempty" oas:"description=Optional tag"`
	CreatedAt time.Time `json:"created_at" oas:"readOnly=true"`
}

func main() {
	srv := builder.NewServerBuilder(parser.OASVersion320, builder.WithoutValidation()).
		SetTitle("Test API").
		SetVersion("1.0.0")

	srv.AddOperation(http.MethodGet, "/pets",
		builder.WithOperationID("listPets"),
		builder.WithResponse(http.StatusOK, []Pet{}),
	)

	srv.Handle(http.MethodGet, "/pets", func(_ context.Context, _ *builder.Request) builder.Response {
		return builder.JSON(http.StatusOK, []Pet{{ID: 1, Name: "Fluffy"}})
	})

	result := srv.MustBuildServer()

	// Create a test helper
	test := builder.NewServerTest(result)

	// Use GetJSON for simple GET requests with JSON response
	var pets []Pet
	rec, err := test.GetJSON("/pets", &pets)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Status: %d\n", rec.Code)
	fmt.Printf("Pets returned: %d\n", len(pets))
	fmt.Printf("First pet: %s\n", pets[0].Name)
}
Output:

Status: 200
Pets returned: 1
First pet: Fluffy
Example (ServerBuilderWithValidation)

Example_serverBuilderWithValidation demonstrates enabling request validation. When validation is enabled, requests are validated against the OpenAPI spec before reaching the handler.

package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/erraggy/oastools/builder"
	"github.com/erraggy/oastools/parser"
)

// Pet represents a pet in the store.
type Pet struct {
	ID        int64     `json:"id" oas:"description=Unique pet identifier"`
	Name      string    `json:"name" oas:"minLength=1,description=Pet name"`
	Tag       string    `json:"tag,omitempty" oas:"description=Optional tag"`
	CreatedAt time.Time `json:"created_at" oas:"readOnly=true"`
}

func main() {
	srv := builder.NewServerBuilder(parser.OASVersion320,
		builder.WithValidationConfig(builder.ValidationConfig{
			IncludeRequestValidation: true,
			StrictMode:               false,
		}),
	).
		SetTitle("Validated API").
		SetVersion("1.0.0")

	srv.AddOperation(http.MethodPost, "/pets",
		builder.WithOperationID("createPet"),
		builder.WithRequestBody("application/json", Pet{}),
		builder.WithResponse(http.StatusCreated, Pet{}),
	)

	srv.Handle(http.MethodPost, "/pets", func(_ context.Context, req *builder.Request) builder.Response {
		// Request has already been validated at this point
		return builder.JSON(http.StatusCreated, req.Body)
	})

	result, err := srv.BuildServer()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Validation enabled: %v\n", result.Validator != nil)
}
Output:

Validation enabled: true
Example (WithComplexRawSchema)

Example_withComplexRawSchema demonstrates using raw schemas for complex multipart uploads.

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/erraggy/oastools/builder"
	"github.com/erraggy/oastools/parser"
)

func main() {
	spec := builder.New(parser.OASVersion320).
		SetTitle("Complex Upload API").
		SetVersion("1.0.0")

	// Complex multipart schema with file and metadata
	uploadSchema := &parser.Schema{
		Type: "object",
		Properties: map[string]*parser.Schema{
			"file": {
				Type:        "string",
				Format:      "binary",
				Description: "The file data",
			},
			"metadata": {
				Type: "object",
				Properties: map[string]*parser.Schema{
					"filename": {
						Type:        "string",
						Description: "Original filename",
					},
					"tags": {
						Type:        "array",
						Items:       &parser.Schema{Type: "string"},
						Description: "File tags",
					},
				},
			},
		},
		Required: []string{"file"},
	}

	spec.AddOperation(http.MethodPost, "/upload-with-metadata",
		builder.WithOperationID("uploadWithMetadata"),
		builder.WithRequestBodyRawSchema("multipart/form-data", uploadSchema,
			builder.WithRequired(true),
			builder.WithRequestDescription("Upload file with metadata"),
		),
		builder.WithResponse(http.StatusCreated, struct {
			ID string `json:"id"`
		}{}),
	)

	doc, err := spec.BuildOAS3()
	if err != nil {
		log.Fatal(err)
	}

	rb := doc.Paths["/upload-with-metadata"].Post.RequestBody
	schema := rb.Content["multipart/form-data"].Schema
	fmt.Printf("Request body required: %v\n", rb.Required)
	fmt.Printf("Has file property: %v\n", schema.Properties["file"] != nil)
	fmt.Printf("Has metadata property: %v\n", schema.Properties["metadata"] != nil)
	fmt.Printf("File format: %s\n", schema.Properties["file"].Format)
	fmt.Printf("Required fields: %v\n", schema.Required)
}
Output:

Request body required: true
Has file property: true
Has metadata property: true
File format: binary
Required fields: [file]
Example (WithFileUpload)

Example_withFileUpload demonstrates file upload support using WithFileParam.

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/erraggy/oastools/builder"
	"github.com/erraggy/oastools/parser"
)

func main() {
	// OAS 3.x file upload with multipart/form-data
	spec := builder.New(parser.OASVersion320).
		SetTitle("File Upload API").
		SetVersion("1.0.0")

	spec.AddOperation(http.MethodPost, "/upload",
		builder.WithOperationID("uploadFile"),
		builder.WithFileParam("file",
			builder.WithParamRequired(true),
			builder.WithParamDescription("File to upload"),
		),
		builder.WithFormParam("description", "",
			builder.WithParamDescription("File description"),
		),
		builder.WithResponse(http.StatusOK, struct {
			Success bool   `json:"success"`
			FileID  string `json:"file_id"`
		}{}),
	)

	doc, err := spec.BuildOAS3()
	if err != nil {
		log.Fatal(err)
	}

	rb := doc.Paths["/upload"].Post.RequestBody
	schema := rb.Content["multipart/form-data"].Schema
	fmt.Printf("Has file property: %v\n", schema.Properties["file"] != nil)
	fmt.Printf("File type: %s\n", schema.Properties["file"].Type)
	fmt.Printf("File format: %s\n", schema.Properties["file"].Format)
	fmt.Printf("Has description: %v\n", schema.Properties["description"] != nil)
	fmt.Printf("Required: %v\n", schema.Required)
}
Output:

Has file property: true
File type: string
File format: binary
Has description: true
Required: [file]
Example (WithFormParameters)

Example_withFormParameters demonstrates using form parameters. Form parameters work differently in OAS 2.0 vs 3.x:

  • OAS 2.0: parameters with in="formData"
  • OAS 3.x: request body with application/x-www-form-urlencoded
package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/erraggy/oastools/builder"
	"github.com/erraggy/oastools/parser"
)

// Error represents an API error.
type Error struct {
	Code    int32  `json:"code" oas:"description=Error code"`
	Message string `json:"message" oas:"description=Error message"`
}

func main() {
	// OAS 3.x example - form parameters become request body
	spec := builder.New(parser.OASVersion320).
		SetTitle("Login API").
		SetVersion("1.0.0")

	type LoginResponse struct {
		Token     string `json:"token"`
		ExpiresIn int32  `json:"expires_in"`
	}

	spec.AddOperation(http.MethodPost, "/login",
		builder.WithOperationID("login"),
		builder.WithSummary("User login"),
		// Form parameters are automatically converted to request body schema
		builder.WithFormParam("username", "",
			builder.WithParamDescription("User's username"),
			builder.WithParamRequired(true),
			builder.WithParamMinLength(3),
			builder.WithParamMaxLength(20),
		),
		builder.WithFormParam("password", "",
			builder.WithParamDescription("User's password"),
			builder.WithParamRequired(true),
			builder.WithParamMinLength(8),
		),
		builder.WithFormParam("remember_me", false,
			builder.WithParamDescription("Remember login session"),
			builder.WithParamDefault(false),
		),
		builder.WithResponse(http.StatusOK, LoginResponse{},
			builder.WithResponseDescription("Successful login"),
		),
		builder.WithResponse(http.StatusUnauthorized, Error{},
			builder.WithResponseDescription("Invalid credentials"),
		),
	)

	doc, err := spec.BuildOAS3()
	if err != nil {
		log.Fatal(err)
	}

	// Form parameters are in the request body
	rb := doc.Paths["/login"].Post.RequestBody
	mediaType := rb.Content["application/x-www-form-urlencoded"]
	fmt.Printf("Request body content type: application/x-www-form-urlencoded\n")
	fmt.Printf("Form fields: %d\n", len(mediaType.Schema.Properties))
	fmt.Printf("Required fields: %d\n", len(mediaType.Schema.Required))
	fmt.Printf("Has username: %v\n", mediaType.Schema.Properties["username"] != nil)
	fmt.Printf("Has password: %v\n", mediaType.Schema.Properties["password"] != nil)
	fmt.Printf("Has remember_me: %v\n", mediaType.Schema.Properties["remember_me"] != nil)
}
Output:

Request body content type: application/x-www-form-urlencoded
Form fields: 3
Required fields: 2
Has username: true
Has password: true
Has remember_me: true
Example (WithParamTypeFormatOverride)

Example_withParamTypeFormatOverride demonstrates explicit type and format overrides. Use WithParamType and WithParamFormat when the Go type doesn't map directly to the desired OpenAPI type/format, such as using a string for UUID identifiers or representing binary data as base64.

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/erraggy/oastools/builder"
	"github.com/erraggy/oastools/parser"
)

func main() {
	spec := builder.New(parser.OASVersion320).
		SetTitle("ID API").
		SetVersion("1.0.0")

	spec.AddOperation(http.MethodGet, "/users/{user_id}",
		builder.WithOperationID("getUser"),
		// String with UUID format (inferred type is string, explicit format)
		builder.WithPathParam("user_id", "",
			builder.WithParamFormat("uuid"),
			builder.WithParamDescription("User UUID identifier"),
		),
		// Override type to integer with int64 format
		builder.WithQueryParam("version", 0,
			builder.WithParamType("integer"),
			builder.WithParamFormat("int64"),
			builder.WithParamDescription("API version number"),
		),
		builder.WithResponse(http.StatusOK, struct {
			ID string `json:"id"`
		}{}),
	)

	doc, err := spec.BuildOAS3()
	if err != nil {
		log.Fatal(err)
	}

	params := doc.Paths["/users/{user_id}"].Get.Parameters
	fmt.Printf("user_id format: %s\n", params[0].Schema.Format)
	fmt.Printf("version type: %s\n", params[1].Schema.Type)
	fmt.Printf("version format: %s\n", params[1].Schema.Format)
}
Output:

user_id format: uuid
version type: integer
version format: int64
Example (WithParameterConstraints)

Example_withParameterConstraints demonstrates adding parameter constraints.

package main

import (
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/erraggy/oastools/builder"
	"github.com/erraggy/oastools/parser"
)

// Pet represents a pet in the store.
type Pet struct {
	ID        int64     `json:"id" oas:"description=Unique pet identifier"`
	Name      string    `json:"name" oas:"minLength=1,description=Pet name"`
	Tag       string    `json:"tag,omitempty" oas:"description=Optional tag"`
	CreatedAt time.Time `json:"created_at" oas:"readOnly=true"`
}

func main() {
	spec := builder.New(parser.OASVersion320).
		SetTitle("Pet Store API").
		SetVersion("1.0.0")

	// Add operation with constrained parameters
	spec.AddOperation(http.MethodGet, "/pets",
		builder.WithOperationID("listPets"),
		// Numeric constraint: limit must be between 1 and 100, default 20
		builder.WithQueryParam("limit", int32(0),
			builder.WithParamDescription("Maximum number of pets to return"),
			builder.WithParamMinimum(1),
			builder.WithParamMaximum(100),
			builder.WithParamDefault(20),
		),
		// Enum constraint: status must be one of the allowed values
		builder.WithQueryParam("status", "",
			builder.WithParamDescription("Filter by status"),
			builder.WithParamEnum("available", "pending", "sold"),
			builder.WithParamDefault("available"),
		),
		// String constraint: name must match pattern and length
		builder.WithQueryParam("name", "",
			builder.WithParamDescription("Filter by name"),
			builder.WithParamMinLength(1),
			builder.WithParamMaxLength(50),
			builder.WithParamPattern("^[a-zA-Z]+$"),
		),
		builder.WithResponse(http.StatusOK, []Pet{}),
	)

	doc, err := spec.BuildOAS3()
	if err != nil {
		log.Fatal(err)
	}

	params := doc.Paths["/pets"].Get.Parameters
	fmt.Printf("Parameters: %d\n", len(params))
	fmt.Printf("limit min: %.0f\n", *params[0].Schema.Minimum)
	fmt.Printf("limit max: %.0f\n", *params[0].Schema.Maximum)
	fmt.Printf("status enum count: %d\n", len(params[1].Schema.Enum))
	fmt.Printf("name pattern: %s\n", params[2].Schema.Pattern)
}
Output:

Parameters: 3
limit min: 1
limit max: 100
status enum count: 3
name pattern: ^[a-zA-Z]+$
Example (WithParameters)

Example_withParameters demonstrates adding parameters.

package main

import (
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/erraggy/oastools/builder"
	"github.com/erraggy/oastools/parser"
)

// Pet represents a pet in the store.
type Pet struct {
	ID        int64     `json:"id" oas:"description=Unique pet identifier"`
	Name      string    `json:"name" oas:"minLength=1,description=Pet name"`
	Tag       string    `json:"tag,omitempty" oas:"description=Optional tag"`
	CreatedAt time.Time `json:"created_at" oas:"readOnly=true"`
}

func main() {
	spec := builder.New(parser.OASVersion320).
		SetTitle("Pet Store API").
		SetVersion("1.0.0").
		AddOperation(http.MethodGet, "/pets/{petId}",
			builder.WithOperationID("getPet"),
			builder.WithPathParam("petId", int64(0),
				builder.WithParamDescription("The ID of the pet"),
			),
			builder.WithQueryParam("include", "",
				builder.WithParamDescription("Include related resources"),
			),
			builder.WithResponse(http.StatusOK, Pet{}),
		)

	doc, err := spec.BuildOAS3()
	if err != nil {
		log.Fatal(err)
	}

	paramCount := len(doc.Paths["/pets/{petId}"].Get.Parameters)
	fmt.Printf("Parameters: %d\n", paramCount)
}
Output:

Parameters: 2
Example (WithRawSchema)

Example_withRawSchema demonstrates using raw schemas for binary data.

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/erraggy/oastools/builder"
	"github.com/erraggy/oastools/parser"
)

func main() {
	spec := builder.New(parser.OASVersion320).
		SetTitle("File Download API").
		SetVersion("1.0.0")

	// Binary file download response
	binarySchema := &parser.Schema{
		Type:   "string",
		Format: "binary",
	}

	spec.AddOperation(http.MethodGet, "/download/{id}",
		builder.WithOperationID("downloadFile"),
		builder.WithPathParam("id", int64(0),
			builder.WithParamDescription("File ID"),
		),
		builder.WithResponseRawSchema(http.StatusOK, "application/octet-stream", binarySchema,
			builder.WithResponseDescription("Binary file content"),
			builder.WithResponseHeader("Content-Disposition", &parser.Header{
				Description: "Suggested filename",
				Schema:      &parser.Schema{Type: "string"},
			}),
		),
	)

	doc, err := spec.BuildOAS3()
	if err != nil {
		log.Fatal(err)
	}

	resp := doc.Paths["/download/{id}"].Get.Responses.Codes["200"]
	mediaType := resp.Content["application/octet-stream"]
	fmt.Printf("Response content type: application/octet-stream\n")
	fmt.Printf("Schema type: %s\n", mediaType.Schema.Type)
	fmt.Printf("Schema format: %s\n", mediaType.Schema.Format)
	fmt.Printf("Has Content-Disposition header: %v\n", resp.Headers["Content-Disposition"] != nil)
}
Output:

Response content type: application/octet-stream
Schema type: string
Schema format: binary
Has Content-Disposition header: true
Example (WithRequestBody)

Example_withRequestBody demonstrates adding request bodies.

package main

import (
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/erraggy/oastools/builder"
	"github.com/erraggy/oastools/parser"
)

// Pet represents a pet in the store.
type Pet struct {
	ID        int64     `json:"id" oas:"description=Unique pet identifier"`
	Name      string    `json:"name" oas:"minLength=1,description=Pet name"`
	Tag       string    `json:"tag,omitempty" oas:"description=Optional tag"`
	CreatedAt time.Time `json:"created_at" oas:"readOnly=true"`
}

func main() {
	type CreatePetRequest struct {
		Name string `json:"name" oas:"minLength=1"`
		Tag  string `json:"tag,omitempty"`
	}

	spec := builder.New(parser.OASVersion320).
		SetTitle("Pet Store API").
		SetVersion("1.0.0").
		AddOperation(http.MethodPost, "/pets",
			builder.WithOperationID("createPet"),
			builder.WithRequestBody("application/json", CreatePetRequest{},
				builder.WithRequired(true),
				builder.WithRequestDescription("Pet to create"),
			),
			builder.WithResponse(http.StatusCreated, Pet{}),
		)

	doc, err := spec.BuildOAS3()
	if err != nil {
		log.Fatal(err)
	}

	hasRequestBody := doc.Paths["/pets"].Post.RequestBody != nil
	fmt.Printf("Has request body: %v\n", hasRequestBody)
}
Output:

Has request body: true
Example (WithSchemaFieldProcessor)

Example_withSchemaFieldProcessor demonstrates custom struct tag processing. This allows libraries to support their own tag formats alongside the standard oas:"..." tags. Common use cases include:

  • Migration support from other OpenAPI libraries with different tag formats
  • Custom validation tag integration
  • Framework integration with existing struct tag conventions
package main

import (
	"fmt"
	"log"
	"net/http"
	"reflect"
	"strconv"
	"strings"

	"github.com/erraggy/oastools/builder"
	"github.com/erraggy/oastools/parser"
)

func main() {
	// Define a struct using standalone tags (legacy format from other libraries)
	type LegacyUser struct {
		Name   string `json:"name" description:"User's full name"`
		Status string `json:"status" enum:"active|inactive|pending"`
		Age    int    `json:"age" minimum:"0" maximum:"150"`
	}

	// Create a processor that handles legacy standalone tags
	legacyTagProcessor := func(schema *parser.Schema, field reflect.StructField) *parser.Schema {
		// Skip if oas tag is present (already processed by oastools)
		if field.Tag.Get("oas") != "" {
			return schema
		}

		// Apply description tag
		if desc := field.Tag.Get("description"); desc != "" {
			schema.Description = desc
		}

		// Apply enum tag (pipe-separated values)
		if enumStr := field.Tag.Get("enum"); enumStr != "" {
			values := strings.Split(enumStr, "|")
			schema.Enum = make([]any, len(values))
			for i, v := range values {
				schema.Enum[i] = strings.TrimSpace(v)
			}
		}

		// Apply numeric constraints
		if minStr := field.Tag.Get("minimum"); minStr != "" {
			if min, err := strconv.ParseFloat(minStr, 64); err == nil {
				schema.Minimum = &min
			}
		}
		if maxStr := field.Tag.Get("maximum"); maxStr != "" {
			if max, err := strconv.ParseFloat(maxStr, 64); err == nil {
				schema.Maximum = &max
			}
		}

		return schema
	}

	// Build specification with the custom processor
	spec := builder.New(parser.OASVersion320,
		builder.WithSchemaFieldProcessor(legacyTagProcessor),
	).
		SetTitle("Legacy API").
		SetVersion("1.0.0").
		AddOperation(http.MethodGet, "/users",
			builder.WithResponse(http.StatusOK, LegacyUser{}),
		)

	doc, err := spec.BuildOAS3()
	if err != nil {
		log.Fatal(err)
	}

	// Access the generated schema
	userSchema := doc.Components.Schemas["builder_test.LegacyUser"]
	nameSchema := userSchema.Properties["name"]
	statusSchema := userSchema.Properties["status"]
	ageSchema := userSchema.Properties["age"]

	fmt.Printf("Name description: %s\n", nameSchema.Description)
	fmt.Printf("Status enum: %v\n", statusSchema.Enum)
	fmt.Printf("Age minimum: %.0f\n", *ageSchema.Minimum)
	fmt.Printf("Age maximum: %.0f\n", *ageSchema.Maximum)
}
Output:

Name description: User's full name
Status enum: [active inactive pending]
Age minimum: 0
Age maximum: 150
Example (WithSecurity)

Example_withSecurity demonstrates security configuration.

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/erraggy/oastools/builder"
	"github.com/erraggy/oastools/parser"
)

func main() {
	spec := builder.New(parser.OASVersion320).
		SetTitle("Secure API").
		SetVersion("1.0.0").
		AddAPIKeySecurityScheme("api_key", "header", "X-API-Key", "API key authentication").
		AddHTTPSecurityScheme("bearer_auth", "bearer", "JWT", "Bearer token authentication").
		SetSecurity(
			builder.SecurityRequirement("api_key"),
			builder.SecurityRequirement("bearer_auth"),
		).
		AddOperation(http.MethodGet, "/secure",
			builder.WithOperationID("secureEndpoint"),
			builder.WithResponse(http.StatusOK, struct{}{}),
		)

	doc, err := spec.BuildOAS3()
	if err != nil {
		log.Fatal(err)
	}

	schemeCount := len(doc.Components.SecuritySchemes)
	securityCount := len(doc.Security)
	fmt.Printf("Security schemes: %d\n", schemeCount)
	fmt.Printf("Global security requirements: %d\n", securityCount)
}
Output:

Security schemes: 2
Global security requirements: 2
Example (WithServer)

Example_withServer demonstrates adding servers.

package main

import (
	"fmt"
	"log"

	"github.com/erraggy/oastools/builder"
	"github.com/erraggy/oastools/parser"
)

func main() {
	spec := builder.New(parser.OASVersion320).
		SetTitle("My API").
		SetVersion("1.0.0").
		AddServer("https://api.example.com/v1",
			builder.WithServerDescription("Production server"),
		).
		AddServer("https://staging.example.com/v1",
			builder.WithServerDescription("Staging server"),
		)

	doc, err := spec.BuildOAS3()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Servers: %d\n", len(doc.Servers))
	fmt.Printf("First server: %s\n", doc.Servers[0].URL)
}
Output:

Servers: 2
First server: https://api.example.com/v1

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func MatchedPath added in v1.34.0

func MatchedPath(r *http.Request) string

MatchedPath returns the matched path template from the request context.

Example:

template := builder.MatchedPath(r) // e.g., "/pets/{petId}"

func PathParam added in v1.34.0

func PathParam(r *http.Request, name string) string

PathParam extracts a path parameter from the request context. This is a package-level function for convenience.

Example:

petID := builder.PathParam(r, "petId")

func SecurityRequirement

func SecurityRequirement(schemeName string, scopes ...string) parser.SecurityRequirement

SecurityRequirement creates a security requirement for use with SetSecurity or WithSecurity.

Types

type Builder

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

Builder is the main entry point for constructing OAS documents. It maintains internal state for accumulated components and reflection cache.

Concurrency: Builder instances are not safe for concurrent use. Create separate Builder instances for concurrent operations.

func FromDocument

func FromDocument(doc *parser.OAS3Document) *Builder

FromDocument creates a builder from an existing OAS3Document. This allows modifying an existing document by adding operations.

For OAS 2.0 documents, use FromOAS2Document instead.

func FromOAS2Document

func FromOAS2Document(doc *parser.OAS2Document) *Builder

FromOAS2Document creates a builder from an existing OAS2Document (Swagger 2.0). This allows modifying an existing document by adding operations.

For OAS 3.x documents, use FromDocument instead.

func New

func New(version parser.OASVersion, opts ...BuilderOption) *Builder

New creates a new Builder instance for the specified OAS version. Use BuildOAS2() for OAS 2.0 (Swagger) or BuildOAS3() for OAS 3.x documents.

Options can be provided to customize schema naming:

// Use PascalCase naming (e.g., "ModelsUser" instead of "models.User")
spec := builder.New(parser.OASVersion320,
    builder.WithSchemaNaming(builder.SchemaNamingPascalCase),
)

The builder does not perform OAS specification validation. Use the validator package to validate built documents.

Example:

spec := builder.New(parser.OASVersion320).
	SetTitle("My API").
	SetVersion("1.0.0")
doc, err := spec.BuildOAS3()

func NewWithInfo

func NewWithInfo(version parser.OASVersion, info *parser.Info) *Builder

NewWithInfo creates a Builder with pre-configured Info.

Example:

info := &parser.Info{Title: "My API", Version: "1.0.0"}
spec := builder.NewWithInfo(parser.OASVersion320, info)

func (*Builder) AddAPIKeySecurityScheme

func (b *Builder) AddAPIKeySecurityScheme(name string, in string, keyName string, description string) *Builder

AddAPIKeySecurityScheme adds an API key security scheme.

func (*Builder) AddHTTPSecurityScheme

func (b *Builder) AddHTTPSecurityScheme(name string, scheme string, bearerFormat string, description string) *Builder

AddHTTPSecurityScheme adds an HTTP security scheme (Basic, Bearer, etc.).

func (*Builder) AddOAuth2SecurityScheme

func (b *Builder) AddOAuth2SecurityScheme(name string, flows *parser.OAuthFlows, description string) *Builder

AddOAuth2SecurityScheme adds an OAuth2 security scheme.

func (*Builder) AddOpenIDConnectSecurityScheme

func (b *Builder) AddOpenIDConnectSecurityScheme(name string, openIDConnectURL string, description string) *Builder

AddOpenIDConnectSecurityScheme adds an OpenID Connect security scheme.

func (*Builder) AddOperation

func (b *Builder) AddOperation(method, path string, opts ...OperationOption) *Builder

AddOperation adds an API operation to the specification. Go types passed to options are automatically converted to schemas via reflection.

Note: OpenAPI requires at least one response per operation. If no responses are defined, the resulting spec will fail OAS validation. Use WithResponse() or WithDefaultResponse() to add responses. The builder package does not perform OAS specification validation; use the validator package to validate built documents.

func (*Builder) AddParameter

func (b *Builder) AddParameter(name string, in string, paramName string, paramType any, opts ...ParamOption) *Builder

AddParameter adds a reusable parameter to components.parameters (OAS 3.x) or parameters (OAS 2.0).

Constraint validation is performed and any errors are accumulated in the builder. Use BuildOAS2() or BuildOAS3() to check for accumulated errors.

func (*Builder) AddResponse

func (b *Builder) AddResponse(name string, description string, responseType any, opts ...ResponseOption) *Builder

AddResponse adds a reusable response to components.responses (OAS 3.x) or responses (OAS 2.0). Use WithResponseContentType to specify a content type other than "application/json".

func (*Builder) AddSecurityScheme

func (b *Builder) AddSecurityScheme(name string, scheme *parser.SecurityScheme) *Builder

AddSecurityScheme adds a security scheme to components.securitySchemes.

func (*Builder) AddServer

func (b *Builder) AddServer(url string, opts ...ServerOption) *Builder

AddServer adds a server to the specification.

func (*Builder) AddTag

func (b *Builder) AddTag(name string, opts ...TagOption) *Builder

AddTag adds a tag to the specification.

func (*Builder) AddWebhook

func (b *Builder) AddWebhook(name, method string, opts ...OperationOption) *Builder

AddWebhook adds a webhook to the specification (OAS 3.1+ only). Webhooks are callbacks that are triggered by the API provider. Returns an error during Build if used with OAS versions earlier than 3.1.

Example:

spec.AddWebhook("newUser", http.MethodPost,
    builder.WithResponse(http.StatusOK, UserCreatedEvent{}),
)

func (*Builder) BuildOAS2

func (b *Builder) BuildOAS2() (*parser.OAS2Document, error)

BuildOAS2 creates an OAS 2.0 (Swagger) document. Returns an error if the builder was created with an OAS 3.x version, or if required fields are missing.

The builder does not perform OAS specification validation. Use the validator package to validate built documents.

Example:

spec := builder.New(parser.OASVersion20).
	SetTitle("My API").
	SetVersion("1.0.0")
doc, err := spec.BuildOAS2()
// doc is *parser.OAS2Document - no type assertion needed

func (*Builder) BuildOAS3

func (b *Builder) BuildOAS3() (*parser.OAS3Document, error)

BuildOAS3 creates an OAS 3.x document. Returns an error if the builder was created with OAS 2.0 version, or if required fields are missing.

The builder does not perform OAS specification validation. Use the validator package to validate built documents.

Example:

spec := builder.New(parser.OASVersion320).
	SetTitle("My API").
	SetVersion("1.0.0")
doc, err := spec.BuildOAS3()
// doc is *parser.OAS3Document - no type assertion needed

func (*Builder) BuildResult

func (b *Builder) BuildResult() (*parser.ParseResult, error)

BuildResult creates a ParseResult for compatibility with other packages. This is useful for validating the built document with the validator package.

The builder does not perform OAS specification validation. Use the validator package to validate built documents:

result, err := spec.BuildResult()
if err != nil { return err }
valResult, err := validator.ValidateParsed(result)

func (*Builder) DeduplicateSchemas added in v1.26.0

func (b *Builder) DeduplicateSchemas() *Builder

DeduplicateSchemas identifies semantically identical schemas and consolidates them to a single canonical schema. This method should be called after all schemas have been added but before Build*() methods.

Schemas are considered semantically identical if they have the same structural properties (type, format, properties, constraints, etc.), ignoring metadata fields like title, description, and examples.

The canonical schema name is selected alphabetically. For example, if "Address" and "Location" schemas are identical, "Address" becomes canonical and all references to "Location" are rewritten to point to "Address".

Returns the Builder for method chaining.

Note: This method is automatically called by Build*() methods when WithSemanticDeduplication(true) is set. Call it manually only if you need to inspect schemaAliases before building.

func (*Builder) MarshalJSON

func (b *Builder) MarshalJSON() ([]byte, error)

MarshalJSON returns the document as JSON bytes.

func (*Builder) MarshalYAML

func (b *Builder) MarshalYAML() ([]byte, error)

MarshalYAML returns the document as YAML bytes.

func (*Builder) ParameterRef

func (b *Builder) ParameterRef(name string) string

ParameterRef returns a reference to a named parameter. This method returns the version-appropriate ref path.

func (*Builder) RegisterType

func (b *Builder) RegisterType(v any) *parser.Schema

RegisterType registers a Go type and returns a $ref to it. The schema is automatically generated via reflection and added to components.schemas.

func (*Builder) RegisterTypeAs

func (b *Builder) RegisterTypeAs(name string, v any) *parser.Schema

RegisterTypeAs registers a Go type with a custom schema name.

func (*Builder) ResponseRef

func (b *Builder) ResponseRef(name string) string

ResponseRef returns a reference to a named response. This method returns the version-appropriate ref path.

func (*Builder) SchemaRef

func (b *Builder) SchemaRef(name string) string

SchemaRef returns a reference string to a named schema. This method returns the version-appropriate ref path:

  • OAS 2.0: "#/definitions/{name}"
  • OAS 3.x: "#/components/schemas/{name}"

func (*Builder) SetContact

func (b *Builder) SetContact(contact *parser.Contact) *Builder

SetContact sets the contact information in the Info object.

func (*Builder) SetDescription

func (b *Builder) SetDescription(desc string) *Builder

SetDescription sets the description in the Info object.

func (*Builder) SetExternalDocs

func (b *Builder) SetExternalDocs(externalDocs *parser.ExternalDocs) *Builder

SetExternalDocs sets the external documentation for the document. This is used for providing additional documentation at the document level.

func (*Builder) SetInfo

func (b *Builder) SetInfo(info *parser.Info) *Builder

SetInfo sets the Info object for the document.

func (*Builder) SetLicense

func (b *Builder) SetLicense(license *parser.License) *Builder

SetLicense sets the license information in the Info object.

func (*Builder) SetSecurity

func (b *Builder) SetSecurity(requirements ...parser.SecurityRequirement) *Builder

SetSecurity sets the global security requirements.

func (*Builder) SetTermsOfService

func (b *Builder) SetTermsOfService(url string) *Builder

SetTermsOfService sets the terms of service URL in the Info object.

func (*Builder) SetTitle

func (b *Builder) SetTitle(title string) *Builder

SetTitle sets the title in the Info object.

func (*Builder) SetVersion

func (b *Builder) SetVersion(version string) *Builder

SetVersion sets the version in the Info object. Note: This is the API version, not the OpenAPI specification version.

func (*Builder) WriteFile

func (b *Builder) WriteFile(path string) error

WriteFile writes the document to a file. The format is inferred from the file extension (.json for JSON, .yaml/.yml for YAML).

type BuilderError added in v1.35.0

type BuilderError struct {
	// Component is the type of component where the error occurred.
	Component ComponentType
	// Method is the HTTP method (for operation/webhook errors).
	Method string
	// Path is the API path (for operation errors) or webhook name.
	Path string
	// OperationID is the operation identifier (if applicable).
	OperationID string
	// Field is the specific field with the error (e.g., "minimum").
	Field string
	// Message describes the error.
	Message string
	// Context provides additional details (e.g., conflicting values).
	Context map[string]any
	// FirstOccurrence tracks where a duplicate was first defined.
	FirstOccurrence *operationLocation
	// Cause is the underlying error, if any.
	Cause error
}

BuilderError represents a structured error from the builder package. It provides detailed context about where and why an error occurred during the fluent API building process.

func NewDuplicateOperationIDError added in v1.35.0

func NewDuplicateOperationIDError(operationID, method, path string, first *operationLocation) *BuilderError

NewDuplicateOperationIDError creates an error for duplicate operation IDs.

func NewDuplicateWebhookOperationIDError added in v1.35.0

func NewDuplicateWebhookOperationIDError(operationID, webhookName, method string, first *operationLocation) *BuilderError

NewDuplicateWebhookOperationIDError creates an error for duplicate operation IDs in webhooks.

func NewInvalidMethodError added in v1.35.0

func NewInvalidMethodError(method, path string) *BuilderError

NewInvalidMethodError creates an error for invalid/unknown HTTP methods.

func NewParameterConstraintError added in v1.35.0

func NewParameterConstraintError(paramName, operationContext, field, message string) *BuilderError

NewParameterConstraintError creates an error for parameter constraint violations.

func NewSchemaError added in v1.35.0

func NewSchemaError(schemaName, message string, cause error) *BuilderError

NewSchemaError creates an error for schema-related issues.

func NewUnsupportedMethodError added in v1.35.0

func NewUnsupportedMethodError(method, path, minVersion string) *BuilderError

NewUnsupportedMethodError creates an error for unsupported HTTP methods.

func (*BuilderError) Error added in v1.35.0

func (e *BuilderError) Error() string

Error implements the error interface with a detailed, formatted message.

func (*BuilderError) HasLocation added in v1.35.0

func (e *BuilderError) HasLocation() bool

HasLocation returns true if this error has location context. BuilderError uses component/method/path instead of line/column.

func (*BuilderError) Is added in v1.35.0

func (e *BuilderError) Is(target error) bool

Is reports whether target matches this error type. All BuilderErrors are classified as ErrConfig errors, enabling callers to use errors.Is(err, oaserrors.ErrConfig) to detect builder configuration issues. This includes validation errors, duplicate detection, and unsupported features.

func (*BuilderError) Location added in v1.35.0

func (e *BuilderError) Location() string

Location returns a descriptive location string.

func (*BuilderError) Unwrap added in v1.35.0

func (e *BuilderError) Unwrap() error

Unwrap returns the underlying error for errors.Is/As support.

type BuilderErrors added in v1.35.0

type BuilderErrors []*BuilderError

BuilderErrors is a collection of BuilderError with formatting support.

func (BuilderErrors) Error added in v1.35.0

func (errs BuilderErrors) Error() string

Error implements the error interface with a formatted multi-error message.

func (BuilderErrors) Unwrap added in v1.35.0

func (errs BuilderErrors) Unwrap() []error

Unwrap returns the errors for Go 1.20+ error wrapping semantics, enabling errors.Is and errors.As to work with multiple wrapped errors.

type BuilderOption added in v1.25.0

type BuilderOption func(*builderConfig)

BuilderOption configures a Builder instance. Options are applied when creating a new Builder with New().

func WithGenericApplyBaseCasing added in v1.25.0

func WithGenericApplyBaseCasing(apply bool) BuilderOption

WithGenericApplyBaseCasing applies the base naming strategy to type parameters. When true with SchemaNamingPascalCase, Response[user_profile] becomes ResponseOfUserProfile. Default is false.

func WithGenericIncludePackage added in v1.25.0

func WithGenericIncludePackage(include bool) BuilderOption

WithGenericIncludePackage includes package names in generic type parameters. When true, Response[models.User] becomes Response_models_User_. Default is false.

func WithGenericNaming added in v1.25.0

func WithGenericNaming(strategy GenericNamingStrategy) BuilderOption

WithGenericNaming sets the strategy for handling generic type names. The default is GenericNamingUnderscore which produces "Response_User_" format.

Available strategies:

  • GenericNamingUnderscore: "Response_User_" (default)
  • GenericNamingOf: "ResponseOfUser"
  • GenericNamingFor: "ResponseForUser"
  • GenericNamingAngleBrackets: "Response<User>" (URI-encoded in $ref)
  • GenericNamingFlattened: "ResponseUser"

func WithGenericNamingConfig added in v1.25.0

func WithGenericNamingConfig(config GenericNamingConfig) BuilderOption

WithGenericNamingConfig provides fine-grained control over generic type naming. This replaces any previous generic naming settings.

Example:

WithGenericNamingConfig(builder.GenericNamingConfig{
    Strategy:        builder.GenericNamingOf,
    ParamSeparator:  "And",
    ApplyBaseCasing: true,
})

func WithGenericParamSeparator added in v1.25.0

func WithGenericParamSeparator(sep string) BuilderOption

WithGenericParamSeparator sets the separator between multiple type parameters. Default is "_".

Example:

WithGenericParamSeparator("And")
// Map[string,int] with GenericNamingOf becomes MapOfStringAndOfInt

func WithGenericSeparator added in v1.25.0

func WithGenericSeparator(sep string) BuilderOption

WithGenericSeparator sets the separator used for generic type parameters. Only applies to GenericNamingUnderscore strategy. Default is "_".

Example:

WithGenericSeparator("__")
// Response[User] becomes Response__User__

func WithSchemaFieldProcessor added in v1.36.0

func WithSchemaFieldProcessor(fn SchemaFieldProcessor) BuilderOption

WithSchemaFieldProcessor sets a custom function for processing struct field tags. This enables libraries to support custom tag formats alongside the standard oas:"..." tags.

The processor is called for each struct field after the base schema is generated and after any oas:"..." tags are applied. It receives the generated schema and the reflect.StructField, allowing access to all struct tags.

Example - supporting legacy standalone tags:

spec := builder.New(parser.OASVersion320,
    builder.WithSchemaFieldProcessor(func(schema *parser.Schema, field reflect.StructField) *parser.Schema {
        // Skip if oas tag is present (already processed)
        if field.Tag.Get("oas") != "" {
            return schema
        }
        // Apply legacy description tag
        if desc := field.Tag.Get("description"); desc != "" {
            schema.Description = desc
        }
        // Apply legacy enum tag
        if enumStr := field.Tag.Get("enum"); enumStr != "" {
            values := strings.Split(enumStr, "|")
            schema.Enum = make([]any, len(values))
            for i, v := range values {
                schema.Enum[i] = strings.TrimSpace(v)
            }
        }
        return schema
    }),
)

Use cases:

  • Migration support from other OpenAPI libraries with different tag formats
  • Custom validation tag integration (e.g., reading from validator tags)
  • Documentation generators pulling from godoc-style comments or other sources
  • Framework integration with existing struct tag conventions

func WithSchemaNameFunc added in v1.25.0

func WithSchemaNameFunc(fn SchemaNameFunc) BuilderOption

WithSchemaNameFunc sets a custom function for schema naming. Provides maximum flexibility for programmatic naming logic. The function receives SchemaNameContext and returns the schema name.

Example:

WithSchemaNameFunc(func(ctx builder.SchemaNameContext) string {
    if ctx.IsAnonymous {
        return "AnonymousType"
    }
    return strings.ToUpper(ctx.Package) + "_" + ctx.Type
})

Setting a custom function clears any previously set template.

func WithSchemaNameTemplate added in v1.25.0

func WithSchemaNameTemplate(tmpl string) BuilderOption

WithSchemaNameTemplate sets a custom Go text/template for schema naming. Template receives SchemaNameContext with type metadata. Template parse errors are returned by Build*() methods.

Available template functions: pascal, camel, snake, kebab, upper, lower, title, sanitize, trimPrefix, trimSuffix, replace, join.

Available context fields:

  • .Type: Go type name without package (e.g., "User", "Response[T]")
  • .TypeSanitized: Type with generic brackets replaced per GenericNamingStrategy
  • .TypeBase: Base type name without generic parameters (e.g., "Response")
  • .Package: Package base name (e.g., "models")
  • .PackagePath: Full import path (e.g., "github.com/org/models")
  • .PackagePathSanitized: PackagePath with slashes replaced
  • .IsGeneric: Whether the type has type parameters
  • .GenericParams: Type parameter names if IsGeneric is true
  • .GenericParamsSanitized: Sanitized type parameter names
  • .GenericSuffix: Formatted generic parameters portion
  • .IsAnonymous: Whether this is an anonymous struct type
  • .IsPointer: Whether the original type was a pointer
  • .Kind: The reflect.Kind as a string

Example:

WithSchemaNameTemplate(`{{pascal .Package}}{{pascal .Type}}`)

Template parse errors are returned by BuildOAS3() or BuildOAS2(). If template execution fails at runtime for a specific type (e.g., due to accessing an invalid field), the naming falls back to the default "package.TypeName" format silently. Ensure templates are tested with representative types to avoid unexpected fallback behavior.

Setting a template clears any previously set custom function.

func WithSchemaNaming added in v1.25.0

func WithSchemaNaming(strategy SchemaNamingStrategy) BuilderOption

WithSchemaNaming sets a built-in schema naming strategy. The default is SchemaNamingDefault which produces "package.TypeName" format.

Available strategies:

  • SchemaNamingDefault: "package.TypeName" (e.g., models.User)
  • SchemaNamingPascalCase: "PackageTypeName" (e.g., ModelsUser)
  • SchemaNamingCamelCase: "packageTypeName" (e.g., modelsUser)
  • SchemaNamingSnakeCase: "package_type_name" (e.g., models_user)
  • SchemaNamingKebabCase: "package-type-name" (e.g., models-user)
  • SchemaNamingTypeOnly: "TypeName" (e.g., User) - may cause conflicts
  • SchemaNamingFullPath: "full_path_TypeName" (e.g., github.com_org_models_User)

Setting a naming strategy clears any previously set template or custom function.

func WithSemanticDeduplication added in v1.26.0

func WithSemanticDeduplication(enabled bool) BuilderOption

WithSemanticDeduplication enables semantic schema deduplication. When enabled, the builder identifies schemas that are structurally identical and consolidates them to a single canonical schema. All references to duplicate schemas are rewritten to point to the canonical schema.

The canonical schema name is selected alphabetically (e.g., if "Address" and "Location" are identical, "Address" becomes canonical).

This option reduces document size when multiple types converge to the same structure. It is disabled by default.

Example:

spec := builder.New(parser.OASVersion320,
    builder.WithSemanticDeduplication(true),
)

type ComponentType added in v1.35.0

type ComponentType string

ComponentType identifies the type of component where an error occurred.

const (
	// ComponentOperation indicates an error in an operation definition.
	ComponentOperation ComponentType = "operation"
	// ComponentWebhook indicates an error in a webhook definition.
	ComponentWebhook ComponentType = "webhook"
	// ComponentParameter indicates an error in a parameter definition.
	ComponentParameter ComponentType = "parameter"
	// ComponentSchema indicates an error in a schema definition.
	ComponentSchema ComponentType = "schema"
	// ComponentRequestBody indicates an error in a request body definition.
	ComponentRequestBody ComponentType = "request_body"
	// ComponentResponse indicates an error in a response definition.
	ComponentResponse ComponentType = "response"
	// ComponentSecurityScheme indicates an error in a security scheme.
	ComponentSecurityScheme ComponentType = "security_scheme"
	// ComponentServer indicates an error in a server definition.
	ComponentServer ComponentType = "server"
)

type ConstraintError added in v1.13.1

type ConstraintError struct {
	// Field is the constraint field that failed (e.g., "minimum", "pattern").
	Field string
	// Message describes the constraint violation.
	Message string
	// ParamName is the parameter name where this constraint was applied (optional).
	ParamName string
	// OperationContext describes the operation context (e.g., "POST /users").
	OperationContext string
}

ConstraintError represents an invalid constraint configuration. It provides context about which field failed validation and why.

func (*ConstraintError) Error added in v1.13.1

func (e *ConstraintError) Error() string

Error implements the error interface.

func (*ConstraintError) HasLocation added in v1.35.0

func (e *ConstraintError) HasLocation() bool

HasLocation returns true if this error has location context.

func (*ConstraintError) Is added in v1.35.0

func (e *ConstraintError) Is(target error) bool

Is reports whether target matches this error type. ConstraintError matches oaserrors.ErrConfig for programmatic error handling.

func (*ConstraintError) Location added in v1.35.0

func (e *ConstraintError) Location() string

Location returns a descriptive location string.

func (*ConstraintError) Unwrap added in v1.35.0

func (e *ConstraintError) Unwrap() error

Unwrap returns nil as ConstraintError has no underlying cause. This method exists for interface consistency with BuilderError.

type ErrorHandler added in v1.34.0

type ErrorHandler func(w http.ResponseWriter, r *http.Request, err error)

ErrorHandler handles errors during request processing.

type GenericNamingConfig added in v1.25.0

type GenericNamingConfig struct {
	// Strategy is the primary generic naming approach.
	Strategy GenericNamingStrategy

	// Separator is used between base type and parameters.
	// Only applies to GenericNamingUnderscore strategy.
	// Default: "_"
	Separator string

	// ParamSeparator is used between multiple type parameters.
	// Example with ParamSeparator="_": Map[string,int] -> Map_string_int
	// Example with ParamSeparator="And": Map[string,int] -> MapOfStringAndOfInt
	// Default: "_"
	ParamSeparator string

	// IncludePackage includes the type parameter's package in the name.
	// Example: Response[models.User] -> Response_models_User (true)
	// Example: Response[models.User] -> Response_User (false, default)
	IncludePackage bool

	// ApplyBaseCasing applies the base naming strategy to type parameters.
	// Example with SchemaNamingPascalCase: Response[user_profile] -> ResponseOfUserProfile
	ApplyBaseCasing bool
}

GenericNamingConfig provides fine-grained control over generic type naming.

func DefaultGenericNamingConfig added in v1.25.0

func DefaultGenericNamingConfig() GenericNamingConfig

DefaultGenericNamingConfig returns the default generic naming configuration. This matches the current behavior where brackets are replaced with underscores.

type GenericNamingStrategy added in v1.25.0

type GenericNamingStrategy int

GenericNamingStrategy defines how generic type parameters are formatted in schema names.

const (
	// GenericNamingUnderscore replaces brackets with underscores (default behavior).
	// Example: Response[User] -> Response_User_
	GenericNamingUnderscore GenericNamingStrategy = iota

	// GenericNamingOf uses "Of" separator between base type and parameters.
	// Example: Response[User] -> ResponseOfUser
	GenericNamingOf

	// GenericNamingFor uses "For" separator.
	// Example: Response[User] -> ResponseForUser
	GenericNamingFor

	// GenericNamingAngleBrackets uses angle brackets (URI-encoded in $ref).
	// Example: Response[User] -> Response<User>
	// Note: Produces Response%3CUser%3E in $ref URIs.
	GenericNamingAngleBrackets

	// GenericNamingFlattened removes brackets entirely.
	// Example: Response[User] -> ResponseUser
	GenericNamingFlattened
)

type HandlerFunc added in v1.34.0

type HandlerFunc func(ctx context.Context, req *Request) Response

HandlerFunc is the signature for operation handlers. The Request contains validated parameters and the raw http.Request. The Response interface allows type-safe response construction.

func ErrorStubHandler added in v1.34.0

func ErrorStubHandler(status int, message string) HandlerFunc

ErrorStubHandler creates a handler that returns an error response.

Example:

srv.Handle(http.MethodDelete, "/pets/{petId}", builder.ErrorStubHandler(http.StatusNotFound, "pet not found"))

func StubHandler added in v1.34.0

func StubHandler(response Response) HandlerFunc

StubHandler creates a handler that returns a fixed response.

Example:

srv.Handle(http.MethodGet, "/pets", builder.StubHandler(builder.JSON(http.StatusOK, pets)))

func StubHandlerFunc added in v1.34.0

func StubHandlerFunc(fn func(req *Request) Response) HandlerFunc

StubHandlerFunc creates a handler that calls a function. Useful for asserting request contents in tests.

Example:

srv.Handle(http.MethodGet, "/pets/{petId}", builder.StubHandlerFunc(func(req *builder.Request) builder.Response {
	petID := req.PathParams["petId"]
	// assertions on petID
	return builder.JSON(http.StatusOK, pet)
}))

type Middleware added in v1.34.0

type Middleware func(http.Handler) http.Handler

Middleware wraps handlers with additional behavior.

type OperationOption

type OperationOption func(*operationConfig)

OperationOption configures an operation.

func WithConsumes added in v1.30.0

func WithConsumes(mimeTypes ...string) OperationOption

WithConsumes sets the consumes MIME types for the operation. This is only applicable to OAS 2.0 specifications. For OAS 3.x, use WithRequestBodyContentTypes instead to specify multiple content types.

Example:

builder.AddOperation("POST", "/users",
    builder.WithConsumes("application/json", "application/xml"),
    builder.WithRequestBody("application/json", User{}),
)

func WithCookieParam

func WithCookieParam(name string, paramType any, opts ...ParamOption) OperationOption

WithCookieParam adds a cookie parameter to the operation.

func WithDefaultResponse

func WithDefaultResponse(responseType any, opts ...ResponseOption) OperationOption

WithDefaultResponse sets the default response for the operation. Use WithResponseContentType to specify a content type other than "application/json".

func WithDeprecated

func WithDeprecated(deprecated bool) OperationOption

WithDeprecated marks the operation as deprecated.

func WithDescription

func WithDescription(desc string) OperationOption

WithDescription sets the operation description.

func WithFileParam added in v1.15.0

func WithFileParam(name string, opts ...ParamOption) OperationOption

WithFileParam adds a file upload parameter to the operation. This is primarily for OAS 2.0 file uploads using formData parameters with type="file". For OAS 3.x, it automatically creates a multipart/form-data request body with binary format.

Note: Parameter constraints (minLength, maxLength, pattern, etc.) are not applicable to file parameters and will be ignored. Only description, required, and deprecated options are meaningful for file uploads.

Example (OAS 2.0):

WithFileParam("file", WithParamDescription("File to upload"), WithParamRequired(true))

Example (OAS 3.x):

// Automatically generates multipart/form-data request body:
WithFileParam("file", WithParamDescription("File to upload"), WithParamRequired(true))

func WithFormParam added in v1.14.0

func WithFormParam(name string, paramType any, opts ...ParamOption) OperationOption

WithFormParam adds a form parameter to the operation. The handling differs based on OAS version:

  • OAS 2.0: Adds a parameter with in="formData"
  • OAS 3.x: Adds to request body with content-type application/x-www-form-urlencoded

Form parameters support all standard parameter options including constraints, description, required flag, default values, and format specifications.

func WithHandler added in v1.34.0

func WithHandler(handler HandlerFunc) OperationOption

WithHandler registers a handler for this operation. The handler is keyed by the operation's method and path. This option only has effect when used with ServerBuilder.AddOperation.

Example:

srv.AddOperation(http.MethodGet, "/pets",
    builder.WithHandler(listPetsHandler),
    builder.WithResponse(http.StatusOK, []Pet{}),
)

func WithHandlerFunc added in v1.34.0

func WithHandlerFunc(handler http.HandlerFunc) OperationOption

WithHandlerFunc registers an http.HandlerFunc for this operation. This is a convenience option for handlers that don't need typed request/response handling. The handler is keyed by the operation's method and path. This option only has effect when used with ServerBuilder.AddOperation.

Example:

srv.AddOperation(http.MethodGet, "/health",
    builder.WithHandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
    }),
)

func WithHeaderParam

func WithHeaderParam(name string, paramType any, opts ...ParamOption) OperationOption

WithHeaderParam adds a header parameter to the operation.

func WithNoSecurity

func WithNoSecurity() OperationOption

WithNoSecurity explicitly marks the operation as requiring no security.

func WithOperationExtension added in v1.30.0

func WithOperationExtension(key string, value any) OperationOption

WithOperationExtension adds a vendor extension (x-* field) to the operation. The key must start with "x-" as per the OpenAPI specification. Extensions are preserved in both OAS 2.0 and OAS 3.x output.

Example:

builder.AddOperation("GET", "/users",
    builder.WithOperationExtension("x-rate-limit", 100),
    builder.WithOperationExtension("x-internal", true),
)

func WithOperationID

func WithOperationID(id string) OperationOption

WithOperationID sets the operation ID.

func WithParameter

func WithParameter(param *parser.Parameter) OperationOption

WithParameter adds a pre-built parameter to the operation.

func WithParameterRef

func WithParameterRef(ref string) OperationOption

WithParameterRef adds a parameter reference to the operation.

func WithPathParam

func WithPathParam(name string, paramType any, opts ...ParamOption) OperationOption

WithPathParam adds a path parameter to the operation. Note: Path parameters are always required per the OAS spec.

func WithProduces added in v1.30.0

func WithProduces(mimeTypes ...string) OperationOption

WithProduces sets the produces MIME types for the operation. This is only applicable to OAS 2.0 specifications. For OAS 3.x, use WithResponseContentTypes instead to specify multiple content types.

Example:

builder.AddOperation("GET", "/users/{id}",
    builder.WithProduces("application/json", "application/xml"),
    builder.WithResponse(200, User{}),
)

func WithQueryParam

func WithQueryParam(name string, paramType any, opts ...ParamOption) OperationOption

WithQueryParam adds a query parameter to the operation.

func WithRequestBody

func WithRequestBody(contentType string, bodyType any, opts ...RequestBodyOption) OperationOption

WithRequestBody sets the request body for the operation. The bodyType is reflected to generate the schema.

func WithRequestBodyContentTypes added in v1.30.0

func WithRequestBodyContentTypes(contentTypes []string, bodyType any, opts ...RequestBodyOption) OperationOption

WithRequestBodyContentTypes sets the request body for the operation with multiple content types. All content types share the same schema. This is primarily useful for OAS 3.x specifications where the request body content map can contain multiple media types.

For OAS 2.0, only the first content type is used for the body parameter schema, and you should set the consumes array separately using WithConsumes.

Example:

builder.AddOperation("POST", "/users",
    builder.WithRequestBodyContentTypes(
        []string{"application/json", "application/xml"},
        User{},
        builder.WithRequired(true),
    ),
)

func WithRequestBodyRawSchema added in v1.15.0

func WithRequestBodyRawSchema(contentType string, schema *parser.Schema, opts ...RequestBodyOption) OperationOption

WithRequestBodyRawSchema sets the request body for the operation with a pre-built schema. This is useful when you need full control over the schema structure or when working with schemas that cannot be easily represented with Go types (e.g., file uploads, oneOf/anyOf).

Example:

schema := &parser.Schema{
	Type: "string",
	Format: "binary",
}
WithRequestBodyRawSchema("application/octet-stream", schema)

func WithResponse

func WithResponse(statusCode int, responseType any, opts ...ResponseOption) OperationOption

WithResponse adds a response to the operation. The responseType is reflected to generate the schema. Use WithResponseContentType to specify a content type other than "application/json".

func WithResponseContentTypes added in v1.30.0

func WithResponseContentTypes(statusCode int, contentTypes []string, responseType any, opts ...ResponseOption) OperationOption

WithResponseContentTypes adds a response with multiple content types to the operation. All content types share the same schema. This is primarily useful for OAS 3.x specifications where the response content map can contain multiple media types.

For OAS 2.0, only the first content type is used for the response schema, and you should set the produces array separately using WithProduces.

Example:

builder.AddOperation("GET", "/users/{id}",
    builder.WithResponseContentTypes(
        200,
        []string{"application/json", "application/xml"},
        User{},
        builder.WithResponseDescription("User found"),
    ),
)

func WithResponseRawSchema added in v1.15.0

func WithResponseRawSchema(statusCode int, contentType string, schema *parser.Schema, opts ...ResponseOption) OperationOption

WithResponseRawSchema adds a response to the operation with a pre-built schema. This is useful when you need full control over the schema structure or when working with schemas that cannot be easily represented with Go types (e.g., file downloads, oneOf/anyOf).

Example:

schema := &parser.Schema{
	Type: "string",
	Format: "binary",
}
WithResponseRawSchema(200, "application/octet-stream", schema,
	WithResponseDescription("File download"))

func WithResponseRef

func WithResponseRef(statusCode int, ref string) OperationOption

WithResponseRef adds a response reference to the operation.

func WithSecurity

func WithSecurity(requirements ...parser.SecurityRequirement) OperationOption

WithSecurity sets the security requirements for the operation.

func WithSummary

func WithSummary(summary string) OperationOption

WithSummary sets the operation summary.

func WithTags

func WithTags(tags ...string) OperationOption

WithTags sets the operation tags.

type ParamOption

type ParamOption func(*paramConfig)

ParamOption configures a parameter.

func WithParamAllowEmptyValue added in v1.30.0

func WithParamAllowEmptyValue(allow bool) ParamOption

WithParamAllowEmptyValue sets whether the parameter allows empty values. This is only applicable to OAS 2.0 specifications for query and formData parameters. Setting this to true allows sending a parameter with an empty value.

Example:

builder.WithQueryParam("filter", "",
    builder.WithParamAllowEmptyValue(true),
)

func WithParamCollectionFormat added in v1.30.0

func WithParamCollectionFormat(format string) ParamOption

WithParamCollectionFormat sets the collection format for array parameters. This is only applicable to OAS 2.0 specifications.

Valid values are:

  • "csv": comma separated values (default) - foo,bar
  • "ssv": space separated values - foo bar
  • "tsv": tab separated values - foo\tbar
  • "pipes": pipe separated values - foo|bar
  • "multi": corresponds to multiple parameter instances - foo=bar&foo=baz

Example:

builder.WithQueryParam("tags", []string{},
    builder.WithParamCollectionFormat("csv"),
)

func WithParamDefault added in v1.13.1

func WithParamDefault(value any) ParamOption

WithParamDefault sets the default value for the parameter.

func WithParamDeprecated

func WithParamDeprecated(deprecated bool) ParamOption

WithParamDeprecated marks the parameter as deprecated.

func WithParamDescription

func WithParamDescription(desc string) ParamOption

WithParamDescription sets the parameter description.

func WithParamEnum added in v1.13.1

func WithParamEnum(values ...any) ParamOption

WithParamEnum sets the allowed values for the parameter.

func WithParamExample

func WithParamExample(example any) ParamOption

WithParamExample sets the parameter example.

func WithParamExclusiveMaximum added in v1.13.1

func WithParamExclusiveMaximum(exclusive bool) ParamOption

WithParamExclusiveMaximum sets whether the maximum is exclusive.

func WithParamExclusiveMinimum added in v1.13.1

func WithParamExclusiveMinimum(exclusive bool) ParamOption

WithParamExclusiveMinimum sets whether the minimum is exclusive.

func WithParamExtension added in v1.30.0

func WithParamExtension(key string, value any) ParamOption

WithParamExtension adds a vendor extension (x-* field) to the parameter. The key must start with "x-" as per the OpenAPI specification. Extensions are preserved in both OAS 2.0 and OAS 3.x output.

Example:

builder.WithQueryParam("limit", 0,
    builder.WithParamExtension("x-example-values", []int{10, 25, 50}),
)

func WithParamFormat added in v1.33.0

func WithParamFormat(format string) ParamOption

WithParamFormat sets an explicit OpenAPI format for the parameter. This overrides the format that would be inferred from the Go type.

Common formats include: "int32", "int64", "float", "double", "byte", "binary", "date", "date-time", "password", "email", "uri", "uuid", "hostname", "ipv4", "ipv6".

Example:

builder.WithQueryParam("user_id", "",
    builder.WithParamFormat("uuid"),
)

func WithParamMaxItems added in v1.13.1

func WithParamMaxItems(max int) ParamOption

WithParamMaxItems sets the maximum number of items for array parameters.

func WithParamMaxLength added in v1.13.1

func WithParamMaxLength(max int) ParamOption

WithParamMaxLength sets the maximum length for string parameters.

func WithParamMaximum added in v1.13.1

func WithParamMaximum(max float64) ParamOption

WithParamMaximum sets the maximum value for numeric parameters.

func WithParamMinItems added in v1.13.1

func WithParamMinItems(min int) ParamOption

WithParamMinItems sets the minimum number of items for array parameters.

func WithParamMinLength added in v1.13.1

func WithParamMinLength(min int) ParamOption

WithParamMinLength sets the minimum length for string parameters.

func WithParamMinimum added in v1.13.1

func WithParamMinimum(min float64) ParamOption

WithParamMinimum sets the minimum value for numeric parameters.

func WithParamMultipleOf added in v1.13.1

func WithParamMultipleOf(value float64) ParamOption

WithParamMultipleOf sets the multipleOf constraint for numeric parameters.

func WithParamPattern added in v1.13.1

func WithParamPattern(pattern string) ParamOption

WithParamPattern sets the pattern (regex) for string parameters.

func WithParamRequired

func WithParamRequired(required bool) ParamOption

WithParamRequired sets whether the parameter is required.

func WithParamSchema added in v1.33.0

func WithParamSchema(schema *parser.Schema) ParamOption

WithParamSchema sets a complete schema for the parameter. This takes precedence over type/format inference and the WithParamType/WithParamFormat options.

Use this for complex schemas that cannot be easily represented with Go types (e.g., oneOf, arrays with specific item constraints).

Example:

builder.WithQueryParam("ids", nil,
    builder.WithParamSchema(&parser.Schema{
        Type:  "array",
        Items: &parser.Schema{Type: "string", Format: "uuid"},
    }),
)

func WithParamType added in v1.33.0

func WithParamType(typeName string) ParamOption

WithParamType sets an explicit OpenAPI type for the parameter. This overrides the type that would be inferred from the Go type.

Valid types per OpenAPI specification: "string", "integer", "number", "boolean", "array", "object".

Example:

builder.WithQueryParam("data", []byte{},
    builder.WithParamType("string"),
    builder.WithParamFormat("byte"),
)

func WithParamUniqueItems added in v1.13.1

func WithParamUniqueItems(unique bool) ParamOption

WithParamUniqueItems sets whether array items must be unique.

type Request added in v1.34.0

type Request struct {
	// HTTPRequest is the original HTTP request.
	HTTPRequest *http.Request

	// PathParams contains the extracted and validated path parameters.
	// Keys are parameter names, values are deserialized values.
	PathParams map[string]any

	// QueryParams contains the extracted and validated query parameters.
	QueryParams map[string]any

	// HeaderParams contains the extracted and validated header parameters.
	HeaderParams map[string]any

	// CookieParams contains the extracted and validated cookie parameters.
	CookieParams map[string]any

	// Body is the unmarshaled request body (typically a map or struct).
	Body any

	// RawBody is the raw request body bytes.
	RawBody []byte

	// OperationID is the operation ID for this request.
	OperationID string

	// MatchedPath is the OpenAPI path template that matched (e.g., "/pets/{petId}").
	MatchedPath string
}

Request contains validated request data passed to operation handlers.

type RequestBodyOption

type RequestBodyOption func(*requestBodyConfig)

RequestBodyOption configures a request body.

func WithRequestBodyExtension added in v1.30.0

func WithRequestBodyExtension(key string, value any) RequestBodyOption

WithRequestBodyExtension adds a vendor extension (x-* field) to the request body. The key must start with "x-" as per the OpenAPI specification. Extensions are preserved in OAS 3.x output. For OAS 2.0, request bodies are converted to body parameters, and extensions will be applied to the body parameter.

Example:

builder.AddOperation("POST", "/users",
    builder.WithRequestBody("application/json", User{},
        builder.WithRequestBodyExtension("x-codegen-request-body-name", "user"),
    ),
)

func WithRequestDescription

func WithRequestDescription(desc string) RequestBodyOption

WithRequestDescription sets the request body description.

func WithRequestExample

func WithRequestExample(example any) RequestBodyOption

WithRequestExample sets the request body example.

func WithRequired

func WithRequired(required bool) RequestBodyOption

WithRequired sets whether the request body is required.

type Response added in v1.34.0

type Response interface {
	// StatusCode returns the HTTP status code.
	StatusCode() int

	// Headers returns the HTTP headers to include in the response.
	Headers() http.Header

	// Body returns the response body (may be nil for no-content responses).
	Body() any

	// WriteTo writes the response to the ResponseWriter.
	WriteTo(w http.ResponseWriter) error
}

Response is implemented by response types. All response helpers return types that implement this interface.

func Error added in v1.34.0

func Error(status int, message string) Response

Error creates an error response with status and message.

Example:

return builder.Error(http.StatusNotFound, "pet not found")

func ErrorWithDetails added in v1.34.0

func ErrorWithDetails(status int, message string, details any) Response

ErrorWithDetails creates an error response with additional details.

Example:

return builder.ErrorWithDetails(http.StatusBadRequest, "validation failed", errors)

func JSON added in v1.34.0

func JSON(status int, body any) Response

JSON creates a JSON response with the given status and body.

Example:

return builder.JSON(http.StatusOK, pets)

func NoContent added in v1.34.0

func NoContent() Response

NoContent creates a 204 No Content response.

Example:

return builder.NoContent()

func Redirect added in v1.34.0

func Redirect(status int, location string) Response

Redirect creates a redirect response.

Example:

return builder.Redirect(http.StatusMovedPermanently, "/new-location")

func Stream added in v1.34.0

func Stream(status int, contentType string, reader io.Reader) Response

Stream creates a streaming response.

Example:

return builder.Stream(http.StatusOK, "application/octet-stream", file)

type ResponseBuilder added in v1.34.0

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

ResponseBuilder provides fluent response construction.

func NewResponse added in v1.34.0

func NewResponse(status int) *ResponseBuilder

NewResponse creates a new ResponseBuilder.

Example:

return builder.NewResponse(http.StatusOK).
	Header("X-Request-Id", requestID).
	JSON(pets)

func (*ResponseBuilder) Binary added in v1.34.0

func (b *ResponseBuilder) Binary(contentType string, data []byte) Response

Binary sets a binary body.

func (*ResponseBuilder) Body added in v1.34.0

func (b *ResponseBuilder) Body() any

func (*ResponseBuilder) Header added in v1.34.0

func (b *ResponseBuilder) Header(key, value string) *ResponseBuilder

Header adds a header to the response.

func (*ResponseBuilder) Headers added in v1.34.0

func (b *ResponseBuilder) Headers() http.Header

func (*ResponseBuilder) JSON added in v1.34.0

func (b *ResponseBuilder) JSON(body any) Response

JSON sets a JSON body.

func (*ResponseBuilder) StatusCode added in v1.34.0

func (b *ResponseBuilder) StatusCode() int

func (*ResponseBuilder) Text added in v1.34.0

func (b *ResponseBuilder) Text(body string) Response

Text sets a plain text body.

func (*ResponseBuilder) WriteTo added in v1.34.0

func (b *ResponseBuilder) WriteTo(w http.ResponseWriter) error

func (*ResponseBuilder) XML added in v1.34.0

func (b *ResponseBuilder) XML(body any) Response

XML sets an XML body.

type ResponseOption

type ResponseOption func(*responseConfig)

ResponseOption configures a response.

func WithResponseContentType

func WithResponseContentType(contentType string) ResponseOption

WithResponseContentType sets the content type for the response. Defaults to "application/json" if not specified.

func WithResponseDescription

func WithResponseDescription(desc string) ResponseOption

WithResponseDescription sets the response description.

func WithResponseExample

func WithResponseExample(example any) ResponseOption

WithResponseExample sets the response example.

func WithResponseExtension added in v1.30.0

func WithResponseExtension(key string, value any) ResponseOption

WithResponseExtension adds a vendor extension (x-* field) to the response. The key must start with "x-" as per the OpenAPI specification. Extensions are preserved in both OAS 2.0 and OAS 3.x output.

Example:

builder.WithResponse(200, User{},
    builder.WithResponseExtension("x-cache-ttl", 3600),
)

func WithResponseHeader

func WithResponseHeader(name string, header *parser.Header) ResponseOption

WithResponseHeader adds a header to the response.

type RouterStrategy added in v1.34.0

type RouterStrategy interface {
	// Build creates an http.Handler that routes requests to the dispatcher.
	// Returns an error if the routes cannot be configured (e.g., invalid path patterns).
	Build(routes []operationRoute, dispatcher http.Handler) (http.Handler, error)

	// PathParam extracts a path parameter from the request context.
	PathParam(r *http.Request, name string) string
}

RouterStrategy defines how paths are matched to handlers. The stdlib router implements this interface. Additional router implementations (e.g., chi, gorilla/mux) can implement this interface for custom routing.

type SchemaFieldProcessor added in v1.36.0

type SchemaFieldProcessor func(schema *parser.Schema, field reflect.StructField) *parser.Schema

SchemaFieldProcessor is called for each struct field during schema generation. It receives the generated schema and the struct field, and returns a potentially modified schema. The processor is called after oastools applies any oas:"..." tags.

This hook enables libraries to support custom tag formats alongside the standard oas:"..." tags. For example, a library could support legacy standalone tags:

type User struct {
    Name string `json:"name" description:"User's full name"`
}

The processor can read these tags and apply them to the schema:

func(schema *parser.Schema, field reflect.StructField) *parser.Schema {
    if desc := field.Tag.Get("description"); desc != "" {
        schema.Description = desc
    }
    return schema
}

func ComposeSchemaFieldProcessors added in v1.36.0

func ComposeSchemaFieldProcessors(processors ...SchemaFieldProcessor) SchemaFieldProcessor

ComposeSchemaFieldProcessors chains multiple processors into a single processor. Processors are executed in order, with each receiving the schema returned by the previous. This enables composing multiple independent tag processors into a single pipeline.

If no processors are provided, returns nil. If only one processor is provided, returns that processor directly. Nil processors in the list are skipped.

Example - combining legacy tag support with custom validation:

descriptionProcessor := func(schema *parser.Schema, field reflect.StructField) *parser.Schema {
    if desc := field.Tag.Get("description"); desc != "" {
        schema.Description = desc
    }
    return schema
}

enumProcessor := func(schema *parser.Schema, field reflect.StructField) *parser.Schema {
    if enumStr := field.Tag.Get("enum"); enumStr != "" {
        values := strings.Split(enumStr, "|")
        schema.Enum = make([]any, len(values))
        for i, v := range values {
            schema.Enum[i] = strings.TrimSpace(v)
        }
    }
    return schema
}

// Combine both processors
spec := builder.New(parser.OASVersion320,
    builder.WithSchemaFieldProcessor(
        builder.ComposeSchemaFieldProcessors(descriptionProcessor, enumProcessor),
    ),
)

type SchemaNameContext added in v1.25.0

type SchemaNameContext struct {
	// Type is the Go type name without package (e.g., "User", "Response[T]").
	Type string

	// TypeSanitized is Type with generic brackets replaced per GenericNamingStrategy.
	TypeSanitized string

	// TypeBase is the base type name without generic parameters (e.g., "Response").
	TypeBase string

	// Package is the package base name (e.g., "models").
	Package string

	// PackagePath is the full import path (e.g., "github.com/org/models").
	PackagePath string

	// PackagePathSanitized is PackagePath with slashes replaced
	// (e.g., "github.com_org_models").
	PackagePathSanitized string

	// IsGeneric indicates if the type has type parameters.
	IsGeneric bool

	// GenericParams contains the type parameter names if IsGeneric is true.
	GenericParams []string

	// GenericParamsSanitized contains sanitized type parameter names.
	GenericParamsSanitized []string

	// GenericSuffix is the formatted generic parameters portion.
	// Varies based on GenericNamingStrategy (e.g., "_User_", "OfUser", "<User>").
	GenericSuffix string

	// IsAnonymous indicates if this is an anonymous struct type.
	IsAnonymous bool

	// IsPointer indicates if the original type was a pointer.
	IsPointer bool

	// Kind is the reflect.Kind as a string (e.g., "struct", "slice", "map").
	Kind string
}

SchemaNameContext provides type metadata for custom naming templates and functions. All fields are populated before being passed to templates or custom naming functions.

type SchemaNameFunc added in v1.25.0

type SchemaNameFunc func(ctx SchemaNameContext) string

SchemaNameFunc is the signature for custom schema naming functions. The function receives a SchemaNameContext with complete type metadata and should return the desired schema name.

type SchemaNamingStrategy added in v1.25.0

type SchemaNamingStrategy int

SchemaNamingStrategy defines built-in schema naming conventions. Use these with WithSchemaNaming to control how schema names are generated from Go types.

const (
	// SchemaNamingDefault uses "package.TypeName" format (current behavior).
	// Example: models.User
	SchemaNamingDefault SchemaNamingStrategy = iota

	// SchemaNamingPascalCase uses "PackageTypeName" format.
	// Example: models.User -> ModelsUser
	SchemaNamingPascalCase

	// SchemaNamingCamelCase uses "packageTypeName" format.
	// Example: models.User -> modelsUser
	SchemaNamingCamelCase

	// SchemaNamingSnakeCase uses "package_type_name" format.
	// Example: models.User -> models_user
	SchemaNamingSnakeCase

	// SchemaNamingKebabCase uses "package-type-name" format.
	// Example: models.User -> models-user
	SchemaNamingKebabCase

	// SchemaNamingTypeOnly uses just "TypeName" without package.
	// Example: models.User -> User
	// Warning: May cause conflicts with same-named types in different packages.
	SchemaNamingTypeOnly

	// SchemaNamingFullPath uses full package path.
	// Example: models.User -> github.com_org_models_User
	SchemaNamingFullPath
)

type ServerBuilder added in v1.34.0

type ServerBuilder struct {
	*Builder
	// contains filtered or unexported fields
}

ServerBuilder extends Builder to support server construction. It embeds Builder, inheriting all specification construction methods.

Concurrency: ServerBuilder instances are not safe for concurrent use. Create separate ServerBuilder instances for concurrent operations.

func FromBuilder added in v1.34.0

func FromBuilder(b *Builder, opts ...ServerBuilderOption) *ServerBuilder

FromBuilder creates a ServerBuilder from an existing Builder. This allows converting an existing specification into a runnable server.

Example:

b := builder.New(parser.OASVersion320).SetTitle("My API")
srv := builder.FromBuilder(b)
srv.Handle(http.MethodGet, "/users", listUsersHandler)

func NewServerBuilder added in v1.34.0

func NewServerBuilder(version parser.OASVersion, opts ...ServerBuilderOption) *ServerBuilder

NewServerBuilder creates a ServerBuilder for the specified OAS version. This is the primary entry point for the server builder API.

Example:

srv := builder.NewServerBuilder(parser.OASVersion320).
	SetTitle("Pet Store API").
	SetVersion("1.0.0")

srv.AddOperation(http.MethodGet, "/pets",
	builder.WithHandler(listPetsHandler),
	builder.WithResponse(http.StatusOK, []Pet{}),
)

result, err := srv.BuildServer()

func (*ServerBuilder) AddOperation added in v1.34.0

func (s *ServerBuilder) AddOperation(method, path string, opts ...OperationOption) *ServerBuilder

AddOperation adds an operation and returns the ServerBuilder for chaining. Overrides Builder.AddOperation to support inline handler registration via WithHandler.

Example:

srv.AddOperation(http.MethodGet, "/pets",
	builder.WithHandler(listPetsHandler),
	builder.WithResponse(http.StatusOK, []Pet{}),
)

func (*ServerBuilder) AddSecurityScheme added in v1.34.0

func (s *ServerBuilder) AddSecurityScheme(name string, scheme *parser.SecurityScheme) *ServerBuilder

AddSecurityScheme adds a security scheme. Overrides Builder.AddSecurityScheme to maintain fluent chaining.

func (*ServerBuilder) AddServer added in v1.34.0

func (s *ServerBuilder) AddServer(url string, opts ...ServerOption) *ServerBuilder

AddServer adds a server definition. Overrides Builder.AddServer to maintain fluent chaining.

func (*ServerBuilder) AddTag added in v1.34.0

func (s *ServerBuilder) AddTag(name string, opts ...TagOption) *ServerBuilder

AddTag adds a tag definition. Overrides Builder.AddTag to maintain fluent chaining.

func (*ServerBuilder) BuildServer added in v1.34.0

func (s *ServerBuilder) BuildServer() (*ServerResult, error)

BuildServer constructs the http.Handler and related artifacts. Returns an error if the OAS document is invalid or the router cannot be configured.

Operations without registered handlers will return 501 Not Implemented at runtime. To enforce that all operations have handlers, check the handlers map before calling BuildServer.

Example:

result, err := srv.BuildServer()
if err != nil {
	log.Fatal(err)
}
http.ListenAndServe(":8080", result.Handler)

func (*ServerBuilder) Handle added in v1.34.0

func (s *ServerBuilder) Handle(method, path string, handler HandlerFunc) *ServerBuilder

Handle registers a handler for an operation by method and path. This is an alternative to using WithHandler in AddOperation, useful for dynamic handler registration or when the handler isn't known at definition time.

Example:

srv.Handle(http.MethodGet, "/pets", func(ctx context.Context, req *builder.Request) builder.Response {
	return builder.JSON(http.StatusOK, pets)
})

func (*ServerBuilder) HandleFunc added in v1.34.0

func (s *ServerBuilder) HandleFunc(method, path string, handler http.HandlerFunc) *ServerBuilder

HandleFunc registers a handler using a standard http.HandlerFunc signature. This is useful for operations that don't need typed parameters.

Example:

srv.HandleFunc(http.MethodGet, "/health", func(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	w.Write([]byte("OK"))
})

func (*ServerBuilder) MustBuildServer added in v1.34.0

func (s *ServerBuilder) MustBuildServer() *ServerResult

MustBuildServer is like BuildServer but panics on error. Useful for main() or init() where errors are fatal.

func (*ServerBuilder) SetDescription added in v1.34.0

func (s *ServerBuilder) SetDescription(desc string) *ServerBuilder

SetDescription sets the API description. Overrides Builder.SetDescription to maintain fluent chaining.

func (*ServerBuilder) SetSecurity added in v1.34.0

func (s *ServerBuilder) SetSecurity(requirements ...parser.SecurityRequirement) *ServerBuilder

SetSecurity sets global security requirements. Overrides Builder.SetSecurity to maintain fluent chaining.

func (*ServerBuilder) SetTitle added in v1.34.0

func (s *ServerBuilder) SetTitle(title string) *ServerBuilder

SetTitle sets the API title. Overrides Builder.SetTitle to maintain fluent chaining.

func (*ServerBuilder) SetVersion added in v1.34.0

func (s *ServerBuilder) SetVersion(version string) *ServerBuilder

SetVersion sets the API version. Overrides Builder.SetVersion to maintain fluent chaining.

func (*ServerBuilder) Use added in v1.34.0

func (s *ServerBuilder) Use(mw ...Middleware) *ServerBuilder

Use adds middleware to the server. Middleware is applied in order: first added = outermost (executes first on request). For example, Use(A, B) results in: A(B(handler)), so A runs first.

Example:

srv.Use(loggingMiddleware, corsMiddleware)

type ServerBuilderOption added in v1.34.0

type ServerBuilderOption func(*serverBuilderConfig)

ServerBuilderOption configures a ServerBuilder.

func WithErrorHandler added in v1.34.0

func WithErrorHandler(handler ErrorHandler) ServerBuilderOption

WithErrorHandler sets the error handler for handler panics and errors.

func WithMethodNotAllowedHandler added in v1.34.0

func WithMethodNotAllowedHandler(handler http.Handler) ServerBuilderOption

WithMethodNotAllowedHandler sets the handler for unmatched methods.

func WithNotFoundHandler added in v1.34.0

func WithNotFoundHandler(handler http.Handler) ServerBuilderOption

WithNotFoundHandler sets the handler for unmatched paths.

func WithRecovery added in v1.34.0

func WithRecovery() ServerBuilderOption

WithRecovery enables panic recovery middleware. Recovered panics are passed to the error handler.

func WithRequestLogging added in v1.34.0

func WithRequestLogging(logger func(method, path string, status int, duration time.Duration)) ServerBuilderOption

WithRequestLogging enables request logging middleware.

func WithRouter added in v1.34.0

func WithRouter(strategy RouterStrategy) ServerBuilderOption

WithRouter sets the routing strategy. Default: StdlibRouter (uses net/http with PathMatcherSet).

func WithStdlibRouter added in v1.34.0

func WithStdlibRouter() ServerBuilderOption

WithStdlibRouter uses net/http with PathMatcherSet for routing. This is the default and adds no dependencies.

func WithValidationConfig added in v1.34.0

func WithValidationConfig(validationCfg ValidationConfig) ServerBuilderOption

WithValidationConfig sets validation middleware configuration.

func WithoutValidation added in v1.34.0

func WithoutValidation() ServerBuilderOption

WithoutValidation disables automatic request validation. Use when validation is handled elsewhere or for maximum performance.

type ServerOption

type ServerOption func(*serverConfig)

ServerOption configures a server.

func WithServerDescription

func WithServerDescription(desc string) ServerOption

WithServerDescription sets the server description.

func WithServerVariable

func WithServerVariable(name, defaultValue string, opts ...ServerVariableOption) ServerOption

WithServerVariable adds a variable to the server.

type ServerResult added in v1.34.0

type ServerResult struct {
	// Handler is the HTTP handler ready to serve requests.
	Handler http.Handler

	// Spec is the built OAS document (*parser.OAS3Document or *parser.OAS2Document).
	Spec any

	// ParseResult is the parse result for compatibility with other packages.
	ParseResult *parser.ParseResult

	// Validator is the httpvalidator instance (nil if validation is disabled).
	Validator *httpvalidator.Validator
}

ServerResult contains the built server and related artifacts.

type ServerTest added in v1.34.0

type ServerTest struct {
	Result *ServerResult
}

ServerTest provides testing utilities for a built server.

func NewServerTest added in v1.34.0

func NewServerTest(result *ServerResult) *ServerTest

NewServerTest creates a ServerTest from a ServerResult. Panics if result is nil or result.Handler is nil, indicating a test setup error.

Example:

result := srv.MustBuildServer()
test := builder.NewServerTest(result)
rec := test.Execute(builder.NewTestRequest(http.MethodGet, "/pets"))

func (*ServerTest) Delete added in v1.34.0

func (t *ServerTest) Delete(path string) *httptest.ResponseRecorder

Delete performs a DELETE request.

Example:

rec := test.Delete("/pets/123")

func (*ServerTest) Execute added in v1.34.0

func (t *ServerTest) Execute(req *TestRequest) *httptest.ResponseRecorder

Execute runs a request and returns the recorder.

func (*ServerTest) GetJSON added in v1.34.0

func (t *ServerTest) GetJSON(path string, target any) (*httptest.ResponseRecorder, error)

GetJSON performs a GET and unmarshals the JSON response.

Example:

var pets []Pet
rec, err := test.GetJSON("/pets", &pets)

func (*ServerTest) PostJSON added in v1.34.0

func (t *ServerTest) PostJSON(path string, body any, target any) (*httptest.ResponseRecorder, error)

PostJSON performs a POST with a JSON body and unmarshals the response.

Example:

var created Pet
rec, err := test.PostJSON("/pets", newPet, &created)

func (*ServerTest) PutJSON added in v1.34.0

func (t *ServerTest) PutJSON(path string, body any, target any) (*httptest.ResponseRecorder, error)

PutJSON performs a PUT with a JSON body and unmarshals the response.

Example:

var updated Pet
rec, err := test.PutJSON("/pets/123", updatedPet, &updated)

func (*ServerTest) Request added in v1.34.0

func (t *ServerTest) Request(method, path string) *TestRequest

Request creates a test request builder.

type ServerVariableOption

type ServerVariableOption func(*serverVariableConfig)

ServerVariableOption configures a server variable.

func WithServerVariableDescription

func WithServerVariableDescription(desc string) ServerVariableOption

WithServerVariableDescription sets the description for a server variable.

func WithServerVariableEnum

func WithServerVariableEnum(values ...string) ServerVariableOption

WithServerVariableEnum sets the enum values for a server variable.

type TagOption

type TagOption func(*tagConfig)

TagOption configures a tag.

func WithTagDescription

func WithTagDescription(desc string) TagOption

WithTagDescription sets the tag description.

func WithTagExternalDocs

func WithTagExternalDocs(url, description string) TagOption

WithTagExternalDocs sets the external documentation for a tag.

type TestRequest added in v1.34.0

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

TestRequest builds requests for testing.

func NewTestRequest added in v1.34.0

func NewTestRequest(method, path string) *TestRequest

NewTestRequest creates a new test request builder.

Example:

req := builder.NewTestRequest(http.MethodGet, "/pets").
	Query("limit", "10").
	Header("Authorization", "Bearer token")

func (*TestRequest) Body added in v1.34.0

func (r *TestRequest) Body(contentType string, body io.Reader) *TestRequest

Body sets a raw request body.

func (*TestRequest) Build added in v1.34.0

func (r *TestRequest) Build() *http.Request

Build creates the http.Request.

func (*TestRequest) Execute added in v1.34.0

func (r *TestRequest) Execute(handler http.Handler) *httptest.ResponseRecorder

Execute runs the request against a handler and returns the response.

func (*TestRequest) Header added in v1.34.0

func (r *TestRequest) Header(key, value string) *TestRequest

Header adds a header.

func (*TestRequest) JSONBody added in v1.34.0

func (r *TestRequest) JSONBody(body any) *TestRequest

JSONBody sets a JSON request body. Panics if the body cannot be marshaled to JSON, indicating a test setup error.

func (*TestRequest) Query added in v1.34.0

func (r *TestRequest) Query(key, value string) *TestRequest

Query adds a query parameter.

type ValidationConfig added in v1.34.0

type ValidationConfig struct {
	// IncludeRequestValidation enables request validation (default: true).
	IncludeRequestValidation bool

	// IncludeResponseValidation enables response validation (default: false).
	IncludeResponseValidation bool

	// StrictMode treats warnings as errors (default: false).
	StrictMode bool

	// OnValidationError is called when validation fails.
	// If nil, a default JSON error response is returned.
	OnValidationError ValidationErrorHandler
}

ValidationConfig configures request/response validation.

func DefaultValidationConfig added in v1.34.0

func DefaultValidationConfig() ValidationConfig

DefaultValidationConfig returns sensible defaults for validation.

type ValidationErrorHandler added in v1.34.0

type ValidationErrorHandler func(w http.ResponseWriter, r *http.Request, result *httpvalidator.RequestValidationResult)

ValidationErrorHandler handles validation failures.

Jump to

Keyboard shortcuts

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