mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 01:53:42 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			390 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			390 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package restful
 | 
						|
 | 
						|
// Copyright 2013 Ernest Micklei. All rights reserved.
 | 
						|
// Use of this source code is governed by a license
 | 
						|
// that can be found in the LICENSE file.
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"os"
 | 
						|
	"path"
 | 
						|
	"reflect"
 | 
						|
	"runtime"
 | 
						|
	"strings"
 | 
						|
	"sync/atomic"
 | 
						|
 | 
						|
	"github.com/emicklei/go-restful/v3/log"
 | 
						|
)
 | 
						|
 | 
						|
// RouteBuilder is a helper to construct Routes.
 | 
						|
type RouteBuilder struct {
 | 
						|
	rootPath                         string
 | 
						|
	currentPath                      string
 | 
						|
	produces                         []string
 | 
						|
	consumes                         []string
 | 
						|
	httpMethod                       string        // required
 | 
						|
	function                         RouteFunction // required
 | 
						|
	filters                          []FilterFunction
 | 
						|
	conditions                       []RouteSelectionConditionFunction
 | 
						|
	allowedMethodsWithoutContentType []string // see Route
 | 
						|
 | 
						|
	typeNameHandleFunc TypeNameHandleFunction // required
 | 
						|
 | 
						|
	// documentation
 | 
						|
	doc                    string
 | 
						|
	notes                  string
 | 
						|
	operation              string
 | 
						|
	readSample             interface{}
 | 
						|
	writeSamples           []interface{}
 | 
						|
	parameters             []*Parameter
 | 
						|
	errorMap               map[int]ResponseError
 | 
						|
	defaultResponse        *ResponseError
 | 
						|
	metadata               map[string]interface{}
 | 
						|
	extensions             map[string]interface{}
 | 
						|
	deprecated             bool
 | 
						|
	contentEncodingEnabled *bool
 | 
						|
}
 | 
						|
 | 
						|
// Do evaluates each argument with the RouteBuilder itself.
 | 
						|
// This allows you to follow DRY principles without breaking the fluent programming style.
 | 
						|
// Example:
 | 
						|
//
 | 
						|
//	ws.Route(ws.DELETE("/{name}").To(t.deletePerson).Do(Returns200, Returns500))
 | 
						|
//
 | 
						|
//	func Returns500(b *RouteBuilder) {
 | 
						|
//		b.Returns(500, "Internal Server Error", restful.ServiceError{})
 | 
						|
//	}
 | 
						|
func (b *RouteBuilder) Do(oneArgBlocks ...func(*RouteBuilder)) *RouteBuilder {
 | 
						|
	for _, each := range oneArgBlocks {
 | 
						|
		each(b)
 | 
						|
	}
 | 
						|
	return b
 | 
						|
}
 | 
						|
 | 
						|
// To bind the route to a function.
 | 
						|
// If this route is matched with the incoming Http Request then call this function with the *Request,*Response pair. Required.
 | 
						|
func (b *RouteBuilder) To(function RouteFunction) *RouteBuilder {
 | 
						|
	b.function = function
 | 
						|
	return b
 | 
						|
}
 | 
						|
 | 
						|
// Method specifies what HTTP method to match. Required.
 | 
						|
func (b *RouteBuilder) Method(method string) *RouteBuilder {
 | 
						|
	b.httpMethod = method
 | 
						|
	return b
 | 
						|
}
 | 
						|
 | 
						|
// Produces specifies what MIME types can be produced ; the matched one will appear in the Content-Type Http header.
 | 
						|
func (b *RouteBuilder) Produces(mimeTypes ...string) *RouteBuilder {
 | 
						|
	b.produces = mimeTypes
 | 
						|
	return b
 | 
						|
}
 | 
						|
 | 
						|
// Consumes specifies what MIME types can be consumes ; the Accept Http header must matched any of these
 | 
						|
func (b *RouteBuilder) Consumes(mimeTypes ...string) *RouteBuilder {
 | 
						|
	b.consumes = mimeTypes
 | 
						|
	return b
 | 
						|
}
 | 
						|
 | 
						|
// Path specifies the relative (w.r.t WebService root path) URL path to match. Default is "/".
 | 
						|
func (b *RouteBuilder) Path(subPath string) *RouteBuilder {
 | 
						|
	b.currentPath = subPath
 | 
						|
	return b
 | 
						|
}
 | 
						|
 | 
						|
// Doc tells what this route is all about. Optional.
 | 
						|
func (b *RouteBuilder) Doc(documentation string) *RouteBuilder {
 | 
						|
	b.doc = documentation
 | 
						|
	return b
 | 
						|
}
 | 
						|
 | 
						|
// Notes is a verbose explanation of the operation behavior. Optional.
 | 
						|
func (b *RouteBuilder) Notes(notes string) *RouteBuilder {
 | 
						|
	b.notes = notes
 | 
						|
	return b
 | 
						|
}
 | 
						|
 | 
						|
// Reads tells what resource type will be read from the request payload. Optional.
 | 
						|
// 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.
 | 
						|
func (b *RouteBuilder) Reads(sample interface{}, optionalDescription ...string) *RouteBuilder {
 | 
						|
	fn := b.typeNameHandleFunc
 | 
						|
	if fn == nil {
 | 
						|
		fn = reflectTypeName
 | 
						|
	}
 | 
						|
	typeAsName := fn(sample)
 | 
						|
	description := ""
 | 
						|
	if len(optionalDescription) > 0 {
 | 
						|
		description = optionalDescription[0]
 | 
						|
	}
 | 
						|
	b.readSample = sample
 | 
						|
	bodyParameter := &Parameter{&ParameterData{Name: "body", Description: description}}
 | 
						|
	bodyParameter.beBody()
 | 
						|
	bodyParameter.Required(true)
 | 
						|
	bodyParameter.DataType(typeAsName)
 | 
						|
	b.Param(bodyParameter)
 | 
						|
	return b
 | 
						|
}
 | 
						|
 | 
						|
// ParameterNamed returns a Parameter already known to the RouteBuilder. Returns nil if not.
 | 
						|
// Use this to modify or extend information for the Parameter (through its Data()).
 | 
						|
func (b RouteBuilder) ParameterNamed(name string) (p *Parameter) {
 | 
						|
	for _, each := range b.parameters {
 | 
						|
		if each.Data().Name == name {
 | 
						|
			return each
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
// Writes tells which one of the resource types will be written as the response payload. Optional.
 | 
						|
func (b *RouteBuilder) Writes(samples ...interface{}) *RouteBuilder {
 | 
						|
	b.writeSamples = samples // oneof
 | 
						|
	return b
 | 
						|
}
 | 
						|
 | 
						|
// Param allows you to document the parameters of the Route. It adds a new Parameter (does not check for duplicates).
 | 
						|
func (b *RouteBuilder) Param(parameter *Parameter) *RouteBuilder {
 | 
						|
	if b.parameters == nil {
 | 
						|
		b.parameters = []*Parameter{}
 | 
						|
	}
 | 
						|
	b.parameters = append(b.parameters, parameter)
 | 
						|
	return b
 | 
						|
}
 | 
						|
 | 
						|
// Operation allows you to document what the actual method/function call is of the Route.
 | 
						|
// Unless called, the operation name is derived from the RouteFunction set using To(..).
 | 
						|
func (b *RouteBuilder) Operation(name string) *RouteBuilder {
 | 
						|
	b.operation = name
 | 
						|
	return b
 | 
						|
}
 | 
						|
 | 
						|
// ReturnsError is deprecated, use Returns instead.
 | 
						|
func (b *RouteBuilder) ReturnsError(code int, message string, model interface{}) *RouteBuilder {
 | 
						|
	log.Print("ReturnsError is deprecated, use Returns instead.")
 | 
						|
	return b.Returns(code, message, model)
 | 
						|
}
 | 
						|
 | 
						|
// Returns allows you to document what responses (errors or regular) can be expected.
 | 
						|
// The model parameter is optional ; either pass a struct instance or use nil if not applicable.
 | 
						|
func (b *RouteBuilder) Returns(code int, message string, model interface{}) *RouteBuilder {
 | 
						|
	err := ResponseError{
 | 
						|
		Code:      code,
 | 
						|
		Message:   message,
 | 
						|
		Model:     model,
 | 
						|
		IsDefault: false, // this field is deprecated, use default response instead.
 | 
						|
	}
 | 
						|
	// lazy init because there is no NewRouteBuilder (yet)
 | 
						|
	if b.errorMap == nil {
 | 
						|
		b.errorMap = map[int]ResponseError{}
 | 
						|
	}
 | 
						|
	b.errorMap[code] = err
 | 
						|
	return b
 | 
						|
}
 | 
						|
 | 
						|
// ReturnsWithHeaders is similar to Returns, but can specify response headers
 | 
						|
func (b *RouteBuilder) ReturnsWithHeaders(code int, message string, model interface{}, headers map[string]Header) *RouteBuilder {
 | 
						|
	b.Returns(code, message, model)
 | 
						|
	err := b.errorMap[code]
 | 
						|
	err.Headers = headers
 | 
						|
	b.errorMap[code] = err
 | 
						|
	return b
 | 
						|
}
 | 
						|
 | 
						|
// DefaultReturns is a special Returns call that sets the default of the response.
 | 
						|
func (b *RouteBuilder) DefaultReturns(message string, model interface{}) *RouteBuilder {
 | 
						|
	b.defaultResponse = &ResponseError{
 | 
						|
		Message: message,
 | 
						|
		Model:   model,
 | 
						|
	}
 | 
						|
	return b
 | 
						|
}
 | 
						|
 | 
						|
// Metadata adds or updates a key=value pair to the metadata map.
 | 
						|
func (b *RouteBuilder) Metadata(key string, value interface{}) *RouteBuilder {
 | 
						|
	if b.metadata == nil {
 | 
						|
		b.metadata = map[string]interface{}{}
 | 
						|
	}
 | 
						|
	b.metadata[key] = value
 | 
						|
	return b
 | 
						|
}
 | 
						|
 | 
						|
// AddExtension adds or updates a key=value pair to the extensions map.
 | 
						|
func (b *RouteBuilder) AddExtension(key string, value interface{}) *RouteBuilder {
 | 
						|
	if b.extensions == nil {
 | 
						|
		b.extensions = map[string]interface{}{}
 | 
						|
	}
 | 
						|
	b.extensions[key] = value
 | 
						|
	return b
 | 
						|
}
 | 
						|
 | 
						|
// Deprecate sets the value of deprecated to true.  Deprecated routes have a special UI treatment to warn against use
 | 
						|
func (b *RouteBuilder) Deprecate() *RouteBuilder {
 | 
						|
	b.deprecated = true
 | 
						|
	return b
 | 
						|
}
 | 
						|
 | 
						|
// AllowedMethodsWithoutContentType overrides the default list GET,HEAD,OPTIONS,DELETE,TRACE
 | 
						|
// If a request does not include a content-type header then
 | 
						|
// depending on the method, it may return a 415 Unsupported Media.
 | 
						|
// Must have uppercase HTTP Method names such as GET,HEAD,OPTIONS,...
 | 
						|
func (b *RouteBuilder) AllowedMethodsWithoutContentType(methods []string) *RouteBuilder {
 | 
						|
	b.allowedMethodsWithoutContentType = methods
 | 
						|
	return b
 | 
						|
}
 | 
						|
 | 
						|
// ResponseError represents a response; not necessarily an error.
 | 
						|
type ResponseError struct {
 | 
						|
	ExtensionProperties
 | 
						|
	Code      int
 | 
						|
	Message   string
 | 
						|
	Model     interface{}
 | 
						|
	Headers   map[string]Header
 | 
						|
	IsDefault bool
 | 
						|
}
 | 
						|
 | 
						|
// Header describes a header for a response of the API
 | 
						|
//
 | 
						|
// For more information: http://goo.gl/8us55a#headerObject
 | 
						|
type Header struct {
 | 
						|
	*Items
 | 
						|
	Description string
 | 
						|
}
 | 
						|
 | 
						|
// Items describe swagger simple schemas for headers
 | 
						|
type Items struct {
 | 
						|
	Type             string
 | 
						|
	Format           string
 | 
						|
	Items            *Items
 | 
						|
	CollectionFormat string
 | 
						|
	Default          interface{}
 | 
						|
}
 | 
						|
 | 
						|
func (b *RouteBuilder) servicePath(path string) *RouteBuilder {
 | 
						|
	b.rootPath = path
 | 
						|
	return b
 | 
						|
}
 | 
						|
 | 
						|
// Filter appends a FilterFunction to the end of filters for this Route to build.
 | 
						|
func (b *RouteBuilder) Filter(filter FilterFunction) *RouteBuilder {
 | 
						|
	b.filters = append(b.filters, filter)
 | 
						|
	return b
 | 
						|
}
 | 
						|
 | 
						|
// If sets a condition function that controls matching the Route based on custom logic.
 | 
						|
// The condition function is provided the HTTP request and should return true if the route
 | 
						|
// should be considered.
 | 
						|
//
 | 
						|
// Efficiency note: the condition function is called before checking the method, produces, and
 | 
						|
// consumes criteria, so that the correct HTTP status code can be returned.
 | 
						|
//
 | 
						|
// Lifecycle note: no filter functions have been called prior to calling the condition function,
 | 
						|
// so the condition function should not depend on any context that might be set up by container
 | 
						|
// or route filters.
 | 
						|
func (b *RouteBuilder) If(condition RouteSelectionConditionFunction) *RouteBuilder {
 | 
						|
	b.conditions = append(b.conditions, condition)
 | 
						|
	return b
 | 
						|
}
 | 
						|
 | 
						|
// ContentEncodingEnabled allows you to override the Containers value for auto-compressing this route response.
 | 
						|
func (b *RouteBuilder) ContentEncodingEnabled(enabled bool) *RouteBuilder {
 | 
						|
	b.contentEncodingEnabled = &enabled
 | 
						|
	return b
 | 
						|
}
 | 
						|
 | 
						|
// If no specific Route path then set to rootPath
 | 
						|
// If no specific Produces then set to rootProduces
 | 
						|
// If no specific Consumes then set to rootConsumes
 | 
						|
func (b *RouteBuilder) copyDefaults(rootProduces, rootConsumes []string) {
 | 
						|
	if len(b.produces) == 0 {
 | 
						|
		b.produces = rootProduces
 | 
						|
	}
 | 
						|
	if len(b.consumes) == 0 {
 | 
						|
		b.consumes = rootConsumes
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// typeNameHandler sets the function that will convert types to strings in the parameter
 | 
						|
// and model definitions.
 | 
						|
func (b *RouteBuilder) typeNameHandler(handler TypeNameHandleFunction) *RouteBuilder {
 | 
						|
	b.typeNameHandleFunc = handler
 | 
						|
	return b
 | 
						|
}
 | 
						|
 | 
						|
// Build creates a new Route using the specification details collected by the RouteBuilder
 | 
						|
func (b *RouteBuilder) Build() Route {
 | 
						|
	pathExpr, err := newPathExpression(b.currentPath)
 | 
						|
	if err != nil {
 | 
						|
		log.Printf("Invalid path:%s because:%v", b.currentPath, err)
 | 
						|
		os.Exit(1)
 | 
						|
	}
 | 
						|
	if b.function == nil {
 | 
						|
		log.Printf("No function specified for route:" + b.currentPath)
 | 
						|
		os.Exit(1)
 | 
						|
	}
 | 
						|
	operationName := b.operation
 | 
						|
	if len(operationName) == 0 && b.function != nil {
 | 
						|
		// extract from definition
 | 
						|
		operationName = nameOfFunction(b.function)
 | 
						|
	}
 | 
						|
	route := Route{
 | 
						|
		Method:                           b.httpMethod,
 | 
						|
		Path:                             concatPath(b.rootPath, b.currentPath),
 | 
						|
		Produces:                         b.produces,
 | 
						|
		Consumes:                         b.consumes,
 | 
						|
		Function:                         b.function,
 | 
						|
		Filters:                          b.filters,
 | 
						|
		If:                               b.conditions,
 | 
						|
		relativePath:                     b.currentPath,
 | 
						|
		pathExpr:                         pathExpr,
 | 
						|
		Doc:                              b.doc,
 | 
						|
		Notes:                            b.notes,
 | 
						|
		Operation:                        operationName,
 | 
						|
		ParameterDocs:                    b.parameters,
 | 
						|
		ResponseErrors:                   b.errorMap,
 | 
						|
		DefaultResponse:                  b.defaultResponse,
 | 
						|
		ReadSample:                       b.readSample,
 | 
						|
		WriteSamples:                     b.writeSamples,
 | 
						|
		Metadata:                         b.metadata,
 | 
						|
		Deprecated:                       b.deprecated,
 | 
						|
		contentEncodingEnabled:           b.contentEncodingEnabled,
 | 
						|
		allowedMethodsWithoutContentType: b.allowedMethodsWithoutContentType,
 | 
						|
	}
 | 
						|
	// set WriteSample if one specified
 | 
						|
	if len(b.writeSamples) == 1 {
 | 
						|
		route.WriteSample = b.writeSamples[0]
 | 
						|
	}
 | 
						|
	route.Extensions = b.extensions
 | 
						|
	route.postBuild()
 | 
						|
	return route
 | 
						|
}
 | 
						|
 | 
						|
// merge two paths using the current (package global) merge path strategy.
 | 
						|
func concatPath(rootPath, routePath string) string {
 | 
						|
 | 
						|
	if TrimRightSlashEnabled {
 | 
						|
		return strings.TrimRight(rootPath, "/") + "/" + strings.TrimLeft(routePath, "/")
 | 
						|
	} else {
 | 
						|
		return path.Join(rootPath, routePath)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
var anonymousFuncCount int32
 | 
						|
 | 
						|
// nameOfFunction returns the short name of the function f for documentation.
 | 
						|
// It uses a runtime feature for debugging ; its value may change for later Go versions.
 | 
						|
func nameOfFunction(f interface{}) string {
 | 
						|
	fun := runtime.FuncForPC(reflect.ValueOf(f).Pointer())
 | 
						|
	tokenized := strings.Split(fun.Name(), ".")
 | 
						|
	last := tokenized[len(tokenized)-1]
 | 
						|
	last = strings.TrimSuffix(last, ")·fm") // < Go 1.5
 | 
						|
	last = strings.TrimSuffix(last, ")-fm") // Go 1.5
 | 
						|
	last = strings.TrimSuffix(last, "·fm")  // < Go 1.5
 | 
						|
	last = strings.TrimSuffix(last, "-fm")  // Go 1.5
 | 
						|
	if last == "func1" {                    // this could mean conflicts in API docs
 | 
						|
		val := atomic.AddInt32(&anonymousFuncCount, 1)
 | 
						|
		last = "func" + fmt.Sprintf("%d", val)
 | 
						|
		atomic.StoreInt32(&anonymousFuncCount, val)
 | 
						|
	}
 | 
						|
	return last
 | 
						|
}
 |