AnnouncementsIntroducing Arkoren

    Menu

    Describing Operations

    Align typed requests and responses with explicit parameters, errors, examples, and security declarations.

    What's on this page
    1. Reuse a typed request schema while keeping runtime decoding and validation explicit.
    2. Declare parameters, response media and headers, input problems, and request examples.
    3. Describe authentication requirements without confusing declarations with enforcement.
    4. Recognize schema export limits and catch invalid declarations before deployment.
    On this page

    A client needs to know what to send and how to interpret both success and failure. Operation callbacks record that contract beside your handler registration. Reuse the handler’s input schema and output type where possible, but keep decoding, validation, and response writing explicit: declarations do not bind or execute those steps for you.

    These examples assume an existing routes router and the Greeting output type from the overview. They use net/http, arkoren.dev/core/http/request, arkoren.dev/core/http/response, arkoren.dev/core/http/router, and arkoren.dev/foundation/validate.

    Describe Input

    To let a caller choose the greeting’s name, define a narrow request type and its validation schema:

    type GreetingInput struct {
    	Name string `json:"name"`
    }
    
    var greetingInput = validate.Struct(func(object *validate.StructBuilder[GreetingInput]) {
    	object.Field("Name", func(name *validate.StringBuilder[string]) {
    		name.Required()
    	})
    })

    The handler must actually decode and validate that input before producing its typed response:

    func createGreeting(writer http.ResponseWriter, incoming *http.Request) error {
    	input, err := request.ParseJSON(incoming, greetingInput)
    	if err != nil {
    		return err
    	}
    
    	return response.JSON(writer, http.StatusOK, Greeting{Message: "Hello, " + input.Name})
    }

    ParseJSON returns the parsed, validated input, with schema defaults applied. An invalid request returns an error instead of reaching the success response. Now reuse the schema in the operation description:

    routes.Post("/greeting", createGreeting, func(operation *router.OperationBuilder) {
    	operation.ID("createGreeting")
    	operation.JSONBody(greetingInput).BodyRequired(true)
    	operation.JSONResponse(http.StatusOK, validate.Infer[Greeting]())
    })

    JSONBody describes application/json; use Body(media, schema) for another representation. Body presence is optional unless you declare BodyRequired(true). That flag is separate from a schema’s required properties, and neither declaration rejects requests by itself.

    Describe Failures

    The success declaration does not discover the errors returned by ParseJSON. Add its known input problems inside the same registration callback:

    operation.Problem(request.ErrInvalidJSON)
    operation.Problem(request.ErrValidationFailed)
    operation.Problem(request.ErrBodyTooLarge)

    These describe malformed JSON at 400, decoding or validation violations at 422, and an oversized body at 413. Operational failures can also escape parsing; this list is not a claim that every failure has one of those identities. See request validation for the runtime boundary.

    Problem accepts problem.Details and describes the router’s negotiated RFC 9457 wire response at its status: application/problem+json, application/json, and text/plain. A zero status becomes 500. The schema does not expose the Go value’s private fields or error cause. Declare application and middleware problems explicitly, matching your actual error mapping.

    Add Parameters

    For a route such as /greetings/{id}, describe an identifier and optional inputs independently of the body. Inside its operation callback:

    operation.Parameter("id", "path", router.RawSchema(`{"type":"integer","minimum":1}`))
    operation.Parameter("language", "query", router.RawSchema(`{"type":"string"}`))
    operation.Parameter("X-Request-ID", "header", router.RawSchema(`{"type":"string"}`))

    The supported locations are path, query, header, and cookie. Path parameters are always required; all other parameters are optional in the document. There is no setting to require a non-path parameter or choose its serialization style. Schema constraints do not change these limits or parse the incoming value.

    Export checks path parameter names against the final path, including group prefixes. Undescribed single-segment wildcards receive required string parameters; an explicit declaration supplies the schema instead. Duplicate names within one location fail export, with case-insensitive comparison for header parameters.

    Describe Responses

    Choose declarations that match what the handler writes. JSONResponse describes a JSON body; Response describes a status without adding a body. For a redirect, declare its status and destination header:

    operation.Response(http.StatusSeeOther, "Continue at the returned location")
    operation.ResponseHeader(http.StatusSeeOther, "Location", router.RawSchema(`{"type":"string","format":"uri-reference"}`))

    Statuses must be integers from 100 through 599; there are no status-range or default response declarations. An empty description uses standard HTTP status text. Response preserves existing bodies and headers at that status; it does not remove them to make a previously declared response bodyless.

    For a binary download, describe the media type rather than inventing a JSON body:

    operation.MediaResponse(http.StatusOK, "application/octet-stream", router.RawSchema(`{"type":"string","format":"binary"}`))
    operation.ResponseHeader(http.StatusOK, "Content-Disposition", router.RawSchema(`{"type":"string"}`))

    Different media types at one status describe alternative representations; repeating a media type replaces its representation. Request and response media types must parse as a type/subtype. These declarations do not select an encoder, set headers, negotiate content, or validate output. Check the actual behavior of downloads and other integrations, including streaming responses.

    Response header names match case-insensitively within one status. A later x-request-id replaces an earlier X-Request-ID, including its exported spelling and schema; other statuses are unaffected. Invalid header names fail export.

    Request Examples

    To show clients a valid greeting request, declare the body representation before adding its named example:

    operation.JSONBody(greetingInput).BodyRequired(true)
    operation.Example("application/json", "personalGreeting", GreetingInput{Name: "Ada"})

    The example is for the request body, not a response. Its JSON encoding is captured immediately, but it is not validated against the schema. Encoding failures become export errors. Adding an example before its media type records an error even if you declare that body representation later.

    Declare Security

    If your routes enforce bearer authentication, name that scheme when exporting:

    document, err := routes.OpenAPI("Greeting API", "1.0.0", func(definition *router.OpenAPIBuilder) {
    	definition.SecurityScheme("bearer", router.RawSchema(`{"type":"http","scheme":"bearer"}`))
    })

    Then reference it in each protected route’s operation callback:

    operation.Security("bearer")

    This declares a requirement; it does not install authentication or authorization. Configure the actual authentication policy separately and declare its relevant error responses. Middleware does not supply schemes or requirements automatically. Pass the same document configuration to OpenAPIHandler if you also serve the JSON.

    The supported schemes are HTTP, API-key, and mutual TLS. HTTP requires a valid scheme token. API-key requires a nonempty name and an in location of query, header, or cookie; a header name must be an HTTP token. For example:

    {
      "type": "apiKey",
      "name": "X-API-Key",
      "in": "header"
    }

    OAuth flows and OpenID Connect scheme definitions are unsupported. Scheme names use letters, digits, ., _, or -; unsupported fields are rejected except x- extensions. Standard supported fields must be strings.

    Every referenced scheme must be declared. Repeated Security calls describe alternatives (OR), not a combined requirement (AND). Nonempty scopes fail export. The document API has no server URL setting.

    Schema Limits

    Bodies, responses, parameters, and headers accept router.Schema, an interface with JSONSchema() ([]byte, error). Published validation schemas implement it, but export includes only their representable rules. Cross-field comparisons, conditions, custom checks, normalizers, and clock-based rules remain runtime behavior; see JSON Schema.

    validate.Infer[T]() describes a Go type’s JSON output without applying value rules. Interfaces and opaque custom MarshalJSON implementations produce open schemas unless a supported explicit type provider describes their shape. Recursive types become open at the repeated level, and pointer nil values are not modeled. Types without a JSON representation produce inference errors.

    If inference cannot describe your wire shape, supply router.RawSchema. Its root must be a JSON object or boolean, not an array, string, or null. The router checks syntax and root shape, not arbitrary JSON Schema keywords. Schema bytes are captured at declaration time and remain inline; references are neither rewritten nor fetched. Successful export is not proof of equivalence with your runtime validator or encoder.

    Check Declarations

    Use Summary, Description, and Tags to identify and organize operations. Descriptions pass CommonMark through without interpretation or sanitization. Deprecated(true) labels an operation but does not disable it or add a warning header.

    Nonempty operation IDs must be unique across the whole document. If one registration expands to several methods, omit its ID or register each method separately with a unique ID. Equivalent templates such as /users/{id} and /users/{name} also conflict, even under different methods. QUERY has its OpenAPI 3.2 method field; CONNECT and extension methods appear in additionalOperations.

    Leave mounts and catch-all registrations unannotated, or set Hidden(true). Visible annotated opaque paths fail export. Hiding an operation suppresses its metadata errors too, but does not prevent requests or remove it from Routes().

    Invalid metadata does not stop route execution. The first declaration error is retained even if a later setter replaces the invalid value. Run offline export before deployment to catch those errors, invalid media types, duplicate IDs, and path conflicts rather than discovering them on the first documentation request.

    For all declaration methods, see the router API reference.

    Open a bulk documentation archive?

    This file contains every guide in its scope, not API declarations. Loading it all into an agent can exhaust its context window and increase processing time and cost. For coding tasks, start with the index and read only the relevant pages.

    Use the full archive for offline indexing, bulk processing, or deliberate large-context use.

    Browse the index instead
    Open archive