mirror of
https://github.com/zap-proto/zip.git
synced 2026-10-19 15:38:57 +00:00
An answer names itself whether or not the app describes itself
self is true of every answer — it is the path the request arrived on, and nothing has to be installed for it to be so. Only the description relations depend on a document existing. Those two rode one condition, and it cost more than the description: installOpenAPIRoutes returns early for an app with an empty registry, so an app serving plain routes named nothing at all, not even itself. Measured across the estate: six services register zero typed ops, so bumping their pin would have changed nothing about what they emit. Whether an app happens to use typed ops is not a statement about whether its answers have addresses.
This commit is contained in:
@@ -183,9 +183,7 @@ func (a *App) materialise(occ []occurrence, ctl []route) *fiber.App {
|
||||
// description lives by being BUILT, so there is no call to forget. It runs
|
||||
// after the report so the report still observes every request, and before
|
||||
// every route so it covers the answers no route matched. See [App.linkHandler].
|
||||
if h := a.linkHandler(); h != nil {
|
||||
f.Use(h)
|
||||
}
|
||||
f.Use(a.linkHandler())
|
||||
for _, o := range occ {
|
||||
switch o.kind() {
|
||||
case kindMiddleware:
|
||||
|
||||
@@ -42,22 +42,30 @@ const (
|
||||
relDoc = "service-doc"
|
||||
)
|
||||
|
||||
// linkHandler returns the middleware that names this app's own description on
|
||||
// every answer, or nil when there is nothing to name.
|
||||
// linkHandler returns the middleware that names an answer's own address, and
|
||||
// this app's description alongside it when there is one.
|
||||
//
|
||||
// The condition is the SAME map installOpenAPIRoutes writes, not a second guess
|
||||
// at it: the header is present exactly when the address it points at is served,
|
||||
// so a link can never advertise a document this app 404s. An app whose document
|
||||
// is Disabled, or which registers no typed op, gets no middleware at all rather
|
||||
// than one that checks a flag on every request.
|
||||
// SELF IS TRUE OF EVERY ANSWER. It is the path the request arrived on, and
|
||||
// nothing has to be installed for it to be so. Only the description relations
|
||||
// depend on a document existing, so only they are conditional — on the SAME map
|
||||
// installOpenAPIRoutes writes, not a second guess at it, so a link can never
|
||||
// advertise a document this app 404s.
|
||||
//
|
||||
// The two used to be one condition, and that cost more than the description:
|
||||
// installOpenAPIRoutes returns early for an app that registers no typed op, so
|
||||
// every such app — and most of ours are, serving plain routes — named nothing
|
||||
// at all, not even itself. Whether an app happens to use typed ops is not a
|
||||
// statement about whether its answers have addresses.
|
||||
func (a *App) linkHandler() fiber.Handler {
|
||||
if !a.controls[fiber.MethodGet+" "+SpecPath] {
|
||||
return nil
|
||||
}
|
||||
// Built once, at materialise: these two are constants of the app, and
|
||||
// Built once, at materialise: these are constants of the app, and
|
||||
// re-rendering them per request would allocate on every answer.
|
||||
desc := "<" + SpecPath + `>; rel="` + relDesc + `"`
|
||||
doc := "<" + DocsPath + `>; rel="` + relDoc + `"`
|
||||
var described []string
|
||||
if a.controls[fiber.MethodGet+" "+SpecPath] {
|
||||
described = []string{
|
||||
"<" + SpecPath + `>; rel="` + relDesc + `"`,
|
||||
"<" + DocsPath + `>; rel="` + relDoc + `"`,
|
||||
}
|
||||
}
|
||||
return func(fc fiber.Ctx) error {
|
||||
// After the handler, so a handler's own Link header is already in place
|
||||
// and these join it rather than racing it. fiber buffers headers until
|
||||
@@ -74,8 +82,9 @@ func (a *App) linkHandler() fiber.Handler {
|
||||
return err
|
||||
}
|
||||
h.Add(fiber.HeaderLink, "<"+fc.Path()+`>; rel="`+relSelf+`"`)
|
||||
h.Add(fiber.HeaderLink, desc)
|
||||
h.Add(fiber.HeaderLink, doc)
|
||||
for _, l := range described {
|
||||
h.Add(fiber.HeaderLink, l)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
+33
-6
@@ -103,10 +103,10 @@ func TestLink_SelfIsTheAddressReached(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// AN APP WITH NO DESCRIPTION SAYS NOTHING. The middleware is absent rather than
|
||||
// present-and-quiet, so an app that disabled its document pays nothing per
|
||||
// request and advertises nothing that would 404.
|
||||
func TestLink_AnAppWithNoDocumentAdvertisesNothing(t *testing.T) {
|
||||
// AN APP WITH NO DESCRIPTION STILL NAMES ITSELF. Disabling the document says
|
||||
// nothing about whether an answer has an address, so self stays and only the
|
||||
// relations that would 404 are withheld.
|
||||
func TestLink_AnAppWithNoDocumentStillNamesItself(t *testing.T) {
|
||||
a := New(Config{AppName: "silent", DisableStartupMessage: true,
|
||||
OpenAPI: OpenAPIConfig{Disabled: true}})
|
||||
Get(a, "/thing", func(ctx context.Context, _ *linkIn) (*linkOut, error) {
|
||||
@@ -120,8 +120,35 @@ func TestLink_AnAppWithNoDocumentAdvertisesNothing(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("GET /thing: %v", err)
|
||||
}
|
||||
if got := resp.Header.Values("Link"); len(got) != 0 {
|
||||
t.Errorf("an app serving no document sent %v", got)
|
||||
got := resp.Header.Values("Link")
|
||||
if len(got) != 1 || !strings.Contains(got[0], `rel="self"`) {
|
||||
t.Errorf("want self alone, got %v", got)
|
||||
}
|
||||
for _, l := range got {
|
||||
if strings.Contains(l, "service-desc") || strings.Contains(l, "service-doc") {
|
||||
t.Errorf("advertised a document it does not serve: %s", l)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A ROUTE IS NOT A TYPED OP, AND AN ANSWER IS STILL AN ANSWER. installOpenAPIRoutes
|
||||
// returns early for an app with an empty registry, and self used to ride that
|
||||
// same condition — so an app serving plain routes, which most of ours do,
|
||||
// named nothing. This is the case that was silently uncovered.
|
||||
func TestLink_PlainRoutesAreNamedToo(t *testing.T) {
|
||||
a := New(Config{AppName: "plain", DisableStartupMessage: true})
|
||||
a.Get("/plain", func(c *Ctx) error { return c.String(200, "ok") })
|
||||
a.installOpenAPIRoutes() // returns early: nothing typed to describe
|
||||
if err := a.Build(); err != nil {
|
||||
t.Fatalf("build: %v", err)
|
||||
}
|
||||
resp, err := a.Fiber().Test(httptest.NewRequest("GET", "/plain", nil))
|
||||
if err != nil {
|
||||
t.Fatalf("GET /plain: %v", err)
|
||||
}
|
||||
got := resp.Header.Values("Link")
|
||||
if len(got) != 1 || !strings.Contains(got[0], `</plain>; rel="self"`) {
|
||||
t.Errorf("a plain route must name its own address, got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user