-
Notifications
You must be signed in to change notification settings - Fork 0
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
})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"))The input struct defines parameters and the request body through struct tags.
type GetItemInput struct {
ID int `path:"id" doc:"Item ID"`
}
// Matches: GET /items/{id}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"`
}type AuthInput struct {
Token string `header:"Authorization" required:"true" doc:"Bearer token"`
}type SessionInput struct {
SessionID string `cookie:"session_id" doc:"Session cookie"`
}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
}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.
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"`
}
}type IDPath struct {
ID int `path:"id" doc:"Item ID"`
}
type GetItemInput struct {
IDPath
}
type UpdateItemInput struct {
IDPath
Body UpdateItemBody
}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"`
}
}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"`
}
}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).
Use a func(core.Context, core.API) body for streaming:
type StreamOutput struct {
Body func(ctx core.Context, api core.API)
}Neoma validates request parameters and bodies using struct tags. Validation failures return 422 Unprocessable Entity.
| 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.) |
| 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 |
| Tag | Example | Description |
|---|---|---|
minItems |
minItems:"1" |
Minimum array length |
maxItems |
maxItems:"50" |
Maximum array length |
uniqueItems |
uniqueItems:"true" |
All items must be unique |
| Tag | Example | Description |
|---|---|---|
minProperties |
minProperties:"1" |
Minimum number of properties |
maxProperties |
maxProperties:"10" |
Maximum number of properties |
| 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 |
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"`
}
}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.
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
}| 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 |