1 package restful
2
3
4
5
6
7 import (
8 "fmt"
9 "os"
10 "path"
11 "reflect"
12 "runtime"
13 "strings"
14 "sync/atomic"
15
16 "github.com/emicklei/go-restful/v3/log"
17 )
18
19
20 type RouteBuilder struct {
21 rootPath string
22 currentPath string
23 produces []string
24 consumes []string
25 httpMethod string
26 function RouteFunction
27 filters []FilterFunction
28 conditions []RouteSelectionConditionFunction
29 allowedMethodsWithoutContentType []string
30
31 typeNameHandleFunc TypeNameHandleFunction
32
33
34 doc string
35 notes string
36 operation string
37 readSample interface{}
38 writeSamples []interface{}
39 parameters []*Parameter
40 errorMap map[int]ResponseError
41 defaultResponse *ResponseError
42 metadata map[string]interface{}
43 extensions map[string]interface{}
44 deprecated bool
45 contentEncodingEnabled *bool
46 }
47
48
49
50
51
52
53
54
55
56
57 func (b *RouteBuilder) Do(oneArgBlocks ...func(*RouteBuilder)) *RouteBuilder {
58 for _, each := range oneArgBlocks {
59 each(b)
60 }
61 return b
62 }
63
64
65
66 func (b *RouteBuilder) To(function RouteFunction) *RouteBuilder {
67 b.function = function
68 return b
69 }
70
71
72 func (b *RouteBuilder) Method(method string) *RouteBuilder {
73 b.httpMethod = method
74 return b
75 }
76
77
78 func (b *RouteBuilder) Produces(mimeTypes ...string) *RouteBuilder {
79 b.produces = mimeTypes
80 return b
81 }
82
83
84 func (b *RouteBuilder) Consumes(mimeTypes ...string) *RouteBuilder {
85 b.consumes = mimeTypes
86 return b
87 }
88
89
90 func (b *RouteBuilder) Path(subPath string) *RouteBuilder {
91 b.currentPath = subPath
92 return b
93 }
94
95
96 func (b *RouteBuilder) Doc(documentation string) *RouteBuilder {
97 b.doc = documentation
98 return b
99 }
100
101
102 func (b *RouteBuilder) Notes(notes string) *RouteBuilder {
103 b.notes = notes
104 return b
105 }
106
107
108
109 func (b *RouteBuilder) Reads(sample interface{}, optionalDescription ...string) *RouteBuilder {
110 fn := b.typeNameHandleFunc
111 if fn == nil {
112 fn = reflectTypeName
113 }
114 typeAsName := fn(sample)
115 description := ""
116 if len(optionalDescription) > 0 {
117 description = optionalDescription[0]
118 }
119 b.readSample = sample
120 bodyParameter := &Parameter{&ParameterData{Name: "body", Description: description}}
121 bodyParameter.beBody()
122 bodyParameter.Required(true)
123 bodyParameter.DataType(typeAsName)
124 b.Param(bodyParameter)
125 return b
126 }
127
128
129
130 func (b RouteBuilder) ParameterNamed(name string) (p *Parameter) {
131 for _, each := range b.parameters {
132 if each.Data().Name == name {
133 return each
134 }
135 }
136 return p
137 }
138
139
140 func (b *RouteBuilder) Writes(samples ...interface{}) *RouteBuilder {
141 b.writeSamples = samples
142 return b
143 }
144
145
146 func (b *RouteBuilder) Param(parameter *Parameter) *RouteBuilder {
147 if b.parameters == nil {
148 b.parameters = []*Parameter{}
149 }
150 b.parameters = append(b.parameters, parameter)
151 return b
152 }
153
154
155
156 func (b *RouteBuilder) Operation(name string) *RouteBuilder {
157 b.operation = name
158 return b
159 }
160
161
162 func (b *RouteBuilder) ReturnsError(code int, message string, model interface{}) *RouteBuilder {
163 log.Print("ReturnsError is deprecated, use Returns instead.")
164 return b.Returns(code, message, model)
165 }
166
167
168
169 func (b *RouteBuilder) Returns(code int, message string, model interface{}) *RouteBuilder {
170 err := ResponseError{
171 Code: code,
172 Message: message,
173 Model: model,
174 IsDefault: false,
175 }
176
177 if b.errorMap == nil {
178 b.errorMap = map[int]ResponseError{}
179 }
180 b.errorMap[code] = err
181 return b
182 }
183
184
185 func (b *RouteBuilder) ReturnsWithHeaders(code int, message string, model interface{}, headers map[string]Header) *RouteBuilder {
186 b.Returns(code, message, model)
187 err := b.errorMap[code]
188 err.Headers = headers
189 b.errorMap[code] = err
190 return b
191 }
192
193
194 func (b *RouteBuilder) DefaultReturns(message string, model interface{}) *RouteBuilder {
195 b.defaultResponse = &ResponseError{
196 Message: message,
197 Model: model,
198 }
199 return b
200 }
201
202
203 func (b *RouteBuilder) Metadata(key string, value interface{}) *RouteBuilder {
204 if b.metadata == nil {
205 b.metadata = map[string]interface{}{}
206 }
207 b.metadata[key] = value
208 return b
209 }
210
211
212 func (b *RouteBuilder) AddExtension(key string, value interface{}) *RouteBuilder {
213 if b.extensions == nil {
214 b.extensions = map[string]interface{}{}
215 }
216 b.extensions[key] = value
217 return b
218 }
219
220
221 func (b *RouteBuilder) Deprecate() *RouteBuilder {
222 b.deprecated = true
223 return b
224 }
225
226
227
228
229
230 func (b *RouteBuilder) AllowedMethodsWithoutContentType(methods []string) *RouteBuilder {
231 b.allowedMethodsWithoutContentType = methods
232 return b
233 }
234
235
236 type ResponseError struct {
237 ExtensionProperties
238 Code int
239 Message string
240 Model interface{}
241 Headers map[string]Header
242 IsDefault bool
243 }
244
245
246
247
248 type Header struct {
249 *Items
250 Description string
251 }
252
253
254 type Items struct {
255 Type string
256 Format string
257 Items *Items
258 CollectionFormat string
259 Default interface{}
260 }
261
262 func (b *RouteBuilder) servicePath(path string) *RouteBuilder {
263 b.rootPath = path
264 return b
265 }
266
267
268 func (b *RouteBuilder) Filter(filter FilterFunction) *RouteBuilder {
269 b.filters = append(b.filters, filter)
270 return b
271 }
272
273
274
275
276
277
278
279
280
281
282
283 func (b *RouteBuilder) If(condition RouteSelectionConditionFunction) *RouteBuilder {
284 b.conditions = append(b.conditions, condition)
285 return b
286 }
287
288
289 func (b *RouteBuilder) ContentEncodingEnabled(enabled bool) *RouteBuilder {
290 b.contentEncodingEnabled = &enabled
291 return b
292 }
293
294
295
296
297 func (b *RouteBuilder) copyDefaults(rootProduces, rootConsumes []string) {
298 if len(b.produces) == 0 {
299 b.produces = rootProduces
300 }
301 if len(b.consumes) == 0 {
302 b.consumes = rootConsumes
303 }
304 }
305
306
307
308 func (b *RouteBuilder) typeNameHandler(handler TypeNameHandleFunction) *RouteBuilder {
309 b.typeNameHandleFunc = handler
310 return b
311 }
312
313
314 func (b *RouteBuilder) Build() Route {
315 pathExpr, err := newPathExpression(b.currentPath)
316 if err != nil {
317 log.Printf("Invalid path:%s because:%v", b.currentPath, err)
318 os.Exit(1)
319 }
320 if b.function == nil {
321 log.Printf("No function specified for route:" + b.currentPath)
322 os.Exit(1)
323 }
324 operationName := b.operation
325 if len(operationName) == 0 && b.function != nil {
326
327 operationName = nameOfFunction(b.function)
328 }
329 route := Route{
330 Method: b.httpMethod,
331 Path: concatPath(b.rootPath, b.currentPath),
332 Produces: b.produces,
333 Consumes: b.consumes,
334 Function: b.function,
335 Filters: b.filters,
336 If: b.conditions,
337 relativePath: b.currentPath,
338 pathExpr: pathExpr,
339 Doc: b.doc,
340 Notes: b.notes,
341 Operation: operationName,
342 ParameterDocs: b.parameters,
343 ResponseErrors: b.errorMap,
344 DefaultResponse: b.defaultResponse,
345 ReadSample: b.readSample,
346 WriteSamples: b.writeSamples,
347 Metadata: b.metadata,
348 Deprecated: b.deprecated,
349 contentEncodingEnabled: b.contentEncodingEnabled,
350 allowedMethodsWithoutContentType: b.allowedMethodsWithoutContentType,
351 }
352
353 if len(b.writeSamples) == 1 {
354 route.WriteSample = b.writeSamples[0]
355 }
356 route.Extensions = b.extensions
357 route.postBuild()
358 return route
359 }
360
361
362 func concatPath(rootPath, routePath string) string {
363
364 if TrimRightSlashEnabled {
365 return strings.TrimRight(rootPath, "/") + "/" + strings.TrimLeft(routePath, "/")
366 } else {
367 return path.Join(rootPath, routePath)
368 }
369 }
370
371 var anonymousFuncCount int32
372
373
374
375 func nameOfFunction(f interface{}) string {
376 fun := runtime.FuncForPC(reflect.ValueOf(f).Pointer())
377 tokenized := strings.Split(fun.Name(), ".")
378 last := tokenized[len(tokenized)-1]
379 last = strings.TrimSuffix(last, ")·fm")
380 last = strings.TrimSuffix(last, ")-fm")
381 last = strings.TrimSuffix(last, "·fm")
382 last = strings.TrimSuffix(last, "-fm")
383 if last == "func1" {
384 val := atomic.AddInt32(&anonymousFuncCount, 1)
385 last = "func" + fmt.Sprintf("%d", val)
386 atomic.StoreInt32(&anonymousFuncCount, val)
387 }
388 return last
389 }
390
View as plain text