1
0
mirror of https://github.com/rancher/norman.git synced 2025-09-07 10:10:06 +00:00
This commit is contained in:
Darren Shepherd
2017-12-05 09:21:12 -07:00
parent 5ec6a8d719
commit 18d3f69aa8
17 changed files with 362 additions and 88 deletions

View File

@@ -2,11 +2,11 @@ package parse
import (
"net/http"
"net/url"
"regexp"
"strings"
"github.com/rancher/norman/api/builtin"
"github.com/rancher/norman/httperror"
"github.com/rancher/norman/types"
"github.com/rancher/norman/urlbuilder"
)
@@ -23,20 +23,83 @@ var (
}
)
type ParsedURL struct {
Version string
Type string
ID string
Link string
Method string
Action string
SubContext map[string]string
SubContextPrefix string
}
type ResolverFunc func(typeName string, context *types.APIContext) error
func Parse(rw http.ResponseWriter, req *http.Request, schemas *types.Schemas, resolverFunc ResolverFunc) (*types.APIContext, error) {
type URLParser func(schema *types.Schemas, url *url.URL) (ParsedURL, error)
func DefaultURLParser(schemas *types.Schemas, url *url.URL) (ParsedURL, error) {
result := ParsedURL{}
version := Version(schemas, url.Path)
if version == nil {
return result, nil
}
path := url.Path
path = multiSlashRegexp.ReplaceAllString(path, "/")
parts := strings.SplitN(path[len(version.Path):], "/", 4)
prefix, parts, subContext := parseSubContext(version, parts)
result.Version = version.Path
result.SubContext = subContext
result.SubContextPrefix = prefix
result.Action, result.Method = parseAction(url)
result.Type = safeIndex(parts, 1)
result.ID = safeIndex(parts, 2)
result.Link = safeIndex(parts, 3)
return result, nil
}
func Parse(rw http.ResponseWriter, req *http.Request, schemas *types.Schemas, urlParser URLParser, resolverFunc ResolverFunc) (*types.APIContext, error) {
var err error
result := &types.APIContext{
Request: req,
Response: rw,
Schemas: schemas,
Request: req,
Response: rw,
Method: parseMethod(req),
ResponseFormat: parseResponseFormat(req),
}
result.URLBuilder, _ = urlbuilder.New(req, types.APIVersion{}, schemas)
// The response format is guarenteed to be set even in the event of an error
result.ResponseFormat = parseResponseFormat(req)
result.Version = parseVersion(schemas, req.URL.Path)
result.Schemas = schemas
parsedURL, err := urlParser(schemas, req.URL)
// wait to check error, want to set as much as possible
result.SubContext = parsedURL.SubContext
result.Type = parsedURL.Type
result.ID = parsedURL.ID
result.Link = parsedURL.Link
result.Action = parsedURL.Action
if parsedURL.Method != "" {
result.Method = parsedURL.Method
}
for i, version := range schemas.Versions() {
if version.Path == parsedURL.Version {
result.Version = &schemas.Versions()[i]
break
}
}
if err != nil {
return result, err
}
if result.Version == nil {
result.Method = http.MethodGet
@@ -46,15 +109,16 @@ func Parse(rw http.ResponseWriter, req *http.Request, schemas *types.Schemas, re
return result, nil
}
result.Method = parseMethod(req)
result.Action, result.Method = parseAction(req, result.Method)
result.URLBuilder, err = urlbuilder.New(req, *result.Version, result.Schemas)
if err != nil {
return result, err
}
if err := parsePath(result, req, resolverFunc); err != nil {
if parsedURL.SubContextPrefix != "" {
result.URLBuilder.SetSubContext(parsedURL.SubContextPrefix)
}
if err := resolverFunc(result.Type, result); err != nil {
return result, err
}
@@ -66,6 +130,8 @@ func Parse(rw http.ResponseWriter, req *http.Request, schemas *types.Schemas, re
return result, nil
}
result.Type = result.Schema.ID
if err := ValidateMethod(result); err != nil {
return result, err
}
@@ -73,33 +139,24 @@ func Parse(rw http.ResponseWriter, req *http.Request, schemas *types.Schemas, re
return result, nil
}
func parseSubContext(parts []string, apiRequest *types.APIContext) []string {
func parseSubContext(version *types.APIVersion, parts []string) (string, []string, map[string]string) {
subContext := ""
apiRequest.SubContext = map[string]string{}
apiRequest.Attributes = map[string]interface{}{}
result := map[string]string{}
for len(parts) > 3 && apiRequest.Version != nil && parts[3] != "" {
for len(parts) > 3 && version != nil && parts[3] != "" {
resourceType := parts[1]
resourceID := parts[2]
if !apiRequest.Version.SubContexts[resourceType] {
if !version.SubContexts[resourceType] {
break
}
if apiRequest.ReferenceValidator != nil && !apiRequest.ReferenceValidator.Validate(resourceType, resourceID) {
return parts
}
apiRequest.SubContext[resourceType] = resourceID
result[resourceType] = resourceID
subContext = subContext + "/" + resourceType + "/" + resourceID
parts = append(parts[:1], parts[3:]...)
}
if subContext != "" {
apiRequest.URLBuilder.SetSubContext(subContext)
}
return parts
return subContext, parts, result
}
func DefaultResolver(typeName string, apiContext *types.APIContext) error {
@@ -120,50 +177,6 @@ func DefaultResolver(typeName string, apiContext *types.APIContext) error {
return nil
}
func parsePath(apiRequest *types.APIContext, request *http.Request, resolverFunc ResolverFunc) error {
if apiRequest.Version == nil {
return nil
}
path := request.URL.Path
path = multiSlashRegexp.ReplaceAllString(path, "/")
versionPrefix := apiRequest.Version.Path
if !strings.HasPrefix(path, versionPrefix) {
return nil
}
parts := strings.Split(path[len(versionPrefix):], "/")
parts = parseSubContext(parts, apiRequest)
if len(parts) > 4 {
return httperror.NewAPIError(httperror.NotFound, "No handler for path")
}
typeName := safeIndex(parts, 1)
id := safeIndex(parts, 2)
link := safeIndex(parts, 3)
if err := resolverFunc(typeName, apiRequest); err != nil {
return err
}
if apiRequest.Schema == nil {
return nil
}
apiRequest.Type = apiRequest.Schema.ID
if id == "" {
return nil
}
apiRequest.ID = id
apiRequest.Link = link
return nil
}
func safeIndex(slice []string, index int) string {
if index >= len(slice) {
return ""
@@ -198,20 +211,16 @@ func parseMethod(req *http.Request) string {
return method
}
func parseAction(req *http.Request, method string) (string, string) {
if req.Method != http.MethodPost {
return "", method
}
action := req.URL.Query().Get("action")
func parseAction(url *url.URL) (string, string) {
action := url.Query().Get("action")
if action == "remove" {
return "", http.MethodDelete
}
return action, method
return action, ""
}
func parseVersion(schemas *types.Schemas, path string) *types.APIVersion {
func Version(schemas *types.Schemas, path string) *types.APIVersion {
path = multiSlashRegexp.ReplaceAllString(path, "/")
for _, version := range schemas.Versions() {
if version.Path == "" {