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