Repository navigation
Expand file tree
/
Copy pathmodule.go
More file actions
105 lines (94 loc) · 3.51 KB
/
Copy pathmodule.go
File metadata and controls
105 lines (94 loc) · 3.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
package expose
import (
"slices"
"github.com/getkin/kin-openapi/openapi3"
)
// ModuleDoc documents a path section ("module") of the api.
type ModuleDoc struct {
// Path is the path prefix of the module, e.g. "/guestlist"
Path string
// Description documents all endpoints below Path. It may contain markdown.
Description string
}
// ModuleDocumenter is implemented by [Function] values that carry
// documentation for the module(s) they belong to. [ReflectSpec] registers
// these docs as tag descriptions in the spec, where they are rendered as
// group headers by swagger ui and picked up by the discovery filter
// [FilterSpec] as the description of collapsed path groups.
type ModuleDocumenter interface {
ModuleDocs() []ModuleDoc
}
// Module attaches a description to all provided functions, documenting the
// path section they live under. This keeps the documentation colocated with
// the route definitions:
//
// expose.Module("/guestlist", "How the guest list works ...",
// expose.Func("/guestlist/add", Add),
// expose.Func("/guestlist/remove", Remove),
// )
//
// The description ends up as the tag description of the module in the openapi
// spec and as the description of the collapsed group in the discovery view
// (see [FilterSpec]). Structs registered with [Struct] can provide their
// module description by implementing `ModuleDoc() string` instead.
func Module(pathPrefix string, description string, fns ...Function) []Function {
doc := ModuleDoc{Path: normalizePrefix(pathPrefix), Description: description}
wrapped := make([]Function, len(fns))
for i, fn := range fns {
wrapped[i] = &moduleFunction{Function: fn, doc: doc}
}
return wrapped
}
// moduleFunction wraps a [Function] with the documentation of its module.
type moduleFunction struct {
Function
doc ModuleDoc
}
func (fn *moduleFunction) ModuleDocs() []ModuleDoc {
docs := []ModuleDoc{fn.doc}
if inner, ok := fn.Function.(ModuleDocumenter); ok {
docs = append(docs, inner.ModuleDocs()...)
}
return docs
}
// AnnotateOperation forwards to the wrapped function, since embedding does
// not promote the optional [OperationAnnotator] interface.
func (fn *moduleFunction) AnnotateOperation(op *openapi3.Operation) {
if annotator, ok := fn.Function.(OperationAnnotator); ok {
annotator.AnnotateOperation(op)
}
}
// upsertTag returns the tags with the description of the named tag set,
// appending a new tag when the name is not present yet. Existing tags are
// replaced by copies, so shared tag values (e.g. from a user provided default
// spec) are never mutated.
func upsertTag(tags openapi3.Tags, name string, description string) openapi3.Tags {
for i, tag := range tags {
if tag.Name != name {
continue
}
replaced := *tag
replaced.Description = description
result := slices.Clone(tags)
result[i] = &replaced
return result
}
// clone before appending: the provided tags may share their backing array
// with a caller owned slice (e.g. the default spec), which a plain append
// could write into when there is spare capacity
return append(slices.Clone(tags), &openapi3.Tag{Name: name, Description: description})
}
// fallbackDescription wraps a [Function] and sets the description of its
// operation, unless the operation already provides one.
type fallbackDescription struct {
Function
description string
}
func (fn *fallbackDescription) AnnotateOperation(op *openapi3.Operation) {
if annotator, ok := fn.Function.(OperationAnnotator); ok {
annotator.AnnotateOperation(op)
}
if op.Description == "" {
op.Description = fn.description
}
}