Skip to content

Navigation Menu

Sign in
Sign up

Documenting a Route

wiki edited this page Sep 4, 2026 · 1 revision

Documenting a route

The opt-in interface

type OpenAPIRoute interface {
	OperationID() string
	Summary() string
	Description() string
	Tags() []string
}

A route that does not implement it is excluded from the document. That is how an operational endpoint stays private without configuration.

type CreateUser struct{ rxroute.Route }
func (r *CreateUser) OperationID() string { return "createUser" }
func (r *CreateUser) Summary() string { return "Create a user" }
func (r *CreateUser) Description() string { return "Creates a user account and returns the created record." }
func (r *CreateUser) Tags() []string { return []string{"users"} }
app.RegisterRoute(&CreateUser{Route: rxroute.New("POST", "/users", createUser)})

⚠ Pointer receiver → register as a pointer, or the assertion fails and the route is silently undocumented.

OperationID must be unique across the whole document. Client generators use it as a method name, so createUser becomes client.CreateUser(...). Duplicates produce a document that generates broken clients.

Bodies and responses

Picked up from the same interface the validation extension reads:

func (r *CreateUser) RequestBody() rextension.BodySchema {
	return rextension.Scalar(CreateUserRequest{})
}
func (r *CreateUser) Responses() map[int]rextension.BodySchema {
	return map[int]rextension.BodySchema{
		201: rextension.Scalar(UserResponse{}),
		422: rextension.Scalar(rextension.Problem{}),
	}
}

Register both extensions and one declaration produces validation and documentation. See Schemas.

Response descriptions

By default each response entry is described as "Response for status N", which is true and useless.

func (r *CreateUser) ResponseDescriptions() map[int]string {
	return map[int]string{
		201: "The user was created",
		409: "A user with that email already exists",
		422: "The request body failed validation",
	}
}

Examples

Examples are the difference between a document a client can read and one they can use.

func (r *CreatePayment) RequestBodyExamples() map[string]openapi.ExampleObject {
	return map[string]openapi.ExampleObject{
		"usd-card": {
			Summary: "Card payment in USD",
			Value: PaymentRequest{Amount: 1000, Currency: "USD", Method: "card"},
		},
		"eur-transfer": {
			Summary: "Bank transfer in EUR",
			Value: PaymentRequest{Amount: 2500, Currency: "EUR", Method: "transfer"},
		},
	}
}
func (r *CreatePayment) ResponseExamples() map[int]map[string]openapi.ExampleObject {
	return map[int]map[string]openapi.ExampleObject{
		201: {"settled": {Summary: "Settled immediately", Value: PaymentResponse{Status: "settled"}}},
		422: {"bad-currency": {Summary: "Unsupported currency", Value: rextension.Problem{
			Status: 422, Title: "Unprocessable Entity",
		}}},
	}
}

Both use the OpenAPI 3.1 examples map — the deprecated singular example property is not emitted.

Tags

Operation tags group endpoints in a UI. Describe them once at the top level:

openapi.NewConfig(
	openapi.WithTags(
		openapi.Tag{Name: "users", Description: "User accounts and profiles"},
		openapi.Tag{
			Name: "payments",
			Description: "Charges, refunds and settlement",
			ExternalDocs: &openapi.ExternalDocs{
				Description: "Payment guide",
				URL: "https://docs.example.com/payments",
			},
		},
	),
)

WithTags merges with previously registered tags; a duplicate name is overwritten by the last entry.

Path parameters

Path parameters are derived from the route pattern — /users/{id} produces an id path parameter automatically. There is nothing to declare.

A terser shape

Implementing four methods per route type gets old. Wrap:

type doc struct {
	rxroute.Route
	id, summary, description string
	tags []string
}
func (d *doc) OperationID() string { return d.id }
func (d *doc) Summary() string { return d.summary }
func (d *doc) Description() string { return d.description }
func (d *doc) Tags() []string { return d.tags }
func documented(rt rxroute.Route, id, summary, description string, tags ...string) rxroute.Route {
	return &doc{Route: rt, id: id, summary: summary, description: description, tags: tags}
}

Embed the schema and security wrappers on the same type when you need them — the interfaces are independent, so one route value can satisfy all of them.

Clone this wiki locally

AltStyle によって変換されたページ (->オリジナル) /