apikit

package module
v0.5.2 Latest Latest
Warning

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

Go to latest
Published: May 7, 2026 License: MIT Imports: 9 Imported by: 0

README

apikit

Tests Go Report Card

Go toolkit for building APIs: HTTP handler generation, OpenAPI 3.1 spec generation, Go SDK generation, and runtime utilities.

Modules

Module Import Path Description
root github.com/kausys/apikit Runtime: HTTP responses, errors, validation, logging, sanitization
scanner github.com/kausys/apikit/scanner Go AST scanner for swagger directives
openapi github.com/kausys/apikit/openapi OpenAPI 3.1 spec generation from scanned directives
handler github.com/kausys/apikit/handler HTTP handler code generation from annotated functions
cmd github.com/kausys/apikit/cmd CLI binary (apikit)

Installation

# CLI tool
go install github.com/kausys/apikit/cmd/apikit@latest

# Runtime library (used by generated code)
go get github.com/kausys/apikit@latest

Root Module — Runtime

github.com/kausys/apikit

The root module provides the runtime types and utilities that generated handler code depends on.

HTTP Responses
import "github.com/kausys/apikit"

// Return structured responses from handlers
response := apikit.NewHttpResponse(http.StatusOK, data)
response.WithHeader("X-Request-ID", reqID)
response.WithContentType("application/json")

// Write JSON directly
apikit.WriteJSON(w, data)

// Handle response/error from a handler call
apikit.HandleResponse(w, response, err)
Errors
// Predefined error constructors
apikit.BadRequest("invalid email format")
apikit.NotFound("user not found")
apikit.Unauthorized("invalid token")
apikit.Forbidden("insufficient permissions")
apikit.Conflict("email already exists")
apikit.UnprocessableEntity("validation failed")
apikit.InternalError("database connection failed")
apikit.TooManyRequests("rate limit exceeded")
apikit.ServiceUnavailable("service down")

// With details and chaining
apikit.BadRequest("validation failed").
    WithDetails(fieldErrors).
    WithRequestID(reqID).
    WithCause(err)
Validation
import "github.com/kausys/apikit/validator"

// Validate structs
err := validator.StructCtx(ctx, &payload)

// Register custom validations
validator.RegisterValidation(func(v *validator.Validate) {
    v.RegisterValidation("custom_rule", customFunc)
})

// Implement ValidEnum for enum types
type Status string
func (s Status) IsValid() bool { return s == "active" || s == "inactive" }
Logging
import "github.com/kausys/apikit"

// Set a global logger (slog-style key-value pattern)
apikit.SetLogger(myLogger)

// Check if a logger is configured
if apikit.LoggerEnabled() {
    // ...
}
Sanitization

Sanitize structs for safe logging using struct tags:

type CreateUserRequest struct {
    Email    string `json:"email"`
    Password string `json:"password" log:"sensitive"` // → "[REDACTED]"
    Internal string `json:"-"        log:"-"`          // → omitted
}

// In log calls
logger.Info("request", "payload", apikit.Sanitize(payload))
// Output: {"email": "user@example.com", "password": "[REDACTED]"}
Tag Behavior
log:"-" Field omitted from output
log:"sensitive" Value replaced with [REDACTED]
(none) Value passed through

Supports nested structs, pointers, slices, and embedded structs. Uses json tag names as map keys.

Swagger UI Handler
import "github.com/kausys/apikit/swagger"

//go:embed swagger-ui.zip
var swaggerUIZip []byte

handler, err := swagger.New(swaggerUIZip, swagger.Config{
    BasePath:      "/swagger",
    SpecPath:      "/openapi/specs",
    ResourcesPath: "/openapi/resources",
    Specs: map[string][]byte{
        "Public API":   publicSpec,
        "Internal API": internalSpec,
    },
    DefaultSpec: "Public API",
})

// Option 1: Use with http.ServeMux
handler.Routes(mux)

// Option 2: Use with chi or similar routers
r.Mount("/swagger", http.StripPrefix("/swagger", http.HandlerFunc(handler.ServeUI)))
r.Get("/openapi/specs", handler.ServeSpec)
r.Get("/openapi/resources", handler.ServeResources)
Time Parsing
t, err := apikit.NewTimeFromString("2024-01-15T10:30:00Z")
// Supports: RFC3339, RFC3339Nano, 2006-01-02, 01/02/2006, etc.

Scanner Module

github.com/kausys/apikit/scanner

Parses Go source files to extract swagger directives into structured data.

import "github.com/kausys/apikit/scanner"

s := scanner.New(
    scanner.WithDir("."),
    scanner.WithPattern("./..."),
    scanner.WithIgnorePaths("vendor", "testdata"),
)

if err := s.Scan(); err != nil {
    log.Fatal(err)
}

// Access results
s.Meta       // *MetaInfo — API metadata
s.Structs    // map[string]*StructInfo — models and parameters
s.Routes     // map[string]*RouteInfo — API endpoints
s.Enums      // map[string]*EnumInfo — enum definitions
Swagger Directives
// swagger:meta
// Title: My API
// Version: 1.0.0
// Description: API description
// BasePath: /api/v1
// SecuritySchemes:
//   BearerAuth:
//     type: http
//     scheme: bearer

// swagger:model
type User struct {
    ID    string `json:"id"`
    Email string `json:"email" example:"user@example.com"`
}

// swagger:enum
type Status string
const (
    StatusActive   Status = "active"
    StatusInactive Status = "inactive"
)

// swagger:parameters
type GetUserParams struct {
    // in: path
    UserID string `json:"user_id" validate:"required,uuid"`
}

// swagger:route GET /users/{user_id} users GetUser
// summary: Get a user by ID
// Responses:
//   200: User
//   404: ErrorResponse
// Security:
//   BearerAuth
Multi-Spec Support

Assign elements to specific specs using the spec: directive:

// swagger:route GET /public/health public Health
// spec: public
// summary: Health check

// swagger:route GET /internal/metrics internal Metrics
// spec: internal
// summary: Internal metrics

OpenAPI Module

github.com/kausys/apikit/openapi

Generates OpenAPI 3.1 specifications from scanner output.

import "github.com/kausys/apikit/openapi"

doc, err := openapi.Generate(
    openapi.WithDir("."),
    openapi.WithPattern("./api/..."),
    openapi.WithOutput("openapi.yaml", "yaml"),
    openapi.WithCache(true),
    openapi.WithFlatten(false),
    openapi.WithValidation(true),
    openapi.WithCleanUnused(true),
    openapi.WithEnumRefs(true),
)
Custom Type Mapping

Create .openapi.yaml in your project root to map Go types to OpenAPI types:

custom_types:
  # Short name (backward compatible)
  decimal.Decimal:
    type: string
    format: decimal
    example: "123.45"

  # Fully-qualified path (avoids collisions)
  github.com/shopspring/decimal.Decimal:
    type: string
    format: decimal
    example: "123.45"

Or register programmatically:

import "github.com/kausys/apikit/openapi/generator"

generator.FieldType(decimal.Decimal{}, func(info *generator.TypeInfo) {
    info.Type = "string"
    info.Format = "decimal"
    info.Example = "123.45"
})
Subpackages
Package Purpose
openapi/spec OpenAPI 3.1 type definitions (Schema, Paths, Components, etc.)
openapi/generator Spec generation engine, custom type registry
openapi/cache Incremental caching for large codebases
openapi/sdkgen Go SDK generation from OpenAPI specs
openapi/swagger Swagger UI asset management

Handler Module

github.com/kausys/apikit/handler

Generates HTTP handler wrappers with request parsing, validation, and response handling.

Usage
//go:generate apikit handler gen

type GetUserRequest struct {
    UserID string `path:"id" validate:"required,uuid"`
    Fields string `query:"fields"`
}

type GetUserResponse struct {
    ID   string `json:"id"`
    Name string `json:"name"`
}

// apikit:handler
func GetUser(ctx context.Context, req GetUserRequest) (GetUserResponse, error) {
    return GetUserResponse{ID: req.UserID, Name: "John"}, nil
}
go generate ./...
Routing
// Standard http
mux.HandleFunc("GET /users/{id}", getUserAPIKit(GetUser))

// Fiber
app.Get("/users/:id", getUserAPIKit(GetUser))

// Gin
router.GET("/users/:id", getUserAPIKit(GetUser))

// Echo
e.GET("/users/:id", getUserAPIKit(GetUser))
Parameter Sources
Tag Source
path:"name" URL path parameter
query:"name" Query string
header:"name" HTTP header
cookie:"name" Cookie value
form:"name" Form field / multipart
json:"name" JSON body (auto-detected)
File Uploads
type UploadRequest struct {
    File  *multipart.FileHeader   `form:"file"`
    Files []*multipart.FileHeader `form:"files"`
}
Raw HTTP Access
type StreamRequest struct {
    Request *http.Request
    Writer  http.ResponseWriter
    RawBody []byte
}
Subpackages
Package Purpose
handler/parser Parses Go files for apikit:handler annotations
handler/codegen Generates handler wrapper code from templates
handler/extractors Framework-specific parameter extraction (http, fiber, gin, echo)
handler/types Custom type extractor registry for parameter parsing

CLI Reference

handler gen
apikit handler gen [flags]

Flags:
  -f, --file string        Source file (defaults to $GOFILE)
  -o, --output string      Output file (defaults to <source>_apikit.go)
      --framework string   http, fiber, gin, echo (default "http")
      --force              Regenerate even if unchanged
      --dry-run            Print without writing
  -v, --verbose            Verbose output
openapi gen
apikit openapi gen [flags]

Flags:
  -o, --output string       Output file (default "openapi.yaml")
  -f, --format string       yaml or json (default "yaml")
  -p, --pattern string      Package pattern (default "./...")
  -d, --dir string          Root directory (default ".")
      --no-cache            Disable caching
      --flatten             Inline $ref schemas
      --validate            Validate output
      --ignore strings      Paths to ignore
      --clean-unused        Remove unreferenced schemas
      --multi-specs         Generate multiple specs
      --spec string         Generate specific spec only
      --no-default          Skip default spec
      --enum-refs           Enums as $ref

apikit openapi clean        Remove cache
apikit openapi status       Show cache stats
sdk gen
apikit sdk gen <config.sdkgen.yaml> -o <output-dir> [flags]

Flags:
  -o, --output string     Output directory (required)
      --provider string   Override provider name
swagger
apikit swagger download [flags]

Flags:
  -o, --output string    Output directory (default ".")
  -v, --version string   Version (default: latest)
      --with-defaults    Include default initializer (default true)
      --simple           Single-spec mode

apikit swagger version   Show latest version

Development

make setup          # Install golangci-lint v2 + lefthook
make build          # Build all modules
make test           # Run all tests
make test-coverage  # Tests with coverage
make lint           # Lint all modules
make lint-fix       # Auto-fix lint issues
make fmt            # Format all modules
make tidy           # go mod tidy all modules
make ci             # Full CI check (fmt + lint + tidy + test)
make install        # Install apikit binary locally

License

MIT License — see LICENSE for details.

Documentation

Overview

Package apikit provides runtime helpers for APIKit-generated code

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrInternalServer is a generic internal server error
	ErrInternalServer = InternalError("internal server error")

	// ErrSomethingWentWrong is a generic internal server error
	ErrSomethingWentWrong = InternalError("something went wrong")

	// ErrNotImplemented indicates the feature is not implemented
	ErrNotImplemented = NotImplemented("not implemented")

	// ErrUnauthorized indicates authentication is required
	ErrUnauthorized = Unauthorized("authentication required")

	// ErrForbidden indicates access is denied
	ErrForbidden = Forbidden("access denied")

	// ErrInvalidRequest indicates the request is invalid
	ErrInvalidRequest = BadRequest("invalid request")

	// ErrTooManyRequests indicates the client has exceeded the rate limit
	ErrTooManyRequests = TooManyRequests("too many requests")
)
View Source
var CommonTimeFormats = []string{
	time.RFC3339,
	"2006-01-02T15:04:05",
	"2006-01-02T15:04:05.999",
	"2006-01-02T15:04:05.999Z",
	"2006-01-02T15:04:05.999-07:00",
	"2006-01-02 15:04:05",
	"2006-01-02",
}

CommonTimeFormats are the formats tried by NewTimeFromString

Functions

func HandleError

func HandleError(w http.ResponseWriter, err error)

HandleError handles errors with custom status codes

func HandleResponse

func HandleResponse(w http.ResponseWriter, response any, err error)

HandleResponse handles both the response and error from a handler This is the main function used by generated code

func LoggerEnabled added in v0.3.3

func LoggerEnabled() bool

LoggerEnabled returns true if a real (non-noop) logger has been set.

func NewTimeFromString

func NewTimeFromString(s string) (time.Time, error)

NewTimeFromString parses a time string using common formats This function is used by APIKit-generated code to parse time.Time fields

Supported formats (tried in order):

  • RFC3339: "2006-01-02T15:04:05Z07:00"
  • "2006-01-02T15:04:05"
  • "2006-01-02T15:04:05.999"
  • "2006-01-02T15:04:05.999Z"
  • "2006-01-02T15:04:05.999-07:00"
  • "2006-01-02 15:04:05"
  • "2006-01-02"

Returns the parsed time.Time or an error if no format matches

func Sanitize added in v0.3.3

func Sanitize(v any) any

Sanitize processes a value for safe logging by respecting struct field tags:

  • `log:"-"` omits the field entirely
  • `log:"sensitive"` replaces the value with "[REDACTED]"
  • No tag passes the value through unchanged

For non-struct types, the value is returned as-is. Map keys use the json tag name (falling back to the field name).

func SetLogger added in v0.3.1

func SetLogger(l Logger)

SetLogger overrides the global logger used by all generated handlers.

func WriteJSON

func WriteJSON(w http.ResponseWriter, data any)

WriteJSON writes a JSON response with default 200 OK status

Types

type Error

type Error struct {
	// HTTP status code
	Code int `json:"code"`

	// Semantic error code for client handling
	ErrorCode string `json:"errorCode,omitempty"`

	// Human-readable error message
	Message string `json:"message"`

	// Additional error details
	Details any `json:"details,omitempty"`

	// Request ID for correlation
	RequestID string `json:"requestId,omitempty"`
	// contains filtered or unexported fields
}

Error represents an API error with an HTTP status code

func BadRequest

func BadRequest(message string) *Error

BadRequest creates a 400 error

func Conflict

func Conflict(message string) *Error

Conflict creates a 409 error

func Forbidden

func Forbidden(message string) *Error

Forbidden creates a 403 error

func GatewayTimeout

func GatewayTimeout(message string) *Error

GatewayTimeout creates a 504 error

func InternalError

func InternalError(message string) *Error

InternalError creates a 500 error

func NewError

func NewError(code int, message string) *Error

NewError creates a new API error with the given status code and message

func NewErrorWithDetails

func NewErrorWithDetails(code int, message string, details any) *Error

NewErrorWithDetails creates a new API error with additional details

func NewErrorf

func NewErrorf(code int, format string, args ...any) *Error

NewErrorf creates a new API error with a formatted message

func NotAcceptable

func NotAcceptable(message string) *Error

NotAcceptable creates a 406 error

func NotFound

func NotFound(resource string) *Error

NotFound creates a 404 error

func NotImplemented

func NotImplemented(message string) *Error

NotImplemented creates a 501 error

func ServiceUnavailable

func ServiceUnavailable(message string) *Error

ServiceUnavailable creates a 503 error

func TooManyRequests added in v0.3.1

func TooManyRequests(message string) *Error

TooManyRequests creates a 429 error

func Unauthorized

func Unauthorized(message string) *Error

Unauthorized creates a 401 error

func UnprocessableEntity

func UnprocessableEntity(message string) *Error

UnprocessableEntity creates a 422 error

func WrapError

func WrapError(code int, message string, cause error) *Error

WrapError wraps an existing error with an API error

func (*Error) Error

func (e *Error) Error() string

Error implements the error interface

func (*Error) StatusCode

func (e *Error) StatusCode() int

StatusCode returns the HTTP status code for this error

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap returns the underlying error for error chain support

func (*Error) WithCause

func (e *Error) WithCause(cause error) *Error

WithCause wraps an underlying error

func (*Error) WithDetails

func (e *Error) WithDetails(details any) *Error

WithDetails adds details to the error

func (*Error) WithRequestID

func (e *Error) WithRequestID(requestID string) *Error

WithRequestID adds request ID to the error

type HttpResponse

type HttpResponse struct {
	StatusCode  int               `json:"statusCode"`
	Body        any               `json:"body"`
	Headers     map[string]string `json:"headers"`
	ContentType string            `json:"contentType"`
}

HttpResponse represents an HTTP response with status code, body, headers, and content type

func NewHttpResponse

func NewHttpResponse(statusCode int, body any) *HttpResponse

NewHttpResponse creates a new HttpResponse with the given status code and body

func (*HttpResponse) WithContentType

func (r *HttpResponse) WithContentType(contentType string) *HttpResponse

WithContentType sets a custom content type

func (*HttpResponse) WithHeader

func (r *HttpResponse) WithHeader(key, value string) *HttpResponse

WithHeader adds a single header to the response

func (*HttpResponse) WithHeaders

func (r *HttpResponse) WithHeaders(headers map[string]string) *HttpResponse

WithHeaders adds custom headers to the response

type Logger added in v0.3.1

type Logger interface {
	Error(ctx context.Context, msg string, args ...any)
}

Logger defines the interface for logging errors in generated handlers. Implementations should follow the slog-style key-value pattern for structured logging.

func GetLogger added in v0.3.1

func GetLogger() Logger

GetLogger returns the current global logger.

Directories

Path Synopsis
cmd module
handler module
openapi module
scanner module
Package swagger provides an HTTP handler to serve Swagger UI with embedded OpenAPI specs.
Package swagger provides an HTTP handler to serve Swagger UI with embedded OpenAPI specs.
Package types provides a type system for converting string values to Go types during request parsing.
Package types provides a type system for converting string values to Go types during request parsing.

Jump to

Keyboard shortcuts

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