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 ¶
- Variables
- type Detector
- type Engine
- func (e *Engine) ApplyLifecycle(version string, opts ...LifecycleOption) error
- func (e *Engine) DefaultVersion() string
- func (e *Engine) DetectVersion(req *http.Request) string
- func (e *Engine) ExtractPathSegment(path string) (string, bool)
- func (e *Engine) SetLifecycleHeaders(w http.ResponseWriter, version, route string) bool
- func (e *Engine) ShouldApplyVersioning(path string) bool
- func (e *Engine) StripPathVersion(path, version string) string
- type LifecycleOption
- type ObserverOption
- type Option
- func WithAcceptDetection(pattern string) Option
- func WithClock(nowFn func() time.Time) Option
- func WithCustomDetection(fn func(*http.Request) string) Option
- func WithDefault(version string) Option
- func WithHeaderDetection(headerName string) Option
- func WithObserver(opts ...ObserverOption) Option
- func WithPathDetection(pattern string) Option
- func WithQueryDetection(paramName string) Option
- func WithResponseHeaders() Option
- func WithSunsetEnforcement() Option
- func WithValidVersions(versions ...string) Option
- func WithWarning299() Option
Constants ¶
This section is empty.
Variables ¶
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
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 ¶
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
DefaultVersion returns the configured default version (e.g. when none is detected).
func (*Engine) DetectVersion ¶
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 ¶
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 ¶
ShouldApplyVersioning determines if versioning should be applied to this path.
func (*Engine) StripPathVersion ¶
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 ¶
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 ¶
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 ¶
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 ¶
WithDefault sets the default version when none is detected.
Example:
version.WithDefault("v2")
func WithHeaderDetection ¶
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 ¶
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 ¶
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 ¶
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..."