github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/api/cloudcontroller/ccv3/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  	// Method is any valid HTTP method
    60  	Method string
    61  	// Path contains a path pattern
    62  	Path string
    63  }
    64  
    65  // CreatePath combines the route's path pattern with a Params map
    66  // to produce a valid path.
    67  func (r Route) CreatePath(params Params) (string, error) {
    68  	components := strings.Split(r.Path, "/")
    69  	for i, c := range components {
    70  		if len(c) == 0 {
    71  			continue
    72  		}
    73  		if c[0] == ':' {
    74  			val, ok := params[c[1:]]
    75  			if !ok {
    76  				return "", fmt.Errorf("missing param %s", c)
    77  			}
    78  			components[i] = val
    79  		}
    80  	}
    81  
    82  	u, err := url.Parse(strings.Join(components, "/"))
    83  	if err != nil {
    84  		return "", err
    85  	}
    86  	return u.String(), nil
    87  }
    88  
    89  // Router combines route and resource information in order to generate HTTP
    90  // requests.
    91  type Router struct {
    92  	routes  map[string]Route
    93  	baseURL string
    94  }
    95  
    96  // NewRouter returns a pointer to a new Router.
    97  func NewRouter(routes map[string]Route, baseURL string) *Router {
    98  	return &Router{
    99  		routes:  routes,
   100  		baseURL: baseURL,
   101  	}
   102  }
   103  
   104  // CreateRequest returns a request key'd off of the name given. The params are
   105  // merged into the URL and body is set as the request body.
   106  func (router Router) CreateRequest(name string, params Params, body io.Reader) (*http.Request, error) {
   107  	route, ok := router.routes[name]
   108  	if !ok {
   109  		return &http.Request{}, fmt.Errorf("no route exists with the name %s", name)
   110  	}
   111  
   112  	uri, err := route.CreatePath(params)
   113  	if err != nil {
   114  		return &http.Request{}, err
   115  	}
   116  
   117  	url, err := router.urlFrom(router.baseURL, uri)
   118  	if err != nil {
   119  		return &http.Request{}, err
   120  	}
   121  
   122  	return http.NewRequest(route.Method, url, body)
   123  }
   124  
   125  func (Router) urlFrom(resource string, uri string) (string, error) {
   126  	u, err := url.Parse(resource)
   127  	if err != nil {
   128  		return "", err
   129  	}
   130  	u.Path = path.Join(u.Path, uri)
   131  	return u.String(), nil
   132  }