version

package
v0.15.1 Latest Latest
Warning

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

Go to latest
Published: Mar 24, 2026 License: Apache-2.0 Imports: 6 Imported by: 0

Documentation

Overview

Package version provides API versioning support for the Rivaas router.

This package implements a clean, functional options-based API for configuring API versioning with excellent developer experience (DX).

Basic Usage

Create a versioned router with detection strategies. An engine can be created with New (returns error) or MustNew (panics on error). Options must not be nil; passing a nil option results in an error (or panic with MustNew).

r := router.New(
    router.WithVersioning(
        version.WithPathDetection("/api/v{version}"),
        version.WithHeaderDetection("X-API-Version"),
        version.WithDefault("v2"),
    ),
)

Detection Strategies

The package supports multiple version detection strategies, checked in priority order:

  • Path-based: version.WithPathDetection("/v{version}/")
  • Header-based: version.WithHeaderDetection("X-API-Version")
  • Query-based: version.WithQueryDetection("v")
  • Accept-header: version.WithAcceptDetection("application/vnd.myapi")
  • Custom: version.WithCustomDetection(func(r *http.Request) string { ... })

Version Lifecycle

Configure per-version lifecycle using functional options. Engine.ApplyLifecycle returns an error if any lifecycle option is nil.

v1 := r.Version("v1",
    version.Deprecated(),
    version.Sunset(time.Date(2025, 12, 31, 0, 0, 0, 0, time.UTC)),
    version.MigrationDocs("https://docs.example.com/v1-to-v2"),
)
v1.GET("/users", listUsersV1)

Response Headers

Configure automatic response headers:

router.WithVersioning(
    version.WithDefault("v2"),
    version.WithResponseHeaders(),  // X-API-Version header
    version.WithWarning299(),        // Warning: 299 for deprecated
    version.WithSunsetEnforcement(), // 410 Gone after sunset
)

Design Philosophy

This package follows these principles:

  • Configuration is via functional options only; no user-facing config struct. Engine exposes Engine.DefaultVersion and behavior such as Engine.SetLifecycleHeaders as needed.
  • Engine must be created with New or MustNew; do not call methods on a nil Engine. Callers (e.g. the router) guard at the call site when versioning is optional.
  • Observer hooks (version detection events) are configured only via WithObserver and OnDetected, OnMissing, OnInvalid, OnDeprecatedUse; no observer type is exported.
  • Constructors: both New and MustNew are provided (use MustNew in main/init, New when handling errors).
  • Progressive disclosure: simple cases are simple, complex cases are possible
  • Self-documenting: code reads like documentation
  • Cohesive: everything about a version is on the version object
  • Familiar: follows Go's functional options pattern

Index

Constants

This section is empty.

Variables

View Source
var (
	// Detection strategy errors
	ErrEmptyPathPattern          = errors.New("path pattern cannot be empty")
	ErrMissingVersionPlaceholder = errors.New("pattern must contain {version} placeholder")
	ErrEmptyHeaderName           = errors.New("header name cannot be empty")
	ErrEmptyQueryParam           = errors.New("query parameter name cannot be empty")
	ErrEmptyAcceptPattern        = errors.New("accept pattern cannot be empty")
	ErrNilCustomDetector         = errors.New("custom detector function cannot be nil")

	// Configuration errors
	ErrEmptyDefaultVersion = errors.New("default version cannot be empty")
	ErrNoValidVersions     = errors.New("at least one valid version is required")
	ErrEmptyVersionEntry   = errors.New("version cannot be empty")
	ErrDefaultRequired     = errors.New("default version is required")
)

Static errors for version configuration validation. These errors should be wrapped with fmt.Errorf and %w when context is needed.

Functions

This section is empty.

Types

type Detector

type Detector interface {
	// Detect attempts to extract a version from the request.
	// Returns the detected version and true if found, empty string and false otherwise.
	Detect(req *http.Request) (version string, found bool)

	// Method returns the detection method name for observability.
	Method() string
}

Detector defines the interface for version detection strategies.

type Engine

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

Engine manages API versioning, including version detection from requests and lifecycle header management.

An Engine must be created with New or MustNew. Do not call methods on a nil Engine; callers (e.g. the router) must guard with a non-nil engine before use.

func MustNew added in v0.15.0

func MustNew(opts ...Option) *Engine

MustNew creates a new versioning engine with the given options. It panics if configuration is invalid. Use in main() or init() where panic on startup is acceptable.

Example:

engine := version.MustNew(
    version.WithHeaderDetection("X-API-Version"),
    version.WithDefault("v1"),
)

func New

func New(opts ...Option) (*Engine, error)

New creates a new versioning engine with the given options.

Example:

engine, err := version.New(
    version.WithHeaderDetection("X-API-Version"),
    version.WithDefault("v1"),
)

func (*Engine) ApplyLifecycle added in v0.15.0

func (e *Engine) ApplyLifecycle(version string, opts ...LifecycleOption) error

ApplyLifecycle applies lifecycle options for a version. Options are merged with any existing lifecycle for that version (e.g. from a previous Version() or Configure() call). Used by the router when r.Version("v1", opts...) or VersionRouter.Configure(opts...) is called. Returns an error if any lifecycle option is nil.

func (*Engine) DefaultVersion added in v0.15.0

func (e *Engine) DefaultVersion() string

DefaultVersion returns the configured default version (e.g. when none is detected).

func (*Engine) DetectVersion

func (e *Engine) DetectVersion(req *http.Request) string

DetectVersion detects the API version from the request. Checks detectors in order until one returns a version. Falls back to default version if none found.

func (*Engine) ExtractPathSegment

func (e *Engine) ExtractPathSegment(path string) (string, bool)

ExtractPathSegment extracts the version segment from a path for stripping.

func (*Engine) SetLifecycleHeaders

func (e *Engine) SetLifecycleHeaders(w http.ResponseWriter, version, route string) bool

SetLifecycleHeaders sets response headers for version lifecycle (deprecation, sunset). Returns true if the version has passed its sunset date (caller should return 410 Gone).

func (*Engine) ShouldApplyVersioning

func (e *Engine) ShouldApplyVersioning(path string) bool

ShouldApplyVersioning determines if versioning should be applied to this path.

func (*Engine) StripPathVersion

func (e *Engine) StripPathVersion(path, version string) string

StripPathVersion removes the version segment from a path.

type LifecycleOption

type LifecycleOption func(*lifecycleConfig)

LifecycleOption configures a specific version's lifecycle. These options are passed to r.Version("v1", opts...) or VersionRouter.Configure(opts...).

func Deprecated

func Deprecated() LifecycleOption

Deprecated marks this version as deprecated. The deprecation date is set to now.

Example:

v1 := r.Version("v1", version.Deprecated())

func DeprecatedSince

func DeprecatedSince(date time.Time) LifecycleOption

DeprecatedSince marks this version as deprecated since a specific date. Use this when the deprecation was announced in the past.

Example:

v1 := r.Version("v1",
    version.DeprecatedSince(time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)),
)

func MigrationDocs

func MigrationDocs(url string) LifecycleOption

MigrationDocs sets the URL for migration documentation. This URL is included in Link headers with rel=deprecation and rel=sunset.

Example:

v1 := r.Version("v1",
    version.Deprecated(),
    version.MigrationDocs("https://docs.example.com/migrate/v1-to-v2"),
)

func SuccessorVersion

func SuccessorVersion(v string) LifecycleOption

SuccessorVersion indicates which version users should migrate to. This is informational and included in deprecation headers.

Example:

v1 := r.Version("v1",
    version.Deprecated(),
    version.SuccessorVersion("v2"),
)

func Sunset

func Sunset(date time.Time) LifecycleOption

Sunset sets when this version will be removed. After this date, requests will receive 410 Gone (if EnforceSunset is enabled).

Example:

v1 := r.Version("v1",
    version.Deprecated(),
    version.Sunset(time.Date(2025, 12, 31, 0, 0, 0, 0, time.UTC)),
)

type ObserverOption

type ObserverOption func(*observer)

ObserverOption configures the version observer (applies to internal observer; no observer type is exported).

func OnDeprecatedUse

func OnDeprecatedUse(fn func(version, route string)) ObserverOption

OnDeprecatedUse sets the callback for deprecated version usage.

func OnDetected

func OnDetected(fn func(version, method string)) ObserverOption

OnDetected sets the callback for successful version detection.

func OnInvalid

func OnInvalid(fn func(attempted string)) ObserverOption

OnInvalid sets the callback for invalid version detection.

func OnMissing

func OnMissing(fn func()) ObserverOption

OnMissing sets the callback for when no version is found (using default).

type Option

type Option func(*config)

Option configures the versioning engine. Options apply to the internal config; validation errors are collected and reported by New/MustNew.

func WithAcceptDetection

func WithAcceptDetection(pattern string) Option

WithAcceptDetection configures Accept-header based version detection. Follows RFC 7231 vendor-specific media types.

Example:

version.WithAcceptDetection("application/vnd.myapi.v{version}+json")
// Client sends: Accept: application/vnd.myapi.v2+json

func WithClock

func WithClock(nowFn func() time.Time) Option

WithClock sets a custom clock function for testing.

Example:

version.WithClock(func() time.Time {
    return time.Date(2025, 6, 1, 0, 0, 0, 0, time.UTC)
})

func WithCustomDetection

func WithCustomDetection(fn func(*http.Request) string) Option

WithCustomDetection configures a custom version detection function. Custom detectors have the highest priority when used.

Example:

version.WithCustomDetection(func(r *http.Request) string {
    // Extract version from JWT token
    := r.Header.Get("Authorization")
    return extractVersionFromToken(token)
})

func WithDefault

func WithDefault(version string) Option

WithDefault sets the default version when none is detected.

Example:

version.WithDefault("v2")

func WithHeaderDetection

func WithHeaderDetection(headerName string) Option

WithHeaderDetection configures header-based version detection.

Example:

version.WithHeaderDetection("X-API-Version")
// Client sends: X-API-Version: v2

version.WithHeaderDetection("API-Version")
// Client sends: API-Version: v2

func WithObserver

func WithObserver(opts ...ObserverOption) Option

WithObserver configures observability hooks for version detection events.

Example:

version.WithObserver(
    version.OnDetected(func(v, method string) {
        metrics.RecordVersionUsage(v, method)
    }),
    version.OnDeprecatedUse(func(v, route string) {
        log.Warn("deprecated API", "version", v, "route", route)
    }),
)

func WithPathDetection

func WithPathDetection(pattern string) Option

WithPathDetection configures path-based version detection. Pattern must contain {version} placeholder.

Example:

version.WithPathDetection("/api/v{version}")
// Matches: /api/v1/users, /api/v2/users

version.WithPathDetection("/v{version}/")
// Matches: /v1/users, /v2/users

func WithQueryDetection

func WithQueryDetection(paramName string) Option

WithQueryDetection configures query parameter-based version detection.

Example:

version.WithQueryDetection("v")
// Client sends: GET /users?v=v2

version.WithQueryDetection("version")
// Client sends: GET /users?version=v2

func WithResponseHeaders

func WithResponseHeaders() Option

WithResponseHeaders enables sending X-API-Version header in all versioned responses.

Example:

version.WithResponseHeaders()
// Response includes: X-API-Version: v2

func WithSunsetEnforcement

func WithSunsetEnforcement() Option

WithSunsetEnforcement enables 410 Gone responses for versions past their sunset date.

Example:

version.WithSunsetEnforcement()

func WithValidVersions

func WithValidVersions(versions ...string) Option

WithValidVersions sets the allowed versions for validation. Requests with invalid versions will fall back to the default version.

Example:

version.WithValidVersions("v1", "v2", "v3")

func WithWarning299

func WithWarning299() Option

WithWarning299 enables RFC 7234 Warning: 299 headers for deprecated versions.

Example:

version.WithWarning299()
// Response includes: Warning: 299 - "API v1 is deprecated..."

Jump to

Keyboard shortcuts

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