Validation
ShiftAPI uses go-playground/validator for request validation. Validation rules are specified using validate struct tags and are automatically reflected in the generated OpenAPI schema.
Basic validation
Section titled “Basic validation”type CreateUser struct { Name string `json:"name" validate:"required"` Email string `json:"email" validate:"required,email"` Age int `json:"age" validate:"gte=0,lte=150"`}When validation fails, ShiftAPI returns a 422 Unprocessable Entity response with structured errors:
{ "message": "validation failed", "errors": [ { "field": "Name", "message": "this field is required" }, { "field": "Email", "message": "must be a valid email address" } ]}Required fields
Section titled “Required fields”Non-pointer fields (string, int, etc.) are always marked as required in the OpenAPI schema — they can’t be absent in Go. Pointer fields (*string, *int) are optional by default and only marked required if they have validate:"required".
type CreateUser struct { Name string `json:"name"` // required in schema (non-pointer) Age int `json:"age"` // required in schema (non-pointer) Nickname *string `json:"nickname"` // optional in schema (pointer) Bio *string `json:"bio" validate:"required"` // required in schema (pointer + validate)}This applies to nested structs too — fields inside nested types follow the same rules.
Note that validate:"required" on a non-pointer string is still useful for request types — it enforces that the value is non-empty at runtime, not just present. A string field will always be in the JSON, but validate:"required" rejects "".
Supported rules
Section titled “Supported rules”| Rule | Description | OpenAPI mapping |
|---|---|---|
required | Field must be present | required array |
email | Valid email address | format: "email" |
url, uri | Valid URI | format: "uri" |
uuid | Valid UUID | format: "uuid" |
min=N | Minimum value/length/items | minimum / minLength / minItems |
max=N | Maximum value/length/items | maximum / maxLength / maxItems |
gte=N | Greater than or equal | minimum |
lte=N | Less than or equal | maximum |
gt=N | Greater than (exclusive) | exclusiveMinimum |
lt=N | Less than (exclusive) | exclusiveMaximum |
len=N | Exact length | minLength + maxLength |
oneof=a b c | Must be one of values | enum |
Response types
Section titled “Response types”Validation is only enforced on request input — response types are not validated at runtime. However, validate tags on response structs still enrich the generated OpenAPI schema, so your TypeScript types and API docs reflect the constraints.
type UserResponse struct { Name string `json:"name"` Email string `json:"email" validate:"email"` // not enforced, but appears in the OpenAPI spec}Validation on query and header fields
Section titled “Validation on query and header fields”Validation rules work on all input sources — json, query, header, and form fields. For example, validate:"required" on a header-tagged field returns 422 if the header is present but empty, and validate:"oneof=json xml" constrains accepted values. Parse errors (e.g. sending "abc" for an int header) return 400; validation failures return 422.
type AuthSearch struct { Token string `header:"Authorization" validate:"required"` Format string `header:"Accept" validate:"oneof=json xml"` Q string `query:"q" validate:"required"`}Custom validator
Section titled “Custom validator”You can provide your own validator instance with custom rules:
import "github.com/go-playground/validator/v10"
v := validator.New()v.RegisterValidation("custom_rule", customRuleFunc)
api := shiftapi.New(shiftapi.WithValidator(v))