1package framework
2
3import (
4	"fmt"
5	"sort"
6	"strings"
7
8	"github.com/hashicorp/vault/logical"
9)
10
11// Helper which returns a generic regex string for creating endpoint patterns
12// that are identified by the given name in the backends
13func GenericNameRegex(name string) string {
14	return fmt.Sprintf("(?P<%s>\\w[\\w-.]+\\w)", name)
15}
16
17// Helper which returns a regex string for optionally accepting the a field
18// from the API URL
19func OptionalParamRegex(name string) string {
20	return fmt.Sprintf("(/(?P<%s>.+))?", name)
21}
22
23// PathAppend is a helper for appending lists of paths into a single
24// list.
25func PathAppend(paths ...[]*Path) []*Path {
26	result := make([]*Path, 0, 10)
27	for _, ps := range paths {
28		result = append(result, ps...)
29	}
30
31	return result
32}
33
34// Path is a single path that the backend responds to.
35type Path struct {
36	// Pattern is the pattern of the URL that matches this path.
37	//
38	// This should be a valid regular expression. Named captures will be
39	// exposed as fields that should map to a schema in Fields. If a named
40	// capture is not a field in the Fields map, then it will be ignored.
41	Pattern string
42
43	// Fields is the mapping of data fields to a schema describing that
44	// field. Named captures in the Pattern also map to fields. If a named
45	// capture name matches a PUT body name, the named capture takes
46	// priority.
47	//
48	// Note that only named capture fields are available in every operation,
49	// whereas all fields are available in the Write operation.
50	Fields map[string]*FieldSchema
51
52	// Callbacks are the set of callbacks that are called for a given
53	// operation. If a callback for a specific operation is not present,
54	// then logical.ErrUnsupportedOperation is automatically generated.
55	//
56	// The help operation is the only operation that the Path will
57	// automatically handle if the Help field is set. If both the Help
58	// field is set and there is a callback registered here, then the
59	// callback will be called.
60	Callbacks map[logical.Operation]OperationFunc
61
62	// ExistenceCheck, if implemented, is used to query whether a given
63	// resource exists or not. This is used for ACL purposes: if an Update
64	// action is specified, and the existence check returns false, the action
65	// is not allowed since the resource must first be created. The reverse is
66	// also true. If not specified, the Update action is forced and the user
67	// must have UpdateCapability on the path.
68	ExistenceCheck func(*logical.Request, *FieldData) (bool, error)
69
70	// Help is text describing how to use this path. This will be used
71	// to auto-generate the help operation. The Path will automatically
72	// generate a parameter listing and URL structure based on the
73	// regular expression, so the help text should just contain a description
74	// of what happens.
75	//
76	// HelpSynopsis is a one-sentence description of the path. This will
77	// be automatically line-wrapped at 80 characters.
78	//
79	// HelpDescription is a long-form description of the path. This will
80	// be automatically line-wrapped at 80 characters.
81	HelpSynopsis    string
82	HelpDescription string
83}
84
85func (p *Path) helpCallback(
86	req *logical.Request, data *FieldData) (*logical.Response, error) {
87	var tplData pathTemplateData
88	tplData.Request = req.Path
89	tplData.RoutePattern = p.Pattern
90	tplData.Synopsis = strings.TrimSpace(p.HelpSynopsis)
91	if tplData.Synopsis == "" {
92		tplData.Synopsis = "<no synopsis>"
93	}
94	tplData.Description = strings.TrimSpace(p.HelpDescription)
95	if tplData.Description == "" {
96		tplData.Description = "<no description>"
97	}
98
99	// Alphabetize the fields
100	fieldKeys := make([]string, 0, len(p.Fields))
101	for k, _ := range p.Fields {
102		fieldKeys = append(fieldKeys, k)
103	}
104	sort.Strings(fieldKeys)
105
106	// Build the field help
107	tplData.Fields = make([]pathTemplateFieldData, len(fieldKeys))
108	for i, k := range fieldKeys {
109		schema := p.Fields[k]
110		description := strings.TrimSpace(schema.Description)
111		if description == "" {
112			description = "<no description>"
113		}
114
115		tplData.Fields[i] = pathTemplateFieldData{
116			Key:         k,
117			Type:        schema.Type.String(),
118			Description: description,
119		}
120	}
121
122	help, err := executeTemplate(pathHelpTemplate, &tplData)
123	if err != nil {
124		return nil, fmt.Errorf("error executing template: %s", err)
125	}
126
127	return logical.HelpResponse(help, nil), nil
128}
129
130type pathTemplateData struct {
131	Request      string
132	RoutePattern string
133	Synopsis     string
134	Description  string
135	Fields       []pathTemplateFieldData
136}
137
138type pathTemplateFieldData struct {
139	Key         string
140	Type        string
141	Description string
142	URL         bool
143}
144
145const pathHelpTemplate = `
146Request:        {{.Request}}
147Matching Route: {{.RoutePattern}}
148
149{{.Synopsis}}
150
151{{ if .Fields -}}
152## PARAMETERS
153{{range .Fields}}
154{{indent 4 .Key}} ({{.Type}})
155{{indent 8 .Description}}
156{{end}}{{end}}
157## DESCRIPTION
158
159{{.Description}}
160`
161