github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/api/uaa/internal/routing.go (about)

     1  package internal
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"net/http"
     7  	"net/url"
     8  	"path"
     9  	"strings"
    10  )
    11  
    12  // Params map path keys to values.  For example, if your route has the path
    13  // pattern:
    14  //   /person/:person_id/pets/:pet_type
    15  // Then a correct Params map would look like:
    16  //   router.Params{
    17  //     "person_id": "123",
    18  //     "pet_type": "cats",
    19  //   }
    20  type Params map[string]string
    21  
    22  // Route defines the property of a Cloud Controller V3 endpoint.
    23  //
    24  // Method can be one of the following:
    25  //  GET HEAD POST PUT PATCH DELETE CONNECT OPTIONS TRACE
    26  //
    27  // Path conforms to Pat-style pattern matching. The following docs are taken
    28  // from http://godoc.org/github.com/bmizerany/pat#PatternServeMux
    29  //
    30  // Path Patterns may contain literals or captures. Capture names start with a
    31  // colon and consist of letters A-Z, a-z, _, and 0-9. The rest of the pattern
    32  // matches literally. The portion of the URL matching each name ends with an
    33  // occurrence of the character in the pattern immediately following the name,
    34  // or a /, whichever comes first. It is possible for a name to match the empty
    35  // string.
    36  //
    37  // Example pattern with one capture:
    38  //   /hello/:name
    39  // Will match:
    40  //   /hello/blake
    41  //   /hello/keith
    42  // Will not match:
    43  //   /hello/blake/
    44  //   /hello/blake/foo
    45  //   /foo
    46  //   /foo/bar
    47  //
    48  // Example 2:
    49  //    /hello/:name/
    50  // Will match:
    51  //   /hello/blake/
    52  //   /hello/keith/foo
    53  //   /hello/blake
    54  //   /hello/keith
    55  // Will not match:
    56  //   /foo
    57  //   /foo/bar
    58  type Route struct {
    59  	// Name is a key specifying which HTTP route the router should associate with
    60  	// the endpoint at runtime.
    61  	Name string
    62  	// Method is any valid HTTP method
    63  	Method string
    64  	// Path contains a path pattern
    65  	Path string
    66  	// Resource is a key specifying which resource root the router should
    67  	// associate with the endpoint at runtime.
    68  	Resource string
    69  }
    70  
    71  // CreatePath combines the route's path pattern with a Params map
    72  // to produce a valid path.
    73  func (r Route) CreatePath(params Params) (string, error) {
    74  	components := strings.Split(r.Path, "/")
    75  	for i, c := range components {
    76  		if len(c) == 0 {
    77  			continue
    78  		}
    79  		if c[0] == ':' {
    80  			val, ok := params[c[1:]]
    81  			if !ok {
    82  				return "", fmt.Errorf("missing param %s", c)
    83  			}
    84  			components[i] = val
    85  		}
    86  	}
    87  
    88  	u, err := url.Parse(strings.Join(components, "/"))
    89  	if err != nil {
    90  		return "", err
    91  	}
    92  	return u.String(), nil
    93  }
    94  
    95  // Router combines route and resource information in order to generate HTTP
    96  // requests.
    97  type Router struct {
    98  	routes    map[string]Route
    99  	resources map[string]string
   100  }
   101  
   102  // NewRouter returns a pointer to a new Router.
   103  func NewRouter(routes []Route, resources map[string]string) *Router {
   104  	mappedRoutes := map[string]Route{}
   105  	for _, route := range routes {
   106  		mappedRoutes[route.Name] = route
   107  	}
   108  	return &Router{
   109  		routes:    mappedRoutes,
   110  		resources: resources,
   111  	}
   112  }
   113  
   114  // CreateRequest returns a request key'd off of the name given. The params are
   115  // merged into the URL and body is set as the request body.
   116  func (router Router) CreateRequest(name string, params Params, body io.Reader) (*http.Request, error) {
   117  	route, ok := router.routes[name]
   118  	if !ok {
   119  		return &http.Request{}, fmt.Errorf("No route exists with the name %s", name)
   120  	}
   121  
   122  	uri, err := route.CreatePath(params)
   123  	if err != nil {
   124  		return &http.Request{}, err
   125  	}
   126  
   127  	resource, ok := router.resources[route.Resource]
   128  	if !ok {
   129  		return &http.Request{}, fmt.Errorf("No resource exists with the name %s", route.Resource)
   130  	}
   131  
   132  	url, err := router.urlFrom(resource, uri)
   133  	if err != nil {
   134  		return &http.Request{}, err
   135  	}
   136  
   137  	return http.NewRequest(route.Method, url, body)
   138  }
   139  
   140  func (Router) urlFrom(resource string, uri string) (string, error) {
   141  	u, err := url.Parse(resource)
   142  	if err != nil {
   143  		return "", err
   144  	}
   145  	u.Path = path.Join(u.Path, uri)
   146  	return u.String(), nil
   147  }