agentleFS
jkanindajkaninda/okapi-skills/validation/SKILL.md

validation

Skill3 starsChanged 3 months agoMITView on GitHub

Validation is declarative: struct tags drive both runtime enforcement and OpenAPI schema generation. Validation runs automatically inside c.Bind and inside the typed handler wrappers (okapi.Handle, okapi.H, okapi.HandleIO, okapi.HandleO). For where values come from (JSON body, query, path, header, cookie, form), see the request_binding/ skill. Slices: enum, const, format, pattern, contains, and notContains apply to each element of a []string. Failures are reported per index — element [2]: .... Empty values: those same tags skip empty strings. Combine with required:"true" to…

20 other projects use a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

1 rule. 0 are common, 0 are distinctive, and 1 is very specific to this project.

  • 0

    Common

    Most of the 20 edited versions we read keep these.

  • 0

    Distinctive

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    All formats apply to string fields and to each element of a []string.

    line 74

“Common” is measured: kept by at least 6 in 10 of the 20 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Okapi Validation

Validation is declarative: struct tags drive both runtime enforcement and OpenAPI schema generation. Validation runs automatically inside c.Bind and inside the typed handler wrappers (okapi.Handle, okapi.H, okapi.HandleIO, okapi.HandleO).

For where values come from (JSON body, query, path, header, cookie, form), see the request_binding/ skill.

Constraint Tags

TagApplies toDescription
required:"true"anyField must be present and non-zero
default:"v"anyAssigned when the field is missing/empty
min:"5"number≥ 5
max:"100"number≤ 100
exclusiveMin:"0"number> 0
exclusiveMax:"100"number< 100
multipleOf:"5"numberDivisible by 5
minLength:"3"stringLength ≥ 3
maxLength:"50"stringLength ≤ 50
pattern:"^[A-Z]+$"string / []stringRegular expression
format:"email"string / []stringFormat validation (table below)
enum:"a,b,c"string / []stringOne of the listed values
const:"active"string / []stringMust equal a fixed value (JSON Schema const in OAS 3.1)
contains:"@"string / []stringMust contain the substring
notContains:" "string / []stringMust not contain the substring
minItems:"2"sliceAt least 2 items
maxItems:"5"sliceAt most 5 items
uniqueItems:"true"sliceNo duplicates
minProperties:"1"mapAt least 1 entry
maxProperties:"10"mapAt most 10 entries

Slices: enum, const, format, pattern, contains, and notContains apply to each element of a []string. Failures are reported per index — element [2]: ....

Empty values: those same tags skip empty strings. Combine with required:"true" to also enforce presence.

Conditional Required Tags

Requiredness that depends on sibling fields in the same struct. Referenced fields use their Go field name, not the JSON name.

TagDescription
requiredIf:"Type card"Required when sibling Type equals card
requiredWith:"Pass"Required when any listed sibling (comma-separated) is non-empty
requiredWithout:"Email"Required when any listed sibling (comma-separated) is empty
type Payment struct {
    Type    string `json:"type"`
    Card    string `json:"card"    requiredIf:"Type card"`
    Pass    string `json:"pass"`
    Confirm string `json:"confirm" requiredWith:"Pass"`
    Email   string `json:"email"`
    Phone   string `json:"phone"   requiredWithout:"Email"`
}

Documentation-Only Tags

These shape the OpenAPI schema; they are not enforced at runtime.

TagDescription
description:"..."Property description
doc:"..."Alternative description tag
example:"v"Example value
deprecated:"true"Mark the property deprecated
hidden:"true"Omit the field from the generated schema
readOnly:"true"Returned by the server, not sent by clients
writeOnly:"true"Sent by clients, not returned
nullable:"true"Nullable property (pointer fields are nullable automatically)

Formats

All formats apply to string fields and to each element of a []string.

Date & time

FormatDescription
dateYYYY-MM-DD
date-timeRFC3339 timestamp
timeRFC3339 full-time (15:04:05Z)
durationGo duration (1h30m, 300ms)

Network, web & identifiers

FormatDescription
emailValid email address
hostnameValid hostname
ipv4 / ipv6IP addresses
macMAC address
cidrCIDR notation (192.168.1.0/24)
uri / uri-referenceAny URI / relative reference allowed
urlAbsolute http/https URL
uuid / ulidUUID / ULID
e164 / phoneE.164 phone number (+14155552671)
credit-cardLuhn-valid card number
semverSemantic version (1.2.3-alpha.1)
json-pointerRFC 6901 JSON Pointer
byte / base64Base64-encoded value
base64urlURL-safe Base64 (padded or unpadded)
jwtJSON Web Token (three base64url segments)
portTCP/UDP port (1–65535)

String content

FormatDescription
alphaLetters only
alphanumericLetters and digits
numericNumeric string (123, -12.5)
asciiASCII only
lowercase / uppercaseCase constraint
slugURL slug (my-post-123)
hexcolor#RGB or #RRGGBB
jsonSyntactically valid JSON string

Geo & time zones

FormatDescription
latitudeDecimal latitude, -90…90
longitudeDecimal longitude, -180…180
timezoneIANA time zone (America/New_York)

Custom pattern

Phone string `json:"phone" format:"regex" pattern:"^\\+?[1-9]\\d{1,14}$"`

Example

type CreateUserRequest struct {
    Email    string            `json:"email" required:"true" format:"email" example:"user@example.com"`
    Password string            `json:"password" minLength:"8" writeOnly:"true" description:"User password"`
    Age      int               `json:"age" exclusiveMin:"0" max:"120" default:"18"`
    Website  string            `json:"website" format:"url"`
    Kind     string            `json:"kind" const:"user"`
    Roles    []string          `json:"roles" minItems:"1" uniqueItems:"true" enum:"admin,editor,viewer"`
    Metadata map[string]string `json:"metadata" minProperties:"1" maxProperties:"10"`
    ID       string            `json:"id" format:"uuid" readOnly:"true"`
    Internal string            `json:"-" hidden:"true"`
}

Five Ways to Validate

1. c.Bind() — bind and validate inside the handler

o.Post("/users", func(c *okapi.Context) error {
    var req CreateUserRequest
    if err := c.Bind(&req); err != nil {
        return c.ErrorBadRequest(err)
    }
    return c.JSON(http.StatusOK, req)
})

2. okapi.Handle() — typed input, manual response

o.Post("/books", okapi.Handle(func(c *okapi.Context, book *Book) error {
    book.ID = nextID()
    return c.Created(book)
}),
    okapi.DocRequestBody(&Book{}),
    okapi.DocResponse(&Book{}),
)

3. okapi.H() — shorthand for Handle

o.Get("/books/{id:int}", okapi.H(func(c *okapi.Context, in *BookDetailInput) error {
    b := find(in.ID)
    if b == nil {
        return c.AbortNotFound("Book not found")
    }
    return c.OK(b)
})).WithInput(&BookDetailInput{})

4. okapi.HandleIO() — typed input and typed output

type BookEditInput struct {
    ID   int  `json:"id" path:"id" required:"true"`
    Body Book `json:"body"`
}
type BookOutput struct {
    Status int
    Body   Book
}

o.Put("/books/{id:int}", okapi.HandleIO(func(c *okapi.Context, in *BookEditInput) (*BookOutput, error) {
    b := update(in.ID, in.Body)
    if b == nil {
        return nil, c.AbortNotFound("Book not found")
    }
    return &BookOutput{Body: *b}, nil
})).WithIO(&BookEditInput{}, &BookOutput{})

5. okapi.HandleO() — typed output only

type BooksResponse struct {
    Body []Book `json:"books"`
}

o.Get("/books", okapi.HandleO(func(c *okapi.Context) (*BooksResponse, error) {
    return &BooksResponse{Body: books}, nil
})).WithOutput(&BooksResponse{})

The output struct follows the body-style convention (Status, Body, header/cookie-tagged fields). The response format comes from the client's Accept header, defaulting to JSON.

Schema Helpers

HelperPurpose
okapi.DocRequestBody(&T{})Document the request body schema
okapi.DocResponse(&T{}) / okapi.DocResponse(201, &T{})Document a response schema
route.WithInput(&T{})Input schema (pairs with okapi.H / Handle)
route.WithOutput(&T{})Output schema (pairs with HandleO)
route.WithIO(&In{}, &Out{})Both (pairs with HandleIO)

Returning Validation Errors

return c.AbortValidationErrors([]okapi.ValidationError{
    {Field: "email", Message: "must be a valid email", Value: req.Email},
    {Field: "age",   Message: "must be >= 18"},
}, "validation failed")

AbortValidationErrors always uses the ValidationErrorResponse shape (an ErrorResponse plus an errors array), regardless of the configured error handler. Use AbortValidationErrorsWithProblemDetail for the RFC 7807 form.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.