route_builder.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  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. "reflect"
  9. "runtime"
  10. "strings"
  11. "sync/atomic"
  12. "github.com/emicklei/go-restful/log"
  13. )
  14. // RouteBuilder is a helper to construct Routes.
  15. type RouteBuilder struct {
  16. rootPath string
  17. currentPath string
  18. produces []string
  19. consumes []string
  20. httpMethod string // required
  21. function RouteFunction // required
  22. filters []FilterFunction
  23. conditions []RouteSelectionConditionFunction
  24. typeNameHandleFunc TypeNameHandleFunction // required
  25. // documentation
  26. doc string
  27. notes string
  28. operation string
  29. readSample, writeSample interface{}
  30. parameters []*Parameter
  31. errorMap map[int]ResponseError
  32. defaultResponse *ResponseError
  33. metadata map[string]interface{}
  34. deprecated bool
  35. }
  36. // Do evaluates each argument with the RouteBuilder itself.
  37. // This allows you to follow DRY principles without breaking the fluent programming style.
  38. // Example:
  39. // ws.Route(ws.DELETE("/{name}").To(t.deletePerson).Do(Returns200, Returns500))
  40. //
  41. // func Returns500(b *RouteBuilder) {
  42. // b.Returns(500, "Internal Server Error", restful.ServiceError{})
  43. // }
  44. func (b *RouteBuilder) Do(oneArgBlocks ...func(*RouteBuilder)) *RouteBuilder {
  45. for _, each := range oneArgBlocks {
  46. each(b)
  47. }
  48. return b
  49. }
  50. // To bind the route to a function.
  51. // If this route is matched with the incoming Http Request then call this function with the *Request,*Response pair. Required.
  52. func (b *RouteBuilder) To(function RouteFunction) *RouteBuilder {
  53. b.function = function
  54. return b
  55. }
  56. // Method specifies what HTTP method to match. Required.
  57. func (b *RouteBuilder) Method(method string) *RouteBuilder {
  58. b.httpMethod = method
  59. return b
  60. }
  61. // Produces specifies what MIME types can be produced ; the matched one will appear in the Content-Type Http header.
  62. func (b *RouteBuilder) Produces(mimeTypes ...string) *RouteBuilder {
  63. b.produces = mimeTypes
  64. return b
  65. }
  66. // Consumes specifies what MIME types can be consumes ; the Accept Http header must matched any of these
  67. func (b *RouteBuilder) Consumes(mimeTypes ...string) *RouteBuilder {
  68. b.consumes = mimeTypes
  69. return b
  70. }
  71. // Path specifies the relative (w.r.t WebService root path) URL path to match. Default is "/".
  72. func (b *RouteBuilder) Path(subPath string) *RouteBuilder {
  73. b.currentPath = subPath
  74. return b
  75. }
  76. // Doc tells what this route is all about. Optional.
  77. func (b *RouteBuilder) Doc(documentation string) *RouteBuilder {
  78. b.doc = documentation
  79. return b
  80. }
  81. // Notes is a verbose explanation of the operation behavior. Optional.
  82. func (b *RouteBuilder) Notes(notes string) *RouteBuilder {
  83. b.notes = notes
  84. return b
  85. }
  86. // Reads tells what resource type will be read from the request payload. Optional.
  87. // 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.
  88. func (b *RouteBuilder) Reads(sample interface{}, optionalDescription ...string) *RouteBuilder {
  89. fn := b.typeNameHandleFunc
  90. if fn == nil {
  91. fn = reflectTypeName
  92. }
  93. typeAsName := fn(sample)
  94. description := ""
  95. if len(optionalDescription) > 0 {
  96. description = optionalDescription[0]
  97. }
  98. b.readSample = sample
  99. bodyParameter := &Parameter{&ParameterData{Name: "body", Description: description}}
  100. bodyParameter.beBody()
  101. bodyParameter.Required(true)
  102. bodyParameter.DataType(typeAsName)
  103. b.Param(bodyParameter)
  104. return b
  105. }
  106. // ParameterNamed returns a Parameter already known to the RouteBuilder. Returns nil if not.
  107. // Use this to modify or extend information for the Parameter (through its Data()).
  108. func (b RouteBuilder) ParameterNamed(name string) (p *Parameter) {
  109. for _, each := range b.parameters {
  110. if each.Data().Name == name {
  111. return each
  112. }
  113. }
  114. return p
  115. }
  116. // Writes tells what resource type will be written as the response payload. Optional.
  117. func (b *RouteBuilder) Writes(sample interface{}) *RouteBuilder {
  118. b.writeSample = sample
  119. return b
  120. }
  121. // Param allows you to document the parameters of the Route. It adds a new Parameter (does not check for duplicates).
  122. func (b *RouteBuilder) Param(parameter *Parameter) *RouteBuilder {
  123. if b.parameters == nil {
  124. b.parameters = []*Parameter{}
  125. }
  126. b.parameters = append(b.parameters, parameter)
  127. return b
  128. }
  129. // Operation allows you to document what the actual method/function call is of the Route.
  130. // Unless called, the operation name is derived from the RouteFunction set using To(..).
  131. func (b *RouteBuilder) Operation(name string) *RouteBuilder {
  132. b.operation = name
  133. return b
  134. }
  135. // ReturnsError is deprecated, use Returns instead.
  136. func (b *RouteBuilder) ReturnsError(code int, message string, model interface{}) *RouteBuilder {
  137. log.Print("ReturnsError is deprecated, use Returns instead.")
  138. return b.Returns(code, message, model)
  139. }
  140. // Returns allows you to document what responses (errors or regular) can be expected.
  141. // The model parameter is optional ; either pass a struct instance or use nil if not applicable.
  142. func (b *RouteBuilder) Returns(code int, message string, model interface{}) *RouteBuilder {
  143. err := ResponseError{
  144. Code: code,
  145. Message: message,
  146. Model: model,
  147. IsDefault: false, // this field is deprecated, use default response instead.
  148. }
  149. // lazy init because there is no NewRouteBuilder (yet)
  150. if b.errorMap == nil {
  151. b.errorMap = map[int]ResponseError{}
  152. }
  153. b.errorMap[code] = err
  154. return b
  155. }
  156. // DefaultReturns is a special Returns call that sets the default of the response.
  157. func (b *RouteBuilder) DefaultReturns(message string, model interface{}) *RouteBuilder {
  158. b.defaultResponse = &ResponseError{
  159. Message: message,
  160. Model: model,
  161. }
  162. return b
  163. }
  164. // Metadata adds or updates a key=value pair to the metadata map.
  165. func (b *RouteBuilder) Metadata(key string, value interface{}) *RouteBuilder {
  166. if b.metadata == nil {
  167. b.metadata = map[string]interface{}{}
  168. }
  169. b.metadata[key] = value
  170. return b
  171. }
  172. // Deprecate sets the value of deprecated to true. Deprecated routes have a special UI treatment to warn against use
  173. func (b *RouteBuilder) Deprecate() *RouteBuilder {
  174. b.deprecated = true
  175. return b
  176. }
  177. // ResponseError represents a response; not necessarily an error.
  178. type ResponseError struct {
  179. Code int
  180. Message string
  181. Model interface{}
  182. IsDefault bool
  183. }
  184. func (b *RouteBuilder) servicePath(path string) *RouteBuilder {
  185. b.rootPath = path
  186. return b
  187. }
  188. // Filter appends a FilterFunction to the end of filters for this Route to build.
  189. func (b *RouteBuilder) Filter(filter FilterFunction) *RouteBuilder {
  190. b.filters = append(b.filters, filter)
  191. return b
  192. }
  193. // If sets a condition function that controls matching the Route based on custom logic.
  194. // The condition function is provided the HTTP request and should return true if the route
  195. // should be considered.
  196. //
  197. // Efficiency note: the condition function is called before checking the method, produces, and
  198. // consumes criteria, so that the correct HTTP status code can be returned.
  199. //
  200. // Lifecycle note: no filter functions have been called prior to calling the condition function,
  201. // so the condition function should not depend on any context that might be set up by container
  202. // or route filters.
  203. func (b *RouteBuilder) If(condition RouteSelectionConditionFunction) *RouteBuilder {
  204. b.conditions = append(b.conditions, condition)
  205. return b
  206. }
  207. // If no specific Route path then set to rootPath
  208. // If no specific Produces then set to rootProduces
  209. // If no specific Consumes then set to rootConsumes
  210. func (b *RouteBuilder) copyDefaults(rootProduces, rootConsumes []string) {
  211. if len(b.produces) == 0 {
  212. b.produces = rootProduces
  213. }
  214. if len(b.consumes) == 0 {
  215. b.consumes = rootConsumes
  216. }
  217. }
  218. // typeNameHandler sets the function that will convert types to strings in the parameter
  219. // and model definitions.
  220. func (b *RouteBuilder) typeNameHandler(handler TypeNameHandleFunction) *RouteBuilder {
  221. b.typeNameHandleFunc = handler
  222. return b
  223. }
  224. // Build creates a new Route using the specification details collected by the RouteBuilder
  225. func (b *RouteBuilder) Build() Route {
  226. pathExpr, err := newPathExpression(b.currentPath)
  227. if err != nil {
  228. log.Printf("Invalid path:%s because:%v", b.currentPath, err)
  229. os.Exit(1)
  230. }
  231. if b.function == nil {
  232. log.Printf("No function specified for route:" + b.currentPath)
  233. os.Exit(1)
  234. }
  235. operationName := b.operation
  236. if len(operationName) == 0 && b.function != nil {
  237. // extract from definition
  238. operationName = nameOfFunction(b.function)
  239. }
  240. route := Route{
  241. Method: b.httpMethod,
  242. Path: concatPath(b.rootPath, b.currentPath),
  243. Produces: b.produces,
  244. Consumes: b.consumes,
  245. Function: b.function,
  246. Filters: b.filters,
  247. If: b.conditions,
  248. relativePath: b.currentPath,
  249. pathExpr: pathExpr,
  250. Doc: b.doc,
  251. Notes: b.notes,
  252. Operation: operationName,
  253. ParameterDocs: b.parameters,
  254. ResponseErrors: b.errorMap,
  255. DefaultResponse: b.defaultResponse,
  256. ReadSample: b.readSample,
  257. WriteSample: b.writeSample,
  258. Metadata: b.metadata,
  259. Deprecated: b.deprecated}
  260. route.postBuild()
  261. return route
  262. }
  263. func concatPath(path1, path2 string) string {
  264. return strings.TrimRight(path1, "/") + "/" + strings.TrimLeft(path2, "/")
  265. }
  266. var anonymousFuncCount int32
  267. // nameOfFunction returns the short name of the function f for documentation.
  268. // It uses a runtime feature for debugging ; its value may change for later Go versions.
  269. func nameOfFunction(f interface{}) string {
  270. fun := runtime.FuncForPC(reflect.ValueOf(f).Pointer())
  271. tokenized := strings.Split(fun.Name(), ".")
  272. last := tokenized[len(tokenized)-1]
  273. last = strings.TrimSuffix(last, ")·fm") // < Go 1.5
  274. last = strings.TrimSuffix(last, ")-fm") // Go 1.5
  275. last = strings.TrimSuffix(last, "·fm") // < Go 1.5
  276. last = strings.TrimSuffix(last, "-fm") // Go 1.5
  277. if last == "func1" { // this could mean conflicts in API docs
  278. val := atomic.AddInt32(&anonymousFuncCount, 1)
  279. last = "func" + fmt.Sprintf("%d", val)
  280. atomic.StoreInt32(&anonymousFuncCount, val)
  281. }
  282. return last
  283. }