/
server.go
284 lines (252 loc) · 7.22 KB
/
server.go
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
package openapi3
import (
"context"
"encoding/json"
"errors"
"fmt"
"math"
"net/url"
"sort"
"strings"
)
// Servers is specified by OpenAPI/Swagger standard version 3.
type Servers []*Server
// Validate returns an error if Servers does not comply with the OpenAPI spec.
func (servers Servers) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
for _, v := range servers {
if err := v.Validate(ctx); err != nil {
return err
}
}
return nil
}
// BasePath returns the base path of the first server in the list, or /.
func (servers Servers) BasePath() (string, error) {
for _, server := range servers {
return server.BasePath()
}
return "/", nil
}
func (servers Servers) MatchURL(parsedURL *url.URL) (*Server, []string, string) {
rawURL := parsedURL.String()
if i := strings.IndexByte(rawURL, '?'); i >= 0 {
rawURL = rawURL[:i]
}
for _, server := range servers {
pathParams, remaining, ok := server.MatchRawURL(rawURL)
if ok {
return server, pathParams, remaining
}
}
return nil, nil, ""
}
// Server is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#server-object
type Server struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
URL string `json:"url" yaml:"url"` // Required
Description string `json:"description,omitempty" yaml:"description,omitempty"`
Variables map[string]*ServerVariable `json:"variables,omitempty" yaml:"variables,omitempty"`
}
// BasePath returns the base path extracted from the default values of variables, if any.
// Assumes a valid struct (per Validate()).
func (server *Server) BasePath() (string, error) {
if server == nil {
return "/", nil
}
uri := server.URL
for name, svar := range server.Variables {
uri = strings.ReplaceAll(uri, "{"+name+"}", svar.Default)
}
u, err := url.ParseRequestURI(uri)
if err != nil {
return "", err
}
if bp := u.Path; bp != "" {
return bp, nil
}
return "/", nil
}
// MarshalJSON returns the JSON encoding of Server.
func (server Server) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, 3+len(server.Extensions))
for k, v := range server.Extensions {
m[k] = v
}
m["url"] = server.URL
if x := server.Description; x != "" {
m["description"] = x
}
if x := server.Variables; len(x) != 0 {
m["variables"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets Server to a copy of data.
func (server *Server) UnmarshalJSON(data []byte) error {
type ServerBis Server
var x ServerBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "url")
delete(x.Extensions, "description")
delete(x.Extensions, "variables")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*server = Server(x)
return nil
}
func (server Server) ParameterNames() ([]string, error) {
pattern := server.URL
var params []string
for len(pattern) > 0 {
i := strings.IndexByte(pattern, '{')
if i < 0 {
break
}
pattern = pattern[i+1:]
i = strings.IndexByte(pattern, '}')
if i < 0 {
return nil, errors.New("missing '}'")
}
params = append(params, strings.TrimSpace(pattern[:i]))
pattern = pattern[i+1:]
}
return params, nil
}
func (server Server) MatchRawURL(input string) ([]string, string, bool) {
pattern := server.URL
var params []string
for len(pattern) > 0 {
c := pattern[0]
if len(pattern) == 1 && c == '/' {
break
}
if c == '{' {
// Find end of pattern
i := strings.IndexByte(pattern, '}')
if i < 0 {
return nil, "", false
}
pattern = pattern[i+1:]
// Find next matching pattern character or next '/' whichever comes first
np := -1
if len(pattern) > 0 {
np = strings.IndexByte(input, pattern[0])
}
ns := strings.IndexByte(input, '/')
if np < 0 {
i = ns
} else if ns < 0 {
i = np
} else {
i = int(math.Min(float64(np), float64(ns)))
}
if i < 0 {
i = len(input)
}
params = append(params, input[:i])
input = input[i:]
continue
}
if len(input) == 0 || input[0] != c {
return nil, "", false
}
pattern = pattern[1:]
input = input[1:]
}
if input == "" {
input = "/"
}
if input[0] != '/' {
return nil, "", false
}
return params, input, true
}
// Validate returns an error if Server does not comply with the OpenAPI spec.
func (server *Server) Validate(ctx context.Context, opts ...ValidationOption) (err error) {
ctx = WithValidationOptions(ctx, opts...)
if server.URL == "" {
return errors.New("value of url must be a non-empty string")
}
opening, closing := strings.Count(server.URL, "{"), strings.Count(server.URL, "}")
if opening != closing {
return errors.New("server URL has mismatched { and }")
}
if opening != len(server.Variables) {
return errors.New("server has undeclared variables")
}
variables := make([]string, 0, len(server.Variables))
for name := range server.Variables {
variables = append(variables, name)
}
sort.Strings(variables)
for _, name := range variables {
v := server.Variables[name]
if !strings.Contains(server.URL, "{"+name+"}") {
return errors.New("server has undeclared variables")
}
if err = v.Validate(ctx); err != nil {
return
}
}
return validateExtensions(ctx, server.Extensions)
}
// ServerVariable is specified by OpenAPI/Swagger standard version 3.
// See https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#server-variable-object
type ServerVariable struct {
Extensions map[string]interface{} `json:"-" yaml:"-"`
Enum []string `json:"enum,omitempty" yaml:"enum,omitempty"`
Default string `json:"default,omitempty" yaml:"default,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
}
// MarshalJSON returns the JSON encoding of ServerVariable.
func (serverVariable ServerVariable) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{}, 4+len(serverVariable.Extensions))
for k, v := range serverVariable.Extensions {
m[k] = v
}
if x := serverVariable.Enum; len(x) != 0 {
m["enum"] = x
}
if x := serverVariable.Default; x != "" {
m["default"] = x
}
if x := serverVariable.Description; x != "" {
m["description"] = x
}
return json.Marshal(m)
}
// UnmarshalJSON sets ServerVariable to a copy of data.
func (serverVariable *ServerVariable) UnmarshalJSON(data []byte) error {
type ServerVariableBis ServerVariable
var x ServerVariableBis
if err := json.Unmarshal(data, &x); err != nil {
return unmarshalError(err)
}
_ = json.Unmarshal(data, &x.Extensions)
delete(x.Extensions, "enum")
delete(x.Extensions, "default")
delete(x.Extensions, "description")
if len(x.Extensions) == 0 {
x.Extensions = nil
}
*serverVariable = ServerVariable(x)
return nil
}
// Validate returns an error if ServerVariable does not comply with the OpenAPI spec.
func (serverVariable *ServerVariable) Validate(ctx context.Context, opts ...ValidationOption) error {
ctx = WithValidationOptions(ctx, opts...)
if serverVariable.Default == "" {
data, err := serverVariable.MarshalJSON()
if err != nil {
return err
}
return fmt.Errorf("field default is required in %s", data)
}
return validateExtensions(ctx, serverVariable.Extensions)
}