Skip to content

Operations

MeGaNeKo edited this page Mar 20, 2026 · 1 revision

Operations

Registering Operations

neoma.Register wires an endpoint by analyzing input type I and output type O at registration time.

import (
    "github.com/MeGaNeKoS/neoma/core"
    "github.com/MeGaNeKoS/neoma/neoma"
)

neoma.Register[InputType, OutputType](api, core.Operation{
    OperationID: "get-item",
    Method:      http.MethodGet,
    Path:        "/items/{id}",
    Summary:     "Get an item",
    Tags:        []string{"Items"},
    Errors:      []int{http.StatusNotFound},
}, func(ctx context.Context, input *InputType) (*OutputType, error) {
    // handler logic
})

Convenience Functions

Shorthand helpers with auto-generated operation IDs and summaries:

neoma.Get[I, O](api, "/items/{id}", handler)
neoma.Post[I, O](api, "/items", handler)
neoma.Put[I, O](api, "/items/{id}", handler)
neoma.Patch[I, O](api, "/items/{id}", handler)
neoma.Delete[I, O](api, "/items/{id}", handler)
neoma.Head[I, O](api, "/items/{id}", handler)

Each accepts optional callbacks to customize the operation:

neoma.Get[I, O](api, "/items", handler,
    func(op *core.Operation) {
        op.Tags = []string{"Items"}
        op.Description = "List all items"
    },
)

neoma.OperationTags is a shortcut for setting tags:

neoma.Get[I, O](api, "/items", handler, neoma.OperationTags("Items"))

Input Struct

The input struct defines parameters and the request body through struct tags.

Path Parameters

type GetItemInput struct {
    ID int `path:"id" doc:"Item ID"`
}
// Matches: GET /items/{id}

Query Parameters

type ListInput struct {
    Page    int    `query:"page" minimum:"1" default:"1" doc:"Page number"`
    PerPage int    `query:"per_page" minimum:"1" maximum:"100" default:"20"`
    Search  string `query:"q" doc:"Search query"`
}

Header Parameters

type AuthInput struct {
    Token string `header:"Authorization" required:"true" doc:"Bearer token"`
}

Cookie Parameters

type SessionInput struct {
    SessionID string `cookie:"session_id" doc:"Session cookie"`
}

Request Body

A field named Body is treated as the request body:

type CreateItemInput struct {
    Body struct {
        Name  string  `json:"name" minLength:"1" maxLength:"100" doc:"Item name"`
        Price float64 `json:"price" minimum:"0" doc:"Item price"`
    }
}

Or reference a named type:

type CreateItemBody struct {
    Name  string  `json:"name" minLength:"1" doc:"Item name"`
    Price float64 `json:"price" minimum:"0" doc:"Item price"`
}

type CreateItemInput struct {
    Body CreateItemBody
}

Multipart Form Data

Use the form tag instead of json on Body fields to declare multipart form fields. The request body content type is automatically set to multipart/form-data.

For file uploads, use core.FormFile (single file) or []core.FormFile (multiple files).

type UploadInput struct {
    Body struct {
        Name   string        `form:"name"`
        Avatar core.FormFile `form:"avatar"`
    }
}

Multiple file uploads:

type BatchUploadInput struct {
    Body struct {
        Label string           `form:"label"`
        Files []core.FormFile  `form:"files"`
    }
}

ParamWrapper works on form fields the same way it works on query and header parameters, so custom types with Unmarshal methods are supported.

Combining Parameters

Parameters from different locations can be combined in one struct:

type UpdateItemInput struct {
    ID     int  `path:"id" doc:"Item ID"`
    DryRun bool `query:"dry_run" doc:"Simulate the update"`
    Body struct {
        Name string `json:"name" doc:"New name"`
    }
}

Embedding for Reuse

type IDPath struct {
    ID int `path:"id" doc:"Item ID"`
}

type GetItemInput struct {
    IDPath
}

type UpdateItemInput struct {
    IDPath
    Body UpdateItemBody
}

Output Struct

Response Body

A field named Body is serialized as the response body:

type GetItemOutput struct {
    Body struct {
        ID   int    `json:"id" doc:"Item ID"`
        Name string `json:"name" doc:"Item name"`
    }
}

Response Headers

Fields with the header tag set response headers:

type CreateItemOutput struct {
    Location string `header:"Location" doc:"URL of the created item"`
    Body struct {
        ID int `json:"id"`
    }
}

Custom Status Code

A field named Status of type int overrides the default status code:

type CreateOutput struct {
    Status int
    Body   MyBody
}

Set Status in your handler to control the response code (e.g., 201, 202).

Streaming Responses

Use a func(core.Context, core.API) body for streaming:

type StreamOutput struct {
    Body func(ctx core.Context, api core.API)
}

Validation Tags

Neoma validates request parameters and bodies using struct tags. Validation failures return 422 Unprocessable Entity.

String Validation

Tag Example Description
minLength minLength:"1" Minimum string length
maxLength maxLength:"100" Maximum string length
pattern pattern:"^[a-z]+$" Regex pattern
patternDescription patternDescription:"lowercase letters" Human-readable pattern description
enum enum:"draft,published,archived" Allowed values
format format:"email" Format hint (uri, email, date-time, uuid, etc.)

Numeric Validation

Tag Example Description
minimum minimum:"0" Minimum value (inclusive)
maximum maximum:"100" Maximum value (inclusive)
exclusiveMinimum exclusiveMinimum:"0" Minimum value (exclusive)
exclusiveMaximum exclusiveMaximum:"100" Maximum value (exclusive)
multipleOf multipleOf:"5" Value must be a multiple of this

Array Validation

Tag Example Description
minItems minItems:"1" Minimum array length
maxItems maxItems:"50" Maximum array length
uniqueItems uniqueItems:"true" All items must be unique

Object Validation

Tag Example Description
minProperties minProperties:"1" Minimum number of properties
maxProperties maxProperties:"10" Maximum number of properties

General Tags

Tag Example Description
required required:"true" Field is required
default default:"10" Default value
doc doc:"Item name" Description for the OpenAPI spec
example example:"My Item" Example value
deprecated deprecated:"true" Mark as deprecated
readOnly readOnly:"true" Read-only field
writeOnly writeOnly:"true" Write-only field
nullable nullable:"false" Override nullability
hidden hidden:"true" Exclude from public spec

Full Example

type CreateUserInput struct {
    Body struct {
        Name     string   `json:"name" minLength:"1" maxLength:"255" doc:"Full name" example:"Jane Doe"`
        Email    string   `json:"email" format:"email" doc:"Email address"`
        Age      int      `json:"age" minimum:"0" maximum:"150" doc:"Age in years"`
        Role     string   `json:"role" enum:"admin,user,guest" default:"user" doc:"User role"`
        Tags     []string `json:"tags" maxItems:"10" uniqueItems:"true" doc:"User tags"`
        Password string   `json:"password" minLength:"8" writeOnly:"true" doc:"Password"`
    }
}

Resolver Interface

Resolvers run after parameter parsing and body validation but before the handler. They allow custom validation or data loading that depends on the parsed input.

There are two resolver interfaces, both defined in core/:

// Resolver receives the request context.
type Resolver interface {
    Resolve(ctx Context) []error
}

// ResolverWithPath also receives a path buffer for location-aware errors.
type ResolverWithPath interface {
    Resolve(ctx Context, prefix *PathBuffer) []error
}

Any field in the input struct (or the input struct itself) that implements either interface will be called automatically. Returned errors are collected and, if any are present, a 422 response is sent before the handler runs.

Example

type AuthInput struct {
    Token string `header:"Authorization" required:"true"`
    UserID int   // populated by resolver
}

func (a *AuthInput) Resolve(ctx core.Context) []error {
    user, err := validateToken(a.Token)
    if err != nil {
        return []error{errors.ErrorUnauthorized("invalid token")}
    }
    a.UserID = user.ID
    return nil
}

Operation Struct Fields

Field Type Description
Method string HTTP method (GET, POST, PUT, DELETE, etc.)
Path string URL path pattern with {param} placeholders
OperationID string Unique operation identifier for the OpenAPI spec
Summary string Short one-line summary
Description string Longer description (supports markdown)
Tags []string Tags for grouping in the docs UI
DefaultStatus int Success status code (default: 200 for GET, 201 for POST if body present)
Errors []int Error status codes this operation may return
ErrorHeaders map[string]*Param Headers added to all error responses
ErrorResponses map[int]*ErrorResponseConfig Per-status error response configuration
MaxBodyBytes int64 Maximum request body size
BodyReadTimeout time.Duration Timeout for reading the request body
SkipValidateParams bool Skip parameter validation
SkipValidateBody bool Skip body validation
RejectUnknownQueryParameters bool Reject unknown query parameters for this operation
Hidden bool Exclude from public spec (included in internal spec)
Middlewares Middlewares Operation-level middleware
Metadata map[string]any Arbitrary metadata (accessible in middleware)
Deprecated bool Mark as deprecated in the spec
Security []map[string][]string Security requirements

Clone this wiki locally