Skip to main content

API Package

The api package is the foundation of VEF's request handling layer. It defines the core abstractions — resources, operations, request/response types, and handler resolution — that all other packages build upon.

API Reference

The generated Public API Index is the no-omissions checklist for every exported symbol, exported field, and exported method. This guide explains the supported behavior and runtime contracts behind that surface.

API groups covered in this guide:

GroupPublic APIs
resource and kindapi.Resource, api.Kind, api.KindRPC, api.KindREST, api.ValidateActionName(action, kind) error, api.NewRPCResource(name, opts...), api.NewRESTResource(name, opts...), api.WithVersion(v), api.WithAuth(config), api.WithOperations(specs...)
engine and routing extensionapi.Engine, api.RouterStrategy, api.Middleware
operationsapi.OperationSpec, api.Operation, api.RateLimitConfig, api.OperationsProvider, api.OperationsCollector
request modelapi.Identifier, api.Request, api.Params, api.Meta, api.P, api.M
authapi.AuthConfig, api.Public(), api.BearerAuth(), api.SignatureAuth(), api.IPAuth(...), api.APIKeyAuth(...), api.HTTPBasicAuth(), api.AuthStrategy, api.AuthStrategyRegistry
handler extensionapi.HandlerResolver, api.HandlerAdapter, api.HandlerParamResolver, api.FactoryParamResolver
audit, headers, versions, errorsapi.AuditEvent, api.SubscribeAuditEvent, api.HeaderXMetaPrefix, api.HeaderXTimestamp, api.HeaderXNonce, api.HeaderXSignature, api.HeaderXAppID, api.HeaderXBodyEncoding, api.VersionV1..api.VersionV9, api.ErrInvalidRequestParams, api.ErrInvalidRequestMeta, api.ErrInvalidParamsType, api.ErrInvalidMetaType, api.ErrUnsupportedBodyEncoding, api.ErrBodyDecodeFailed, api.ErrBodyTooLarge

Architecture

api.Engine
├── Register(resources...) — register resources
├── Mount(router) — attach to Fiber
└── Lookup(id) — find operation at runtime

api.Resource
├── Kind() — RPC or REST
├── Name() — resource path (e.g., "sys/user")
├── Version() — API version (e.g., "v1")
├── Auth() — authentication config
└── Operations() — list of OperationSpec

api.OperationSpec → api.Operation (runtime)

Resource

A Resource groups related API operations under a common path. VEF provides two resource kinds:

Creating Resources

// RPC resource — uses snake_case actions
resource := api.NewRPCResource("sys/user")

// REST resource — uses HTTP verbs
resource := api.NewRESTResource("sys/user")

// With options
resource := api.NewRPCResource("sys/user",
api.WithVersion("v2"),
api.WithAuth(api.BearerAuth()),
api.WithOperations(
api.OperationSpec{Action: "create", Handler: createHandler},
api.OperationSpec{Action: "find_page", Handler: findPageHandler},
),
)

Resource Kind

KindConstantName FormatAction FormatExample
RPCapi.KindRPCsnake_case with / separatorssnake_casesys/usercreate, find_page
RESTapi.KindRESTkebab-case with / separators<verb> or <verb> <sub-resource>sys/userget, post, get user-friends

Kind.String() returns rpc for KindRPC, rest for KindREST, and unknown for any other value.

Resource Name Rules

RuleValidInvalid
Must start with lowercase letteruser, sys/userUser, 1user
No leading/trailing slashessys/user/sys/user/
No consecutive slashessys/usersys//user
RPC: snake_case segmentssys/data_dictsys/data-dict
REST: kebab-case segmentssys/data-dictsys/data_dict

Resource Options

api.ResourceOption is the option function type accepted by the resource constructors:

OptionDescription
api.WithVersion(v)Override the engine's default version; v can be a literal matching ^v\d+$ (e.g., "v2") or a constant such as api.VersionV2
api.WithAuth(config)Set resource-level authentication
api.WithOperations(specs...)Provide operation specs directly

Version constants are provided for the reserved ladder api.VersionV1 through api.VersionV9. api.VersionV1 is the framework default; api.VersionV2, api.VersionV3, api.VersionV4, api.VersionV5, api.VersionV6, api.VersionV7, api.VersionV8, and api.VersionV9 are convenience constants for declaring higher API versions on a resource. Any literal matching ^v\d+$ is also accepted.

api.NewRPCResource and api.NewRESTResource validate the resource name, version, and any specs passed through direct api.WithOperations(...) at construction time. They panic when validation fails. api.ValidateActionName(action, kind) is public for code that builds resources dynamically and wants to apply the same RPC/REST action validation before constructing an OperationSpec.

REST action validation accepts these lowercase method tokens: get, post, put, delete, patch, head, options, trace, connect, and all. Sub-resource paths may contain /, but each segment must use kebab-case; dynamic Fiber params such as /:id are not accepted by the public validator.

Resource Interface

type Resource interface {
Kind() Kind
Name() string
Version() string
Auth() *AuthConfig
Operations() []OperationSpec
}

When using CRUD builders, you typically embed api.Resource and CRUD providers. The built-in collectors read direct Resource.Operations() specs and anonymous embedded OperationsProvider values. Direct WithOperations(...) specs are validated by the resource constructor. During engine registration, collected operation specs must have a non-empty action; custom OperationsProvider implementations should still produce action strings that already satisfy api.ValidateActionName(...).

OperationSpec

OperationSpec is the static definition of an API endpoint:

type OperationSpec struct {
Action string // Action name (e.g., "create", "find_page")
EnableAudit bool // Enable audit logging
Timeout time.Duration // Request timeout
Public bool // No authentication required
RequiredPermission string // Required permission token
RateLimit *RateLimitConfig // Rate limiting config
Handler any // Business logic handler
}

At runtime the engine materializes an Operation with final Auth and RateLimit pointers for router strategies, middleware, diagnostics, and tests. Operation and Request both embed Identifier, so Identifier.String() is promoted to Operation.String() and Request.String().

Operation defaulting rules:

FieldRuntime behavior
Actionrequired; direct WithOperations(...) specs are validated against the resource kind by the resource constructor; engine registration rejects an empty action
EnableAuditcopied directly to the runtime operation
Timeoutnon-positive values use the engine default, which is 30s unless overridden
Publictrue resolves auth to api.Public() before resource/default auth
RequiredPermissioncopied into auth options as the required permission token when non-empty
Operation.RateLimitnil uses the engine default — vef.api.rate_limit when configured, else Max=100, Period=5m; a custom RateLimitConfig replaces the default. An explicit Max <= 0 does not disable limiting — the middleware falls back to the engine/config default for non-positive values
HandlerRPC may infer from action when omitted; REST requires an explicit handler

Permission declarations are validated at registration and a violation fails startup:

  • RequiredPermission must be a dot-separated token matching ^[A-Za-z0-9_]+(\.[A-Za-z0-9_]+)*$ (e.g. sys.user.query); colons, slashes, dashes, empty segments, and whitespace are rejected with ErrPermissionTokenInvalid.
  • Declaring RequiredPermission on an operation whose resolved auth strategy is none (Public: true, or a resource-level api.Public()) is contradictory — the anonymous principal can never satisfy it — and is rejected with ErrPermissionOnPublicOp.

Using with CRUD Builders

Most operations are defined through CRUD builders rather than raw OperationSpec:

type UserResource struct {
api.Resource

crud.FindPage[User, UserSearch]
crud.Create[User, UserParams]
crud.Update[User, UserParams]
crud.Delete[User]
}

func NewUserResource() *UserResource {
return &UserResource{
Resource: api.NewRPCResource("sys/user"),
FindPage: crud.NewFindPage[User, UserSearch]().RequiredPermission("sys.user.query"),
Create: crud.NewCreate[User, UserParams]().RequiredPermission("sys.user.create"),
Update: crud.NewUpdate[User, UserParams]().RequiredPermission("sys.user.update"),
Delete: crud.NewDelete[User]().RequiredPermission("sys.user.delete"),
}
}

Using with Custom Handlers

For non-CRUD operations, use WithOperations or implement OperationsProvider:

resource := api.NewRPCResource("sys/user",
api.WithOperations(
api.OperationSpec{
Action: "reset_password",
Handler: resetPasswordHandler,
RequiredPermission: "sys.user.reset_password",
},
),
)

Identifier

Every operation has a unique Identifier:

type Identifier struct {
Resource string `json:"resource" form:"resource" validate:"required,alphanum_us_slash" label_i18n:"api_request_resource"`
Action string `json:"action" form:"action" validate:"required" label_i18n:"api_request_action"`
Version string `json:"version" form:"version" validate:"required,alphanum" label_i18n:"api_request_version"`
}

// String format: "sys/user:create:v1"
id.String()

The same Identifier fields are used by JSON RPC bodies and form RPC requests. Identifier.String() always formats as {resource}:{action}:{version}.

Request / Params / Meta

Request

The unified API request structure:

type Request struct {
Identifier
Params Params `json:"params"`
Meta Meta `json:"meta"`
}

Embed api.P in request parameter structs and api.M in metadata structs when you want handler injection to decode from params or meta explicitly.

Params

Params holds the business data of a request (the "what"):

type Params map[string]any

// Decode into a typed struct
var userParams UserParams
err := request.Params.Decode(&userParams)

// Access individual values
value, exists := request.Params["username"]

Meta

Meta holds request metadata (the "how" — pagination, sorting, format):

type Meta map[string]any

// Decode into a typed struct
var pageable page.Pageable
err := request.Meta.Decode(&pageable)

// Access individual values
value, exists := request.Meta["format"]

Params vs Meta

AspectParamsMeta
PurposeBusiness dataRequest control
Decoded intoTParams or TSearchpage.Pageable, DataOptionConfig, etc.
Examplesusername, email, departmentIdpage, size, sort, format

Authentication

AuthConfig

type AuthConfig struct {
Strategy string // "none", "bearer", "signature", "ip", or custom
Options map[string]any // Strategy-specific options
}

AuthConfig.Clone() returns a copy of the strategy/options pair. Use it when a resource or operation customizes auth without mutating shared config.

Built-in Auth Strategies

StrategyConstantDescription
Noneapi.AuthStrategyNoneNo authentication (public)
Bearerapi.AuthStrategyBearerBearer token authentication
Signatureapi.AuthStrategySignatureRequest signature authentication
IPapi.AuthStrategyIPSource-IP whitelist authentication
API keyapi.AuthStrategyAPIKeyStatic API key authentication
HTTP Basicapi.AuthStrategyHTTPBasicRFC 7617 Basic authentication

Helper Functions

api.Public() // AuthConfig with strategy "none"
api.BearerAuth() // AuthConfig with strategy "bearer"
api.SignatureAuth() // AuthConfig with strategy "signature"
api.IPAuth() // AuthConfig with strategy "ip" and whitelist "default"
api.IPAuth("ops") // AuthConfig with strategy "ip" and whitelist "ops"
api.APIKeyAuth() // AuthConfig with strategy "api_key", header X-API-Key
api.APIKeyAuth("X-My-Key") // custom key header; more than one name panics
api.HTTPBasicAuth() // AuthConfig with strategy "http_basic"

api.IPAuth(...) accepts zero or one whitelist name. With no argument it uses api.DefaultIPWhitelist ("default"); the selected name is stored under api.AuthOptionWhitelist in AuthConfig.Options. Passing more than one name panics. The built-in IP strategy resolves the named list through security.IPWhitelistLoader; the default loader reads vef.security.ip_whitelists. All auth failures deny with security.ErrIPNotAllowed, and an empty or missing named whitelist is fail-closed rather than treated as public access. Behind a reverse proxy, configure vef.app.trusted_proxies so Fiber resolves the real client IP.

api.APIKeyAuth(...) reads the key from api.HeaderXAPIKey (X-API-Key) by default; one custom header name may be passed and is stored under api.AuthOptionAPIKeyHeader. Keys resolve through security.APIKeyLoader (default: vef.security.api_keys). api.HTTPBasicAuth() verifies Authorization: Basic credentials through security.BasicAccountLoader (default: vef.security.basic_accounts) in constant time. Both reject uniformly with 401 (ErrAPIKeyInvalid / ErrBasicCredentialsInvalid); see Authentication for the loader contracts.

Custom authentication strategies implement api.AuthStrategy and register with vef.ProvideAuthStrategy(...) into vef:api:auth_strategies.

Auth at Resource vs Operation Level

// Resource-level: all operations use signature auth
api.NewRPCResource("external/webhook", api.WithAuth(api.SignatureAuth()))

// Operation-level: override per operation
crud.NewCreate[User, UserParams]().Public() // No auth
crud.NewFindPage[User, UserSearch]().RequiredPermission("sys.user.query") // Bearer + permission

Rate Limiting

type RateLimitConfig struct {
Max int // Maximum requests allowed
Period time.Duration // Time window
Key string // Custom rate limit key (optional)
}

Usage via CRUD builder:

crud.NewCreate[User, UserParams]().RateLimit(100, time.Minute)

The built-in rate limiter uses a sliding window, counted per node. An operation that declares no RateLimit of its own receives the engine default, which is user-configurable:

[vef.api.rate_limit]
max = 100 # default when omitted
period = "5m" # default when omitted

An explicit RateLimitConfig with Max <= 0 does not disable limiting: the middleware only honors positive values and falls back to the engine/config default (which is always positive) for anything else — there is no per-operation off switch. The framework's default key includes resource, version, action, resolved client IP, and the principal ID; anonymous requests use the anonymous principal.

Operation auth is carried by Operation.Auth; a public operation resolves to api.AuthStrategyNone, while protected operations carry the selected auth strategy and options.

Engine

The Engine manages resource registration and HTTP routing:

type Engine interface {
Register(resources ...Resource) error // Add resources
Lookup(id Identifier) *Operation // Find operation at runtime
Mount(router fiber.Router) error // Attach to Fiber router
}

Handler Resolution

VEF supports flexible handler signatures through parameter injection:

// Minimal handler
func (r *UserResource) Create(ctx fiber.Ctx) error { ... }

// With auto-injected parameters
func (r *UserResource) Create(ctx fiber.Ctx, db orm.DB, principal *security.Principal) error { ... }

// With typed params
func (r *UserResource) Create(ctx fiber.Ctx, params UserParams, db orm.DB) error { ... }

Parameter Resolution Interfaces

InterfacePurpose
HandlerParamResolverResolves handler params from request context at runtime
FactoryParamResolverResolves handler params once at startup (dependency injection)
HandlerAdapterConverts any handler signature to fiber.Handler
HandlerResolverFinds the handler function on a resource

RouterStrategy is also public for custom HTTP exposure. A strategy declares which api.Kind values it can handle, receives the Fiber router in Setup, and registers each resolved operation in Route.

Error Types

ErrorMeaning
ErrEmptyResourceNameResource name is empty
ErrInvalidResourceNameResource name doesn't match naming rules
ErrResourceNameSlashResource name starts or ends with /
ErrResourceNameDoubleSlashResource name contains //
ErrInvalidResourceKindInvalid resource kind value
ErrInvalidVersionFormatVersion doesn't match v\d+ pattern
ErrEmptyActionNameAction name is empty
ErrInvalidActionNameAction doesn't match kind-specific rules
ErrInvalidParamsTypeParams.Decode target is not a pointer to struct
ErrInvalidMetaTypeMeta.Decode target is not a pointer to struct
ErrUnsupportedBodyEncodingThe X-Body-Encoding header value is not supported
ErrBodyDecodeFailedThe encoded body could not be decoded (malformed base64 or corrupt gzip)
ErrBodyTooLargeThe decoded body exceeds the configured body limit

Params.Decode and Meta.Decode require a pointer to a struct. Passing any other target returns ErrInvalidParamsType or ErrInvalidMetaType.

ErrInvalidRequestParams and ErrInvalidRequestMeta use result.ErrCodeBadRequest (1400) and HTTP status 400. They are returned when RPC form params/meta JSON or REST JSON body parsing fails.

The remaining public surface of the api package — version constants, request headers, the audit event, the auth strategy registry, operation collection types, request helpers, and handler/router extension interfaces — is ledgered symbol by symbol in the Public API Index.

Next Step