Skip to content

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.

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" }
]
}

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 "".

RuleDescriptionOpenAPI mapping
requiredField must be presentrequired array
emailValid email addressformat: "email"
url, uriValid URIformat: "uri"
uuidValid UUIDformat: "uuid"
min=NMinimum value/length/itemsminimum / minLength / minItems
max=NMaximum value/length/itemsmaximum / maxLength / maxItems
gte=NGreater than or equalminimum
lte=NLess than or equalmaximum
gt=NGreater than (exclusive)exclusiveMinimum
lt=NLess than (exclusive)exclusiveMaximum
len=NExact lengthminLength + maxLength
oneof=a b cMust be one of valuesenum

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 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"`
}

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))