package restful import ( "errors" "os" "reflect" "sync" "github.com/emicklei/go-restful/log" ) // 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. // WebService holds a collection of Route values that bind a Http Method + URL Path to a function. type WebService struct { rootPath string pathExpr *pathExpression // cached compilation of rootPath as RegExp routes []Route produces []string consumes []string pathParameters []*Parameter filters []FilterFunction documentation string apiVersion string typeNameHandleFunc TypeNameHandleFunction dynamicRoutes bool // protects 'routes' if dynamic routes are enabled routesLock sync.RWMutex } func (w *WebService) SetDynamicRoutes(enable bool) { w.dynamicRoutes = enable } // TypeNameHandleFunction declares functions that can handle translating the name of a sample object // into the restful documentation for the service. type TypeNameHandleFunction func(sample interface{}) string // TypeNameHandler sets the function that will convert types to strings in the parameter // and model definitions. If not set, the web service will invoke // reflect.TypeOf(object).String(). func (w *WebService) TypeNameHandler(handler TypeNameHandleFunction) *WebService { w.typeNameHandleFunc = handler return w } // reflectTypeName is the default TypeNameHandleFunction and for a given object // returns the name that Go identifies it with (e.g. "string" or "v1.Object") via // the reflection API. func reflectTypeName(sample interface{}) string { return reflect.TypeOf(sample).String() } // compilePathExpression ensures that the path is compiled into a RegEx for those routers that need it. func (w *WebService) compilePathExpression() { compiled, err := newPathExpression(w.rootPath) if err != nil { log.Printf("invalid path:%s because:%v", w.rootPath, err) os.Exit(1) } w.pathExpr = compiled } // ApiVersion sets the API version for documentation purposes. func (w *WebService) ApiVersion(apiVersion string) *WebService { w.apiVersion = apiVersion return w } // Version returns the API version for documentation purposes. func (w *WebService) Version() string { return w.apiVersion } // Path specifies the root URL template path of the WebService. // All Routes will be relative to this path. func (w *WebService) Path(root string) *WebService { w.rootPath = root if len(w.rootPath) == 0 { w.rootPath = "/" } w.compilePathExpression() return w } // Param adds a PathParameter to document parameters used in the root path. func (w *WebService) Param(parameter *Parameter) *WebService { if w.pathParameters == nil { w.pathParameters = []*Parameter{} } w.pathParameters = append(w.pathParameters, parameter) return w } // PathParameter creates a new Parameter of kind Path for documentation purposes. // It is initialized as required with string as its DataType. func (w *WebService) PathParameter(name, description string) *Parameter { return PathParameter(name, description) } // PathParameter creates a new Parameter of kind Path for documentation purposes. // It is initialized as required with string as its DataType. func PathParameter(name, description string) *Parameter { p := &Parameter{&ParameterData{Name: name, Description: description, Required: true, DataType: "string"}} p.bePath() return p } // QueryParameter creates a new Parameter of kind Query for documentation purposes. // It is initialized as not required with string as its DataType. func (w *WebService) QueryParameter(name, description string) *Parameter { return QueryParameter(name, description) } // QueryParameter creates a new Parameter of kind Query for documentation purposes. // It is initialized as not required with string as its DataType. func QueryParameter(name, description string) *Parameter { p := &Parameter{&ParameterData{Name: name, Description: description, Required: false, DataType: "string", CollectionFormat: CollectionFormatCSV.String()}} p.beQuery() return p } // BodyParameter creates a new Parameter of kind Body for documentation purposes. // It is initialized as required without a DataType. func (w *WebService) BodyParameter(name, description string) *Parameter { return BodyParameter(name, description) } // BodyParameter creates a new Parameter of kind Body for documentation purposes. // It is initialized as required without a DataType. func BodyParameter(name, description string) *Parameter { p := &Parameter{&ParameterData{Name: name, Description: description, Required: true}} p.beBody() return p } // HeaderParameter creates a new Parameter of kind (Http) Header for documentation purposes. // It is initialized as not required with string as its DataType. func (w *WebService) HeaderParameter(name, description string) *Parameter { return HeaderParameter(name, description) } // HeaderParameter creates a new Parameter of kind (Http) Header for documentation purposes. // It is initialized as not required with string as its DataType. func HeaderParameter(name, description string) *Parameter { p := &Parameter{&ParameterData{Name: name, Description: description, Required: false, DataType: "string"}} p.beHeader() return p } // FormParameter creates a new Parameter of kind Form (using application/x-www-form-urlencoded) for documentation purposes. // It is initialized as required with string as its DataType. func (w *WebService) FormParameter(name, description string) *Parameter { return FormParameter(name, description) } // FormParameter creates a new Parameter of kind Form (using application/x-www-form-urlencoded) for documentation purposes. // It is initialized as required with string as its DataType. func FormParameter(name, description string) *Parameter { p := &Parameter{&ParameterData{Name: name, Description: description, Required: false, DataType: "string"}} p.beForm() return p } // Route creates a new Route using the RouteBuilder and add to the ordered list of Routes. func (w *WebService) Route(builder *RouteBuilder) *WebService { w.routesLock.Lock() defer w.routesLock.Unlock() builder.copyDefaults(w.produces, w.consumes) w.routes = append(w.routes, builder.Build()) return w } // RemoveRoute removes the specified route, looks for something that matches 'path' and 'method' func (w *WebService) RemoveRoute(path, method string) error { if !w.dynamicRoutes { return errors.New("dynamic routes are not enabled.") } w.routesLock.Lock() defer w.routesLock.Unlock() newRoutes := make([]Route, (len(w.routes) - 1)) current := 0 for ix := range w.routes { if w.routes[ix].Method == method && w.routes[ix].Path == path { continue } newRoutes[current] = w.routes[ix] current++ } w.routes = newRoutes return nil } // Method creates a new RouteBuilder and initialize its http method func (w *WebService) Method(httpMethod string) *RouteBuilder { return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method(httpMethod) } // Produces specifies that this WebService can produce one or more MIME types. // Http requests must have one of these values set for the Accept header. func (w *WebService) Produces(contentTypes ...string) *WebService { w.produces = contentTypes return w } // Consumes specifies that this WebService can consume one or more MIME types. // Http requests must have one of these values set for the Content-Type header. func (w *WebService) Consumes(accepts ...string) *WebService { w.consumes = accepts return w } // Routes returns the Routes associated with this WebService func (w *WebService) Routes() []Route { if !w.dynamicRoutes { return w.routes } // Make a copy of the array to prevent concurrency problems w.routesLock.RLock() defer w.routesLock.RUnlock() result := make([]Route, len(w.routes)) for ix := range w.routes { result[ix] = w.routes[ix] } return result } // RootPath returns the RootPath associated with this WebService. Default "/" func (w *WebService) RootPath() string { return w.rootPath } // PathParameters return the path parameter names for (shared among its Routes) func (w *WebService) PathParameters() []*Parameter { return w.pathParameters } // Filter adds a filter function to the chain of filters applicable to all its Routes func (w *WebService) Filter(filter FilterFunction) *WebService { w.filters = append(w.filters, filter) return w } // Doc is used to set the documentation of this service. func (w *WebService) Doc(plainText string) *WebService { w.documentation = plainText return w } // Documentation returns it. func (w *WebService) Documentation() string { return w.documentation } /* Convenience methods */ // HEAD is a shortcut for .Method("HEAD").Path(subPath) func (w *WebService) HEAD(subPath string) *RouteBuilder { return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("HEAD").Path(subPath) } // GET is a shortcut for .Method("GET").Path(subPath) func (w *WebService) GET(subPath string) *RouteBuilder { return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("GET").Path(subPath) } // POST is a shortcut for .Method("POST").Path(subPath) func (w *WebService) POST(subPath string) *RouteBuilder { return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("POST").Path(subPath) } // PUT is a shortcut for .Method("PUT").Path(subPath) func (w *WebService) PUT(subPath string) *RouteBuilder { return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("PUT").Path(subPath) } // PATCH is a shortcut for .Method("PATCH").Path(subPath) func (w *WebService) PATCH(subPath string) *RouteBuilder { return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("PATCH").Path(subPath) } // DELETE is a shortcut for .Method("DELETE").Path(subPath) func (w *WebService) DELETE(subPath string) *RouteBuilder { return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("DELETE").Path(subPath) }