route_builder.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  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. "os"
  7. "reflect"
  8. "runtime"
  9. "strings"
  10. "github.com/emicklei/go-restful/log"
  11. )
  12. // RouteBuilder is a helper to construct Routes.
  13. type RouteBuilder struct {
  14. rootPath string
  15. currentPath string
  16. produces []string
  17. consumes []string
  18. httpMethod string // required
  19. function RouteFunction // required
  20. filters []FilterFunction
  21. // documentation
  22. doc string
  23. notes string
  24. operation string
  25. readSample, writeSample interface{}
  26. parameters []*Parameter
  27. errorMap map[int]ResponseError
  28. }
  29. // Do evaluates each argument with the RouteBuilder itself.
  30. // This allows you to follow DRY principles without breaking the fluent programming style.
  31. // Example:
  32. // ws.Route(ws.DELETE("/{name}").To(t.deletePerson).Do(Returns200, Returns500))
  33. //
  34. // func Returns500(b *RouteBuilder) {
  35. // b.Returns(500, "Internal Server Error", restful.ServiceError{})
  36. // }
  37. func (b *RouteBuilder) Do(oneArgBlocks ...func(*RouteBuilder)) *RouteBuilder {
  38. for _, each := range oneArgBlocks {
  39. each(b)
  40. }
  41. return b
  42. }
  43. // To bind the route to a function.
  44. // If this route is matched with the incoming Http Request then call this function with the *Request,*Response pair. Required.
  45. func (b *RouteBuilder) To(function RouteFunction) *RouteBuilder {
  46. b.function = function
  47. return b
  48. }
  49. // Method specifies what HTTP method to match. Required.
  50. func (b *RouteBuilder) Method(method string) *RouteBuilder {
  51. b.httpMethod = method
  52. return b
  53. }
  54. // Produces specifies what MIME types can be produced ; the matched one will appear in the Content-Type Http header.
  55. func (b *RouteBuilder) Produces(mimeTypes ...string) *RouteBuilder {
  56. b.produces = mimeTypes
  57. return b
  58. }
  59. // Consumes specifies what MIME types can be consumes ; the Accept Http header must matched any of these
  60. func (b *RouteBuilder) Consumes(mimeTypes ...string) *RouteBuilder {
  61. b.consumes = mimeTypes
  62. return b
  63. }
  64. // Path specifies the relative (w.r.t WebService root path) URL path to match. Default is "/".
  65. func (b *RouteBuilder) Path(subPath string) *RouteBuilder {
  66. b.currentPath = subPath
  67. return b
  68. }
  69. // Doc tells what this route is all about. Optional.
  70. func (b *RouteBuilder) Doc(documentation string) *RouteBuilder {
  71. b.doc = documentation
  72. return b
  73. }
  74. // A verbose explanation of the operation behavior. Optional.
  75. func (b *RouteBuilder) Notes(notes string) *RouteBuilder {
  76. b.notes = notes
  77. return b
  78. }
  79. // Reads tells what resource type will be read from the request payload. Optional.
  80. // 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.
  81. func (b *RouteBuilder) Reads(sample interface{}) *RouteBuilder {
  82. b.readSample = sample
  83. typeAsName := reflect.TypeOf(sample).String()
  84. bodyParameter := &Parameter{&ParameterData{Name: "body"}}
  85. bodyParameter.beBody()
  86. bodyParameter.Required(true)
  87. bodyParameter.DataType(typeAsName)
  88. b.Param(bodyParameter)
  89. return b
  90. }
  91. // ParameterNamed returns a Parameter already known to the RouteBuilder. Returns nil if not.
  92. // Use this to modify or extend information for the Parameter (through its Data()).
  93. func (b RouteBuilder) ParameterNamed(name string) (p *Parameter) {
  94. for _, each := range b.parameters {
  95. if each.Data().Name == name {
  96. return each
  97. }
  98. }
  99. return p
  100. }
  101. // Writes tells what resource type will be written as the response payload. Optional.
  102. func (b *RouteBuilder) Writes(sample interface{}) *RouteBuilder {
  103. b.writeSample = sample
  104. return b
  105. }
  106. // Param allows you to document the parameters of the Route. It adds a new Parameter (does not check for duplicates).
  107. func (b *RouteBuilder) Param(parameter *Parameter) *RouteBuilder {
  108. if b.parameters == nil {
  109. b.parameters = []*Parameter{}
  110. }
  111. b.parameters = append(b.parameters, parameter)
  112. return b
  113. }
  114. // Operation allows you to document what the actual method/function call is of the Route.
  115. // Unless called, the operation name is derived from the RouteFunction set using To(..).
  116. func (b *RouteBuilder) Operation(name string) *RouteBuilder {
  117. b.operation = name
  118. return b
  119. }
  120. // ReturnsError is deprecated, use Returns instead.
  121. func (b *RouteBuilder) ReturnsError(code int, message string, model interface{}) *RouteBuilder {
  122. log.Print("ReturnsError is deprecated, use Returns instead.")
  123. return b.Returns(code, message, model)
  124. }
  125. // Returns allows you to document what responses (errors or regular) can be expected.
  126. // The model parameter is optional ; either pass a struct instance or use nil if not applicable.
  127. func (b *RouteBuilder) Returns(code int, message string, model interface{}) *RouteBuilder {
  128. err := ResponseError{
  129. Code: code,
  130. Message: message,
  131. Model: model,
  132. }
  133. // lazy init because there is no NewRouteBuilder (yet)
  134. if b.errorMap == nil {
  135. b.errorMap = map[int]ResponseError{}
  136. }
  137. b.errorMap[code] = err
  138. return b
  139. }
  140. type ResponseError struct {
  141. Code int
  142. Message string
  143. Model interface{}
  144. }
  145. func (b *RouteBuilder) servicePath(path string) *RouteBuilder {
  146. b.rootPath = path
  147. return b
  148. }
  149. // Filter appends a FilterFunction to the end of filters for this Route to build.
  150. func (b *RouteBuilder) Filter(filter FilterFunction) *RouteBuilder {
  151. b.filters = append(b.filters, filter)
  152. return b
  153. }
  154. // If no specific Route path then set to rootPath
  155. // If no specific Produces then set to rootProduces
  156. // If no specific Consumes then set to rootConsumes
  157. func (b *RouteBuilder) copyDefaults(rootProduces, rootConsumes []string) {
  158. if len(b.produces) == 0 {
  159. b.produces = rootProduces
  160. }
  161. if len(b.consumes) == 0 {
  162. b.consumes = rootConsumes
  163. }
  164. }
  165. // Build creates a new Route using the specification details collected by the RouteBuilder
  166. func (b *RouteBuilder) Build() Route {
  167. pathExpr, err := newPathExpression(b.currentPath)
  168. if err != nil {
  169. log.Printf("[restful] Invalid path:%s because:%v", b.currentPath, err)
  170. os.Exit(1)
  171. }
  172. if b.function == nil {
  173. log.Printf("[restful] No function specified for route:" + b.currentPath)
  174. os.Exit(1)
  175. }
  176. operationName := b.operation
  177. if len(operationName) == 0 && b.function != nil {
  178. // extract from definition
  179. operationName = nameOfFunction(b.function)
  180. }
  181. route := Route{
  182. Method: b.httpMethod,
  183. Path: concatPath(b.rootPath, b.currentPath),
  184. Produces: b.produces,
  185. Consumes: b.consumes,
  186. Function: b.function,
  187. Filters: b.filters,
  188. relativePath: b.currentPath,
  189. pathExpr: pathExpr,
  190. Doc: b.doc,
  191. Notes: b.notes,
  192. Operation: operationName,
  193. ParameterDocs: b.parameters,
  194. ResponseErrors: b.errorMap,
  195. ReadSample: b.readSample,
  196. WriteSample: b.writeSample}
  197. route.postBuild()
  198. return route
  199. }
  200. func concatPath(path1, path2 string) string {
  201. return strings.TrimRight(path1, "/") + "/" + strings.TrimLeft(path2, "/")
  202. }
  203. // nameOfFunction returns the short name of the function f for documentation.
  204. // It uses a runtime feature for debugging ; its value may change for later Go versions.
  205. func nameOfFunction(f interface{}) string {
  206. fun := runtime.FuncForPC(reflect.ValueOf(f).Pointer())
  207. tokenized := strings.Split(fun.Name(), ".")
  208. last := tokenized[len(tokenized)-1]
  209. last = strings.TrimSuffix(last, ")·fm") // < Go 1.5
  210. last = strings.TrimSuffix(last, ")-fm") // Go 1.5
  211. last = strings.TrimSuffix(last, "·fm") // < Go 1.5
  212. last = strings.TrimSuffix(last, "-fm") // Go 1.5
  213. return last
  214. }