Documentation
¶
Overview ¶
Package apidoc serves the OpenAPI document a rig project generates.
It exists because a document on disk is not a document anybody can reach. The generated specification describes exactly the routes the generated router mounts — that is the whole claim the openapi generator makes — and until this package there was no way for a running server to say so. A viewer, an SDK generator or a contract test had to be pointed at the repository rather than at the API, and a deployed build carried no statement of what it answers.
What it does not do is hold the bytes. An embed directive resolves against the directory of the file it is written in and cannot climb out of it, and the document is written to the openapi generator's out_dir — `docs/` by convention — rather than beside the router. So that generator writes the embed beside the document, in a package of its own, and hands the io/fs.FS here. This package finds the renderings inside it by name, which is what makes the directory somebody else's business: a project with `out_dir: api/docs` changes nothing here.
A project doing it by hand embeds the same directory the same way. What is looked for is JSONName and YAMLName anywhere in the filesystem given, so an embed.FS rooted at the module is as good as one rooted at the document.
The routes are public. No claims are read and no permission is checked, for the reason a health probe reads none: what the document says is what every client was generated against, and a specification nobody may fetch is a specification nobody can use. A project that has to gate it turns `api.openapi.serve` off and mounts this itself, behind whatever it gates the rest with.
No CORS header, deliberately. A viewer served from another origin needs one, and which origins may read this API is a decision about the whole API rather than about these two routes — so it belongs in a wrapper around the mux, where it applies to everything.
Example ¶
What a project writes is the go:embed line; the paths come from the generated router, which took them from the compiled document. Here the filesystem is a fake so the example can run.
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"testing/fstest"
"github.com/simonjanss/rig/runtime/apidoc"
)
func main() {
// In a real project:
//
// //go:embed docs/openapi.gen.json docs/openapi.gen.yaml
// var apidocs embed.FS
apidocs := fstest.MapFS{
"docs/openapi.gen.json": {Data: []byte(`{"openapi":"3.1.0"}`)},
}
docs, err := apidoc.New(apidocs, apidoc.Options{
JSONPath: "/api/v1/openapi.json",
YAMLPath: "/api/v1/openapi.yaml",
})
if err != nil {
panic(err)
}
mux := http.NewServeMux()
docs.Mount(mux)
// Only the JSON rendering was embedded, so only its route exists. A request
// for the other one is a 404 rather than an empty document.
fmt.Println(docs.Paths())
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/api/v1/openapi.json", nil))
fmt.Println(rec.Code, rec.Header().Get("Content-Type"))
fmt.Println(rec.Body.String())
}
Output: [/api/v1/openapi.json] 200 application/json {"openapi":"3.1.0"}
Index ¶
Examples ¶
Constants ¶
const ( // JSONName is the JSON rendering's filename. JSONName = "openapi.gen.json" // YAMLName is the YAML rendering's filename. YAMLName = "openapi.gen.yaml" )
Names of the two renderings, as the openapi generator writes them.
A directory prefix is not here: the filesystem handed to New is whatever was embedded, and it is searched by these names rather than by a path this package guessed.
The generator carries the same pair. It cannot import them from here — rig's root module requires a released `runtime`, and `go mod tidy` resolves that from the module proxy rather than from the workspace, so a package added since the last release is not there to import — so a rename has to touch both.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler serves the renderings it found.
func New ¶
New reads the document out of an embedded filesystem.
The two renderings are found by name anywhere in fsys, so the directory the project embedded from is not this package's business. Either may be absent: a project whose openapi generator writes `formats: [json]` gets one route and not two, which is what keeps the routes and the document that describes them from disagreeing without either side knowing what the other was configured with.
Both absent is an error, and it is returned rather than tolerated because it has exactly one cause worth reporting: the go:embed line names a directory with no document in it. Two routes quietly answering 404 would be the same mistake, found by whoever tried to use them.
func (*Handler) Mount ¶
Mount registers a GET route per rendering found.
One route per document rather than one route that content-negotiates. A specification is fetched by URL far more often than by Accept header — by a viewer, by a code generator, by curl — and two URLs are two things a person can paste.
Nothing here is traced, logged or throttled, and that is not arranged: rig opens its spans and writes its request lines inside each generated handler, so anything that is not one is already outside both.
type Options ¶
type Options struct {
// JSONPath is where the JSON rendering answers, for example
// "/api/v1/openapi.json". Empty leaves it unmounted.
JSONPath string
// YAMLPath is where the YAML rendering answers. Empty leaves it unmounted.
YAMLPath string
}
Options say where the document answers.
Both paths are absolute and already carry the API's base path, because the generated router passes what the compiler computed — the same base every other route was expanded against, checked against every other route for a collision. Nothing here joins a prefix.