3 // Copyright 2013 Ernest Micklei. All rights reserved.
4 // Use of this source code is governed by a license
5 // that can be found in the LICENSE file.
15 "github.com/emicklei/go-restful/log"
18 // RouteBuilder is a helper to construct Routes.
19 type RouteBuilder struct {
24 httpMethod string // required
25 function RouteFunction // required
26 filters []FilterFunction
27 conditions []RouteSelectionConditionFunction
29 typeNameHandleFunc TypeNameHandleFunction // required
35 readSample, writeSample interface{}
36 parameters []*Parameter
37 errorMap map[int]ResponseError
38 defaultResponse *ResponseError
39 metadata map[string]interface{}
43 // Do evaluates each argument with the RouteBuilder itself.
44 // This allows you to follow DRY principles without breaking the fluent programming style.
46 // ws.Route(ws.DELETE("/{name}").To(t.deletePerson).Do(Returns200, Returns500))
48 // func Returns500(b *RouteBuilder) {
49 // b.Returns(500, "Internal Server Error", restful.ServiceError{})
51 func (b *RouteBuilder) Do(oneArgBlocks ...func(*RouteBuilder)) *RouteBuilder {
52 for _, each := range oneArgBlocks {
58 // To bind the route to a function.
59 // If this route is matched with the incoming Http Request then call this function with the *Request,*Response pair. Required.
60 func (b *RouteBuilder) To(function RouteFunction) *RouteBuilder {
65 // Method specifies what HTTP method to match. Required.
66 func (b *RouteBuilder) Method(method string) *RouteBuilder {
71 // Produces specifies what MIME types can be produced ; the matched one will appear in the Content-Type Http header.
72 func (b *RouteBuilder) Produces(mimeTypes ...string) *RouteBuilder {
73 b.produces = mimeTypes
77 // Consumes specifies what MIME types can be consumes ; the Accept Http header must matched any of these
78 func (b *RouteBuilder) Consumes(mimeTypes ...string) *RouteBuilder {
79 b.consumes = mimeTypes
83 // Path specifies the relative (w.r.t WebService root path) URL path to match. Default is "/".
84 func (b *RouteBuilder) Path(subPath string) *RouteBuilder {
85 b.currentPath = subPath
89 // Doc tells what this route is all about. Optional.
90 func (b *RouteBuilder) Doc(documentation string) *RouteBuilder {
95 // Notes is a verbose explanation of the operation behavior. Optional.
96 func (b *RouteBuilder) Notes(notes string) *RouteBuilder {
101 // Reads tells what resource type will be read from the request payload. Optional.
102 // 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.
103 func (b *RouteBuilder) Reads(sample interface{}, optionalDescription ...string) *RouteBuilder {
104 fn := b.typeNameHandleFunc
108 typeAsName := fn(sample)
110 if len(optionalDescription) > 0 {
111 description = optionalDescription[0]
113 b.readSample = sample
114 bodyParameter := &Parameter{&ParameterData{Name: "body", Description: description}}
115 bodyParameter.beBody()
116 bodyParameter.Required(true)
117 bodyParameter.DataType(typeAsName)
118 b.Param(bodyParameter)
122 // ParameterNamed returns a Parameter already known to the RouteBuilder. Returns nil if not.
123 // Use this to modify or extend information for the Parameter (through its Data()).
124 func (b RouteBuilder) ParameterNamed(name string) (p *Parameter) {
125 for _, each := range b.parameters {
126 if each.Data().Name == name {
133 // Writes tells what resource type will be written as the response payload. Optional.
134 func (b *RouteBuilder) Writes(sample interface{}) *RouteBuilder {
135 b.writeSample = sample
139 // Param allows you to document the parameters of the Route. It adds a new Parameter (does not check for duplicates).
140 func (b *RouteBuilder) Param(parameter *Parameter) *RouteBuilder {
141 if b.parameters == nil {
142 b.parameters = []*Parameter{}
144 b.parameters = append(b.parameters, parameter)
148 // Operation allows you to document what the actual method/function call is of the Route.
149 // Unless called, the operation name is derived from the RouteFunction set using To(..).
150 func (b *RouteBuilder) Operation(name string) *RouteBuilder {
155 // ReturnsError is deprecated, use Returns instead.
156 func (b *RouteBuilder) ReturnsError(code int, message string, model interface{}) *RouteBuilder {
157 log.Print("ReturnsError is deprecated, use Returns instead.")
158 return b.Returns(code, message, model)
161 // Returns allows you to document what responses (errors or regular) can be expected.
162 // The model parameter is optional ; either pass a struct instance or use nil if not applicable.
163 func (b *RouteBuilder) Returns(code int, message string, model interface{}) *RouteBuilder {
164 err := ResponseError{
168 IsDefault: false, // this field is deprecated, use default response instead.
170 // lazy init because there is no NewRouteBuilder (yet)
171 if b.errorMap == nil {
172 b.errorMap = map[int]ResponseError{}
174 b.errorMap[code] = err
178 // DefaultReturns is a special Returns call that sets the default of the response.
179 func (b *RouteBuilder) DefaultReturns(message string, model interface{}) *RouteBuilder {
180 b.defaultResponse = &ResponseError{
187 // Metadata adds or updates a key=value pair to the metadata map.
188 func (b *RouteBuilder) Metadata(key string, value interface{}) *RouteBuilder {
189 if b.metadata == nil {
190 b.metadata = map[string]interface{}{}
192 b.metadata[key] = value
196 // Deprecate sets the value of deprecated to true. Deprecated routes have a special UI treatment to warn against use
197 func (b *RouteBuilder) Deprecate() *RouteBuilder {
202 // ResponseError represents a response; not necessarily an error.
203 type ResponseError struct {
210 func (b *RouteBuilder) servicePath(path string) *RouteBuilder {
215 // Filter appends a FilterFunction to the end of filters for this Route to build.
216 func (b *RouteBuilder) Filter(filter FilterFunction) *RouteBuilder {
217 b.filters = append(b.filters, filter)
221 // If sets a condition function that controls matching the Route based on custom logic.
222 // The condition function is provided the HTTP request and should return true if the route
223 // should be considered.
225 // Efficiency note: the condition function is called before checking the method, produces, and
226 // consumes criteria, so that the correct HTTP status code can be returned.
228 // Lifecycle note: no filter functions have been called prior to calling the condition function,
229 // so the condition function should not depend on any context that might be set up by container
231 func (b *RouteBuilder) If(condition RouteSelectionConditionFunction) *RouteBuilder {
232 b.conditions = append(b.conditions, condition)
236 // If no specific Route path then set to rootPath
237 // If no specific Produces then set to rootProduces
238 // If no specific Consumes then set to rootConsumes
239 func (b *RouteBuilder) copyDefaults(rootProduces, rootConsumes []string) {
240 if len(b.produces) == 0 {
241 b.produces = rootProduces
243 if len(b.consumes) == 0 {
244 b.consumes = rootConsumes
248 // typeNameHandler sets the function that will convert types to strings in the parameter
249 // and model definitions.
250 func (b *RouteBuilder) typeNameHandler(handler TypeNameHandleFunction) *RouteBuilder {
251 b.typeNameHandleFunc = handler
255 // Build creates a new Route using the specification details collected by the RouteBuilder
256 func (b *RouteBuilder) Build() Route {
257 pathExpr, err := newPathExpression(b.currentPath)
259 log.Printf("Invalid path:%s because:%v", b.currentPath, err)
262 if b.function == nil {
263 log.Printf("No function specified for route:" + b.currentPath)
266 operationName := b.operation
267 if len(operationName) == 0 && b.function != nil {
268 // extract from definition
269 operationName = nameOfFunction(b.function)
272 Method: b.httpMethod,
273 Path: concatPath(b.rootPath, b.currentPath),
274 Produces: b.produces,
275 Consumes: b.consumes,
276 Function: b.function,
279 relativePath: b.currentPath,
283 Operation: operationName,
284 ParameterDocs: b.parameters,
285 ResponseErrors: b.errorMap,
286 DefaultResponse: b.defaultResponse,
287 ReadSample: b.readSample,
288 WriteSample: b.writeSample,
289 Metadata: b.metadata,
290 Deprecated: b.deprecated}
295 func concatPath(path1, path2 string) string {
296 return strings.TrimRight(path1, "/") + "/" + strings.TrimLeft(path2, "/")
299 var anonymousFuncCount int32
301 // nameOfFunction returns the short name of the function f for documentation.
302 // It uses a runtime feature for debugging ; its value may change for later Go versions.
303 func nameOfFunction(f interface{}) string {
304 fun := runtime.FuncForPC(reflect.ValueOf(f).Pointer())
305 tokenized := strings.Split(fun.Name(), ".")
306 last := tokenized[len(tokenized)-1]
307 last = strings.TrimSuffix(last, ")·fm") // < Go 1.5
308 last = strings.TrimSuffix(last, ")-fm") // Go 1.5
309 last = strings.TrimSuffix(last, "·fm") // < Go 1.5
310 last = strings.TrimSuffix(last, "-fm") // Go 1.5
311 if last == "func1" { // this could mean conflicts in API docs
312 val := atomic.AddInt32(&anonymousFuncCount, 1)
313 last = "func" + fmt.Sprintf("%d", val)
314 atomic.StoreInt32(&anonymousFuncCount, val)