route_builder.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. package restful
  2. // Copyright 2013 Ernest Micklei. All rights reserved.
  3. // Use of this source code is governed by a license
  4. // that can be found in the LICENSE file.
  5. import (
  6. "fmt"
  7. "os"
  8. "path"
  9. "reflect"
  10. "runtime"
  11. "strings"
  12. "sync/atomic"
  13. "github.com/emicklei/go-restful/v3/log"
  14. )
  15. // RouteBuilder is a helper to construct Routes.
  16. type RouteBuilder struct {
  17. rootPath string
  18. currentPath string
  19. produces []string
  20. consumes []string
  21. httpMethod string // required
  22. function RouteFunction // required
  23. filters []FilterFunction
  24. conditions []RouteSelectionConditionFunction
  25. allowedMethodsWithoutContentType []string // see Route
  26. typeNameHandleFunc TypeNameHandleFunction // required
  27. // documentation
  28. doc string
  29. notes string
  30. operation string
  31. readSample interface{}
  32. writeSamples []interface{}
  33. parameters []*Parameter
  34. errorMap map[int]ResponseError
  35. defaultResponse *ResponseError
  36. metadata map[string]interface{}
  37. extensions map[string]interface{}
  38. deprecated bool
  39. contentEncodingEnabled *bool
  40. }
  41. // Do evaluates each argument with the RouteBuilder itself.
  42. // This allows you to follow DRY principles without breaking the fluent programming style.
  43. // Example:
  44. //
  45. // ws.Route(ws.DELETE("/{name}").To(t.deletePerson).Do(Returns200, Returns500))
  46. //
  47. // func Returns500(b *RouteBuilder) {
  48. // b.Returns(500, "Internal Server Error", restful.ServiceError{})
  49. // }
  50. func (b *RouteBuilder) Do(oneArgBlocks ...func(*RouteBuilder)) *RouteBuilder {
  51. for _, each := range oneArgBlocks {
  52. each(b)
  53. }
  54. return b
  55. }
  56. // To bind the route to a function.
  57. // If this route is matched with the incoming Http Request then call this function with the *Request,*Response pair. Required.
  58. func (b *RouteBuilder) To(function RouteFunction) *RouteBuilder {
  59. b.function = function
  60. return b
  61. }
  62. // Method specifies what HTTP method to match. Required.
  63. func (b *RouteBuilder) Method(method string) *RouteBuilder {
  64. b.httpMethod = method
  65. return b
  66. }
  67. // Produces specifies what MIME types can be produced ; the matched one will appear in the Content-Type Http header.
  68. func (b *RouteBuilder) Produces(mimeTypes ...string) *RouteBuilder {
  69. b.produces = mimeTypes
  70. return b
  71. }
  72. // Consumes specifies what MIME types can be consumes ; the Accept Http header must matched any of these
  73. func (b *RouteBuilder) Consumes(mimeTypes ...string) *RouteBuilder {
  74. b.consumes = mimeTypes
  75. return b
  76. }
  77. // Path specifies the relative (w.r.t WebService root path) URL path to match. Default is "/".
  78. func (b *RouteBuilder) Path(subPath string) *RouteBuilder {
  79. b.currentPath = subPath
  80. return b
  81. }
  82. // Doc tells what this route is all about. Optional.
  83. func (b *RouteBuilder) Doc(documentation string) *RouteBuilder {
  84. b.doc = documentation
  85. return b
  86. }
  87. // Notes is a verbose explanation of the operation behavior. Optional.
  88. func (b *RouteBuilder) Notes(notes string) *RouteBuilder {
  89. b.notes = notes
  90. return b
  91. }
  92. // Reads tells what resource type will be read from the request payload. Optional.
  93. // A parameter of type "body" is added ,required is set to true and the dataType is set to the qualified name of the sample's type.
  94. func (b *RouteBuilder) Reads(sample interface{}, optionalDescription ...string) *RouteBuilder {
  95. fn := b.typeNameHandleFunc
  96. if fn == nil {
  97. fn = reflectTypeName
  98. }
  99. typeAsName := fn(sample)
  100. description := ""
  101. if len(optionalDescription) > 0 {
  102. description = optionalDescription[0]
  103. }
  104. b.readSample = sample
  105. bodyParameter := &Parameter{&ParameterData{Name: "body", Description: description}}
  106. bodyParameter.beBody()
  107. bodyParameter.Required(true)
  108. bodyParameter.DataType(typeAsName)
  109. b.Param(bodyParameter)
  110. return b
  111. }
  112. // ParameterNamed returns a Parameter already known to the RouteBuilder. Returns nil if not.
  113. // Use this to modify or extend information for the Parameter (through its Data()).
  114. func (b RouteBuilder) ParameterNamed(name string) (p *Parameter) {
  115. for _, each := range b.parameters {
  116. if each.Data().Name == name {
  117. return each
  118. }
  119. }
  120. return p
  121. }
  122. // Writes tells which one of the resource types will be written as the response payload. Optional.
  123. func (b *RouteBuilder) Writes(samples ...interface{}) *RouteBuilder {
  124. b.writeSamples = samples // oneof
  125. return b
  126. }
  127. // Param allows you to document the parameters of the Route. It adds a new Parameter (does not check for duplicates).
  128. func (b *RouteBuilder) Param(parameter *Parameter) *RouteBuilder {
  129. if b.parameters == nil {
  130. b.parameters = []*Parameter{}
  131. }
  132. b.parameters = append(b.parameters, parameter)
  133. return b
  134. }
  135. // Operation allows you to document what the actual method/function call is of the Route.
  136. // Unless called, the operation name is derived from the RouteFunction set using To(..).
  137. func (b *RouteBuilder) Operation(name string) *RouteBuilder {
  138. b.operation = name
  139. return b
  140. }
  141. // ReturnsError is deprecated, use Returns instead.
  142. func (b *RouteBuilder) ReturnsError(code int, message string, model interface{}) *RouteBuilder {
  143. log.Print("ReturnsError is deprecated, use Returns instead.")
  144. return b.Returns(code, message, model)
  145. }
  146. // Returns allows you to document what responses (errors or regular) can be expected.
  147. // The model parameter is optional ; either pass a struct instance or use nil if not applicable.
  148. func (b *RouteBuilder) Returns(code int, message string, model interface{}) *RouteBuilder {
  149. err := ResponseError{
  150. Code: code,
  151. Message: message,
  152. Model: model,
  153. IsDefault: false, // this field is deprecated, use default response instead.
  154. }
  155. // lazy init because there is no NewRouteBuilder (yet)
  156. if b.errorMap == nil {
  157. b.errorMap = map[int]ResponseError{}
  158. }
  159. b.errorMap[code] = err
  160. return b
  161. }
  162. // ReturnsWithHeaders is similar to Returns, but can specify response headers
  163. func (b *RouteBuilder) ReturnsWithHeaders(code int, message string, model interface{}, headers map[string]Header) *RouteBuilder {
  164. b.Returns(code, message, model)
  165. err := b.errorMap[code]
  166. err.Headers = headers
  167. b.errorMap[code] = err
  168. return b
  169. }
  170. // DefaultReturns is a special Returns call that sets the default of the response.
  171. func (b *RouteBuilder) DefaultReturns(message string, model interface{}) *RouteBuilder {
  172. b.defaultResponse = &ResponseError{
  173. Message: message,
  174. Model: model,
  175. }
  176. return b
  177. }
  178. // Metadata adds or updates a key=value pair to the metadata map.
  179. func (b *RouteBuilder) Metadata(key string, value interface{}) *RouteBuilder {
  180. if b.metadata == nil {
  181. b.metadata = map[string]interface{}{}
  182. }
  183. b.metadata[key] = value
  184. return b
  185. }
  186. // AddExtension adds or updates a key=value pair to the extensions map.
  187. func (b *RouteBuilder) AddExtension(key string, value interface{}) *RouteBuilder {
  188. if b.extensions == nil {
  189. b.extensions = map[string]interface{}{}
  190. }
  191. b.extensions[key] = value
  192. return b
  193. }
  194. // Deprecate sets the value of deprecated to true. Deprecated routes have a special UI treatment to warn against use
  195. func (b *RouteBuilder) Deprecate() *RouteBuilder {
  196. b.deprecated = true
  197. return b
  198. }
  199. // AllowedMethodsWithoutContentType overrides the default list GET,HEAD,OPTIONS,DELETE,TRACE
  200. // If a request does not include a content-type header then
  201. // depending on the method, it may return a 415 Unsupported Media.
  202. // Must have uppercase HTTP Method names such as GET,HEAD,OPTIONS,...
  203. func (b *RouteBuilder) AllowedMethodsWithoutContentType(methods []string) *RouteBuilder {
  204. b.allowedMethodsWithoutContentType = methods
  205. return b
  206. }
  207. // ResponseError represents a response; not necessarily an error.
  208. type ResponseError struct {
  209. ExtensionProperties
  210. Code int
  211. Message string
  212. Model interface{}
  213. Headers map[string]Header
  214. IsDefault bool
  215. }
  216. // Header describes a header for a response of the API
  217. //
  218. // For more information: http://goo.gl/8us55a#headerObject
  219. type Header struct {
  220. *Items
  221. Description string
  222. }
  223. // Items describe swagger simple schemas for headers
  224. type Items struct {
  225. Type string
  226. Format string
  227. Items *Items
  228. CollectionFormat string
  229. Default interface{}
  230. }
  231. func (b *RouteBuilder) servicePath(path string) *RouteBuilder {
  232. b.rootPath = path
  233. return b
  234. }
  235. // Filter appends a FilterFunction to the end of filters for this Route to build.
  236. func (b *RouteBuilder) Filter(filter FilterFunction) *RouteBuilder {
  237. b.filters = append(b.filters, filter)
  238. return b
  239. }
  240. // If sets a condition function that controls matching the Route based on custom logic.
  241. // The condition function is provided the HTTP request and should return true if the route
  242. // should be considered.
  243. //
  244. // Efficiency note: the condition function is called before checking the method, produces, and
  245. // consumes criteria, so that the correct HTTP status code can be returned.
  246. //
  247. // Lifecycle note: no filter functions have been called prior to calling the condition function,
  248. // so the condition function should not depend on any context that might be set up by container
  249. // or route filters.
  250. func (b *RouteBuilder) If(condition RouteSelectionConditionFunction) *RouteBuilder {
  251. b.conditions = append(b.conditions, condition)
  252. return b
  253. }
  254. // ContentEncodingEnabled allows you to override the Containers value for auto-compressing this route response.
  255. func (b *RouteBuilder) ContentEncodingEnabled(enabled bool) *RouteBuilder {
  256. b.contentEncodingEnabled = &enabled
  257. return b
  258. }
  259. // If no specific Route path then set to rootPath
  260. // If no specific Produces then set to rootProduces
  261. // If no specific Consumes then set to rootConsumes
  262. func (b *RouteBuilder) copyDefaults(rootProduces, rootConsumes []string) {
  263. if len(b.produces) == 0 {
  264. b.produces = rootProduces
  265. }
  266. if len(b.consumes) == 0 {
  267. b.consumes = rootConsumes
  268. }
  269. }
  270. // typeNameHandler sets the function that will convert types to strings in the parameter
  271. // and model definitions.
  272. func (b *RouteBuilder) typeNameHandler(handler TypeNameHandleFunction) *RouteBuilder {
  273. b.typeNameHandleFunc = handler
  274. return b
  275. }
  276. // Build creates a new Route using the specification details collected by the RouteBuilder
  277. func (b *RouteBuilder) Build() Route {
  278. pathExpr, err := newPathExpression(b.currentPath)
  279. if err != nil {
  280. log.Printf("Invalid path:%s because:%v", b.currentPath, err)
  281. os.Exit(1)
  282. }
  283. if b.function == nil {
  284. log.Printf("No function specified for route:" + b.currentPath)
  285. os.Exit(1)
  286. }
  287. operationName := b.operation
  288. if len(operationName) == 0 && b.function != nil {
  289. // extract from definition
  290. operationName = nameOfFunction(b.function)
  291. }
  292. route := Route{
  293. Method: b.httpMethod,
  294. Path: concatPath(b.rootPath, b.currentPath),
  295. Produces: b.produces,
  296. Consumes: b.consumes,
  297. Function: b.function,
  298. Filters: b.filters,
  299. If: b.conditions,
  300. relativePath: b.currentPath,
  301. pathExpr: pathExpr,
  302. Doc: b.doc,
  303. Notes: b.notes,
  304. Operation: operationName,
  305. ParameterDocs: b.parameters,
  306. ResponseErrors: b.errorMap,
  307. DefaultResponse: b.defaultResponse,
  308. ReadSample: b.readSample,
  309. WriteSamples: b.writeSamples,
  310. Metadata: b.metadata,
  311. Deprecated: b.deprecated,
  312. contentEncodingEnabled: b.contentEncodingEnabled,
  313. allowedMethodsWithoutContentType: b.allowedMethodsWithoutContentType,
  314. }
  315. // set WriteSample if one specified
  316. if len(b.writeSamples) == 1 {
  317. route.WriteSample = b.writeSamples[0]
  318. }
  319. route.Extensions = b.extensions
  320. route.postBuild()
  321. return route
  322. }
  323. // merge two paths using the current (package global) merge path strategy.
  324. func concatPath(rootPath, routePath string) string {
  325. if TrimRightSlashEnabled {
  326. return strings.TrimRight(rootPath, "/") + "/" + strings.TrimLeft(routePath, "/")
  327. } else {
  328. return path.Join(rootPath, routePath)
  329. }
  330. }
  331. var anonymousFuncCount int32
  332. // nameOfFunction returns the short name of the function f for documentation.
  333. // It uses a runtime feature for debugging ; its value may change for later Go versions.
  334. func nameOfFunction(f interface{}) string {
  335. fun := runtime.FuncForPC(reflect.ValueOf(f).Pointer())
  336. tokenized := strings.Split(fun.Name(), ".")
  337. last := tokenized[len(tokenized)-1]
  338. last = strings.TrimSuffix(last, ")·fm") // < Go 1.5
  339. last = strings.TrimSuffix(last, ")-fm") // Go 1.5
  340. last = strings.TrimSuffix(last, "·fm") // < Go 1.5
  341. last = strings.TrimSuffix(last, "-fm") // Go 1.5
  342. if last == "func1" { // this could mean conflicts in API docs
  343. val := atomic.AddInt32(&anonymousFuncCount, 1)
  344. last = "func" + fmt.Sprintf("%d", val)
  345. atomic.StoreInt32(&anonymousFuncCount, val)
  346. }
  347. return last
  348. }