Generating servers
The same document generates a server-stub module. The document stays the source of truth: generate the client and the server from one specification and implement one handler function per operation.
using OpenAPI, HTTP
OpenAPI.server(
"https://example.com/openapi.yaml";
framework = :HTTP,
name = "ExampleServer",
path = "ExampleServer.jl",
)framework = :HTTP (the default, available when HTTP.jl is loaded) targets HTTP.Router. Server framework packages add their own emitters through the OpenAPI.server_source extension seam. An extension must assemble its generated module through OpenAPI.server_module_source; this keeps the runtime data, pasted server code, and generated-code contract guard together. OpenAPI.serverplan is the staged sibling of OpenAPI.plan and rejects documents whose requests cannot be decoded faithfully (for example multipart/mixed request bodies, or two exploded object query parameters whose wire names cannot be told apart).
The generated header
The generated module header lists every handler signature the implementation must define. For a small document:
using OpenAPI, HTTP
document = """
openapi: 3.1.0
info: {title: Widgets, version: 1.0.0}
paths:
/widgets/{id}:
get:
operationId: getWidget
parameters:
- {name: id, in: path, required: true, schema: {type: integer, format: int64}}
- {name: verbose, in: query, schema: {type: boolean}}
responses:
"200":
description: one widget
content:
application/json:
schema:
type: object
required: [id, name]
properties:
id: {type: integer, format: int64}
name: {type: string}
delete:
operationId: deleteWidget
parameters:
- {name: id, in: path, required: true, schema: {type: integer, format: int64}}
responses:
"204": {description: deleted}
"""
source_code = OpenAPI.server(document; framework = :HTTP, name = "WidgetsServer")
header = Iterators.takewhile(!startswith("module"), eachsplit(source_code, '\n'))
println(join(header, '\n'))# Generated by OpenAPI.jl v1.1.2 from "Widgets" version "1.0.0". Do not edit.
# Implement these handler functions in a module (or any value
# supporting `getfield`) and mount them with `register!(router, impl)`:
# deletewidget(request, id::Int64) -> Nothing
# getwidget(request, id::Int64; verbose::Union{Absent,Bool} = ABSENT) -> GetwidgetResponse200Implementing handlers
Handler functions receive the raw request first, then typed path parameters in template order, then a required body; optional parameters arrive as keyword arguments only when the request supplied them.
include("ExampleServer.jl")
module Handlers
using HTTP
# GET /widgets/{id} -> get_widget(request, id::Int64; verbose = ABSENT)
function get_widget(request, id; verbose = false)
return lookup_widget(id; verbose) # encoded, validated, 200
end
# This operation documents 204, so nothing becomes an empty 204 response.
delete_widget(request, id) = nothing
# Return an HTTP.Response directly for custom behavior.
create_widget(request, body) = HTTP.Response(409, "already exists")
end
router = HTTP.Router()
ExampleServer.register!(router, Handlers; path_prefix = "/v1")
server = HTTP.serve!(router, "127.0.0.1", 8080)register!(router, impl; path_prefix, middleware) mounts every documented operation and fails eagerly, listing the expected signatures, when impl is missing any handler. middleware wraps each operation handler (middleware(handler) -> handler). register is kept as an alias, and the handler contract — implementation module second, typed positional parameters, typed-value-or-HTTP.Response returns — matches the shape OpenAPI.jl 0.2.x users generated with -g julia-server.
Request decoding and response encoding
Request decoding mirrors client encoding: parameter styles (simple, label, matrix, form, spaceDelimited, pipeDelimited, deepObject), header and cookie parameters, JSON, application/x-www-form-urlencoded, and multipart/form-data request bodies, with request-direction schema validation before handlers run. Decoding failures produce structured JSON 400 (or 415 for undocumented media types) responses without invoking the handler. Response values are validated against the output-direction schema and encoded from the first documented success response. Returning nothing follows that response: it emits an empty body when the response has no content, or JSON null when the selected JSON schema accepts null. A full HTTP.Response bypasses generated status, header, and body validation. The handler owns that validation.
One client capability has no server counterpart yet: a path parameter declared allowReserved: true. Generated clients send such a value with its reserved characters intact, so a slash-delimited value spans several path segments, but generated servers register the path template as written and HTTP.Router matches {name} against a single segment. Those requests reach the router as 404s rather than the handler.
deepObject bracket paths
OAS 3.x defines deepObject only for objects whose property values are scalars (filter[role]=admin). OpenAPI.jl extends the style with the bracket convention that qs, Rack, and PHP produce, so deepObject parameters may declare array schemas and nested object or array values. Nested objects nest brackets, arrays use zero-based bracket indices, and servers also accept name[]=value for appended items:
filters[0][field]=severity&filters[0][values][0]=error&filters[0][values][1]=warning
sorts[0]=-created_at&sorts[1]=%2BnameGenerated clients emit this form and generated servers decode it, in both strict and permissive mode. Bracket keys may arrive percent-encoded. The server rebuilds the value in the shape the parameter schema declares: integer keys become array indices only where the schema expects an array, so an additionalProperties object keeps "0" as a string key, and leaf values stay strings where the schema says string rather than being read as numbers. Schemas reached through $ref, allOf, oneOf, and anyOf are followed. The result is then validated against the schema as for every other parameter. Only scalar deepObject schemas remain a permissive-mode compatibility case.
An operation that documents no success response at all (only error entries) produces a missing_success_response planning warning; its handler's nothing return is answered with an empty 200, which the OpenAPI specification permits — response documentation is explicitly non-exhaustive.
Security and unsupported encodings
Generated server stubs do not authenticate or authorize requests. Apply a middleware that enforces the operation's security policy before it calls the handler. serverplan rejects request-body Encoding Objects that the generated HTTP server cannot recover faithfully. This includes nested encodings, custom multipart part headers, and explicit body encoding style modifiers.