...

Source file src/github.com/emicklei/go-restful/v3/route_builder.go

Documentation: github.com/emicklei/go-restful/v3

     1  package restful
     2  
     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.
     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  // RouteBuilder is a helper to construct Routes.
    20  type RouteBuilder struct {
    21  	rootPath                         string
    22  	currentPath                      string
    23  	produces                         []string
    24  	consumes                         []string
    25  	httpMethod                       string        // required
    26  	function                         RouteFunction // required
    27  	filters                          []FilterFunction
    28  	conditions                       []RouteSelectionConditionFunction
    29  	allowedMethodsWithoutContentType []string // see Route
    30  
    31  	typeNameHandleFunc TypeNameHandleFunction // required
    32  
    33  	// documentation
    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  // Do evaluates each argument with the RouteBuilder itself.
    49  // This allows you to follow DRY principles without breaking the fluent programming style.
    50  // Example:
    51  //
    52  //	ws.Route(ws.DELETE("/{name}").To(t.deletePerson).Do(Returns200, Returns500))
    53  //
    54  //	func Returns500(b *RouteBuilder) {
    55  //		b.Returns(500, "Internal Server Error", restful.ServiceError{})
    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  // To bind the route to a function.
    65  // If this route is matched with the incoming Http Request then call this function with the *Request,*Response pair. Required.
    66  func (b *RouteBuilder) To(function RouteFunction) *RouteBuilder {
    67  	b.function = function
    68  	return b
    69  }
    70  
    71  // Method specifies what HTTP method to match. Required.
    72  func (b *RouteBuilder) Method(method string) *RouteBuilder {
    73  	b.httpMethod = method
    74  	return b
    75  }
    76  
    77  // Produces specifies what MIME types can be produced ; the matched one will appear in the Content-Type Http header.
    78  func (b *RouteBuilder) Produces(mimeTypes ...string) *RouteBuilder {
    79  	b.produces = mimeTypes
    80  	return b
    81  }
    82  
    83  // Consumes specifies what MIME types can be consumes ; the Accept Http header must matched any of these
    84  func (b *RouteBuilder) Consumes(mimeTypes ...string) *RouteBuilder {
    85  	b.consumes = mimeTypes
    86  	return b
    87  }
    88  
    89  // Path specifies the relative (w.r.t WebService root path) URL path to match. Default is "/".
    90  func (b *RouteBuilder) Path(subPath string) *RouteBuilder {
    91  	b.currentPath = subPath
    92  	return b
    93  }
    94  
    95  // Doc tells what this route is all about. Optional.
    96  func (b *RouteBuilder) Doc(documentation string) *RouteBuilder {
    97  	b.doc = documentation
    98  	return b
    99  }
   100  
   101  // Notes is a verbose explanation of the operation behavior. Optional.
   102  func (b *RouteBuilder) Notes(notes string) *RouteBuilder {
   103  	b.notes = notes
   104  	return b
   105  }
   106  
   107  // Reads tells what resource type will be read from the request payload. Optional.
   108  // 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.
   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  // ParameterNamed returns a Parameter already known to the RouteBuilder. Returns nil if not.
   129  // Use this to modify or extend information for the Parameter (through its Data()).
   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  // Writes tells which one of the resource types will be written as the response payload. Optional.
   140  func (b *RouteBuilder) Writes(samples ...interface{}) *RouteBuilder {
   141  	b.writeSamples = samples // oneof
   142  	return b
   143  }
   144  
   145  // Param allows you to document the parameters of the Route. It adds a new Parameter (does not check for duplicates).
   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  // Operation allows you to document what the actual method/function call is of the Route.
   155  // Unless called, the operation name is derived from the RouteFunction set using To(..).
   156  func (b *RouteBuilder) Operation(name string) *RouteBuilder {
   157  	b.operation = name
   158  	return b
   159  }
   160  
   161  // ReturnsError is deprecated, use Returns instead.
   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  // Returns allows you to document what responses (errors or regular) can be expected.
   168  // The model parameter is optional ; either pass a struct instance or use nil if not applicable.
   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, // this field is deprecated, use default response instead.
   175  	}
   176  	// lazy init because there is no NewRouteBuilder (yet)
   177  	if b.errorMap == nil {
   178  		b.errorMap = map[int]ResponseError{}
   179  	}
   180  	b.errorMap[code] = err
   181  	return b
   182  }
   183  
   184  // ReturnsWithHeaders is similar to Returns, but can specify response headers
   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  // DefaultReturns is a special Returns call that sets the default of the response.
   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  // Metadata adds or updates a key=value pair to the metadata map.
   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  // AddExtension adds or updates a key=value pair to the extensions map.
   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  // Deprecate sets the value of deprecated to true.  Deprecated routes have a special UI treatment to warn against use
   221  func (b *RouteBuilder) Deprecate() *RouteBuilder {
   222  	b.deprecated = true
   223  	return b
   224  }
   225  
   226  // AllowedMethodsWithoutContentType overrides the default list GET,HEAD,OPTIONS,DELETE,TRACE
   227  // If a request does not include a content-type header then
   228  // depending on the method, it may return a 415 Unsupported Media.
   229  // Must have uppercase HTTP Method names such as GET,HEAD,OPTIONS,...
   230  func (b *RouteBuilder) AllowedMethodsWithoutContentType(methods []string) *RouteBuilder {
   231  	b.allowedMethodsWithoutContentType = methods
   232  	return b
   233  }
   234  
   235  // ResponseError represents a response; not necessarily an error.
   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  // Header describes a header for a response of the API
   246  //
   247  // For more information: http://goo.gl/8us55a#headerObject
   248  type Header struct {
   249  	*Items
   250  	Description string
   251  }
   252  
   253  // Items describe swagger simple schemas for headers
   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  // Filter appends a FilterFunction to the end of filters for this Route to build.
   268  func (b *RouteBuilder) Filter(filter FilterFunction) *RouteBuilder {
   269  	b.filters = append(b.filters, filter)
   270  	return b
   271  }
   272  
   273  // If sets a condition function that controls matching the Route based on custom logic.
   274  // The condition function is provided the HTTP request and should return true if the route
   275  // should be considered.
   276  //
   277  // Efficiency note: the condition function is called before checking the method, produces, and
   278  // consumes criteria, so that the correct HTTP status code can be returned.
   279  //
   280  // Lifecycle note: no filter functions have been called prior to calling the condition function,
   281  // so the condition function should not depend on any context that might be set up by container
   282  // or route filters.
   283  func (b *RouteBuilder) If(condition RouteSelectionConditionFunction) *RouteBuilder {
   284  	b.conditions = append(b.conditions, condition)
   285  	return b
   286  }
   287  
   288  // ContentEncodingEnabled allows you to override the Containers value for auto-compressing this route response.
   289  func (b *RouteBuilder) ContentEncodingEnabled(enabled bool) *RouteBuilder {
   290  	b.contentEncodingEnabled = &enabled
   291  	return b
   292  }
   293  
   294  // If no specific Route path then set to rootPath
   295  // If no specific Produces then set to rootProduces
   296  // If no specific Consumes then set to rootConsumes
   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  // typeNameHandler sets the function that will convert types to strings in the parameter
   307  // and model definitions.
   308  func (b *RouteBuilder) typeNameHandler(handler TypeNameHandleFunction) *RouteBuilder {
   309  	b.typeNameHandleFunc = handler
   310  	return b
   311  }
   312  
   313  // Build creates a new Route using the specification details collected by the RouteBuilder
   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  		// extract from definition
   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  	// set WriteSample if one specified
   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  // merge two paths using the current (package global) merge path strategy.
   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  // nameOfFunction returns the short name of the function f for documentation.
   374  // It uses a runtime feature for debugging ; its value may change for later Go versions.
   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") // < Go 1.5
   380  	last = strings.TrimSuffix(last, ")-fm") // Go 1.5
   381  	last = strings.TrimSuffix(last, "·fm")  // < Go 1.5
   382  	last = strings.TrimSuffix(last, "-fm")  // Go 1.5
   383  	if last == "func1" {                    // this could mean conflicts in API docs
   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