Expose URL() on Request to allow building URLs

The client/Request type is the appropriate place to build
URLs, this allows callers to generate URLs for providing to
others (such as SelfLinks or relative links to objects).
This commit is contained in:
Clayton Coleman 2015-04-29 23:27:13 -04:00
parent 6f5e08114a
commit 53ca1fcc7a
2 changed files with 27 additions and 24 deletions

View File

@ -455,7 +455,8 @@ func (r *Request) Body(obj interface{}) *Request {
return r return r
} }
func (r *Request) finalURL() string { // URL returns the current working URL.
func (r *Request) URL() *url.URL {
p := r.path p := r.path
if r.namespaceSet && !r.namespaceInQuery && len(r.namespace) > 0 { if r.namespaceSet && !r.namespaceInQuery && len(r.namespace) > 0 {
p = path.Join(p, "namespaces", r.namespace) p = path.Join(p, "namespaces", r.namespace)
@ -472,9 +473,9 @@ func (r *Request) finalURL() string {
p = path.Join(p, r.resourceName, r.subresource, r.subpath) p = path.Join(p, r.resourceName, r.subresource, r.subpath)
} }
finalURL := url.URL{} finalURL := &url.URL{}
if r.baseURL != nil { if r.baseURL != nil {
finalURL = *r.baseURL *finalURL = *r.baseURL
} }
finalURL.Path = p finalURL.Path = p
@ -494,16 +495,16 @@ func (r *Request) finalURL() string {
query.Set("timeout", r.timeout.String()) query.Set("timeout", r.timeout.String())
} }
finalURL.RawQuery = query.Encode() finalURL.RawQuery = query.Encode()
return finalURL.String() return finalURL
} }
// Similar to finalURL(), but if the request contains name of an object // finalURLTemplate is similar to URL(), but if the request contains name of an object
// (e.g. GET for a specific Pod) it will be substited with "<name>". // (e.g. GET for a specific Pod) it will be substited with "<name>".
func (r Request) finalURLTemplate() string { func (r *Request) finalURLTemplate() string {
if len(r.resourceName) != 0 { if len(r.resourceName) != 0 {
r.resourceName = "<name>" r.resourceName = "<name>"
} }
return r.finalURL() return r.URL().String()
} }
// Watch attempts to begin watching the requested location. // Watch attempts to begin watching the requested location.
@ -512,7 +513,8 @@ func (r *Request) Watch() (watch.Interface, error) {
if r.err != nil { if r.err != nil {
return nil, r.err return nil, r.err
} }
req, err := http.NewRequest(r.verb, r.finalURL(), r.body) url := r.URL().String()
req, err := http.NewRequest(r.verb, url, r.body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -533,7 +535,7 @@ func (r *Request) Watch() (watch.Interface, error) {
if result := r.transformResponse(resp, req); result.err != nil { if result := r.transformResponse(resp, req); result.err != nil {
return nil, result.err return nil, result.err
} }
return nil, fmt.Errorf("for request '%+v', got status: %v", req.URL, resp.StatusCode) return nil, fmt.Errorf("for request '%+v', got status: %v", url, resp.StatusCode)
} }
return watch.NewStreamWatcher(watchjson.NewDecoder(resp.Body, r.codec)), nil return watch.NewStreamWatcher(watchjson.NewDecoder(resp.Body, r.codec)), nil
} }
@ -546,7 +548,8 @@ func (r *Request) Stream() (io.ReadCloser, error) {
if r.err != nil { if r.err != nil {
return nil, r.err return nil, r.err
} }
req, err := http.NewRequest(r.verb, r.finalURL(), nil) url := r.URL().String()
req, err := http.NewRequest(r.verb, url, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -567,7 +570,7 @@ func (r *Request) Stream() (io.ReadCloser, error) {
// we have a decent shot at taking the object returned, parsing it as a status object and returning a more normal error // we have a decent shot at taking the object returned, parsing it as a status object and returning a more normal error
bodyBytes, err := ioutil.ReadAll(resp.Body) bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, fmt.Errorf("%v while accessing %v", resp.Status, r.finalURL()) return nil, fmt.Errorf("%v while accessing %v", resp.Status, url)
} }
if runtimeObject, err := r.codec.Decode(bodyBytes); err == nil { if runtimeObject, err := r.codec.Decode(bodyBytes); err == nil {
@ -579,7 +582,7 @@ func (r *Request) Stream() (io.ReadCloser, error) {
} }
bodyText := string(bodyBytes) bodyText := string(bodyBytes)
return nil, fmt.Errorf("%s while accessing %v: %s", resp.Status, r.finalURL(), bodyText) return nil, fmt.Errorf("%s while accessing %v: %s", resp.Status, url, bodyText)
} }
return resp.Body, nil return resp.Body, nil
@ -606,7 +609,7 @@ func (r *Request) Upgrade(config *Config, newRoundTripperFunc func(*tls.Config)
r.client = &http.Client{Transport: wrapper} r.client = &http.Client{Transport: wrapper}
req, err := http.NewRequest(r.verb, r.finalURL(), nil) req, err := http.NewRequest(r.verb, r.URL().String(), nil)
if err != nil { if err != nil {
return nil, fmt.Errorf("Error creating request: %s", err) return nil, fmt.Errorf("Error creating request: %s", err)
} }
@ -647,8 +650,8 @@ func (r *Request) request(fn func(*http.Request, *http.Response)) error {
maxRetries := 10 maxRetries := 10
retries := 0 retries := 0
for { for {
url := r.finalURL() url := r.URL().String()
req, err := http.NewRequest(r.verb, r.finalURL(), r.body) req, err := http.NewRequest(r.verb, url, r.body)
if err != nil { if err != nil {
return err return err
} }

View File

@ -71,26 +71,26 @@ func TestRequestWithErrorWontChange(t *testing.T) {
func TestRequestPreservesBaseTrailingSlash(t *testing.T) { func TestRequestPreservesBaseTrailingSlash(t *testing.T) {
r := &Request{baseURL: &url.URL{}, path: "/path/", namespaceInQuery: true} r := &Request{baseURL: &url.URL{}, path: "/path/", namespaceInQuery: true}
if s := r.finalURL(); s != "/path/" { if s := r.URL().String(); s != "/path/" {
t.Errorf("trailing slash should be preserved: %s", s) t.Errorf("trailing slash should be preserved: %s", s)
} }
} }
func TestRequestAbsPathPreservesTrailingSlash(t *testing.T) { func TestRequestAbsPathPreservesTrailingSlash(t *testing.T) {
r := (&Request{baseURL: &url.URL{}, namespaceInQuery: true}).AbsPath("/foo/") r := (&Request{baseURL: &url.URL{}, namespaceInQuery: true}).AbsPath("/foo/")
if s := r.finalURL(); s != "/foo/" { if s := r.URL().String(); s != "/foo/" {
t.Errorf("trailing slash should be preserved: %s", s) t.Errorf("trailing slash should be preserved: %s", s)
} }
r = (&Request{baseURL: &url.URL{}}).AbsPath("/foo/") r = (&Request{baseURL: &url.URL{}}).AbsPath("/foo/")
if s := r.finalURL(); s != "/foo/" { if s := r.URL().String(); s != "/foo/" {
t.Errorf("trailing slash should be preserved: %s", s) t.Errorf("trailing slash should be preserved: %s", s)
} }
} }
func TestRequestAbsPathJoins(t *testing.T) { func TestRequestAbsPathJoins(t *testing.T) {
r := (&Request{baseURL: &url.URL{}, namespaceInQuery: true}).AbsPath("foo/bar", "baz") r := (&Request{baseURL: &url.URL{}, namespaceInQuery: true}).AbsPath("foo/bar", "baz")
if s := r.finalURL(); s != "foo/bar/baz" { if s := r.URL().String(); s != "foo/bar/baz" {
t.Errorf("trailing slash should be preserved: %s", s) t.Errorf("trailing slash should be preserved: %s", s)
} }
} }
@ -105,7 +105,7 @@ func TestRequestSetsNamespace(t *testing.T) {
if r.namespace == "" { if r.namespace == "" {
t.Errorf("namespace should be set: %#v", r) t.Errorf("namespace should be set: %#v", r)
} }
if s := r.finalURL(); s != "?namespace=foo" { if s := r.URL().String(); s != "?namespace=foo" {
t.Errorf("namespace should be in params: %s", s) t.Errorf("namespace should be in params: %s", s)
} }
@ -114,7 +114,7 @@ func TestRequestSetsNamespace(t *testing.T) {
Path: "/", Path: "/",
}, },
}).Namespace("foo") }).Namespace("foo")
if s := r.finalURL(); s != "namespaces/foo" { if s := r.URL().String(); s != "namespaces/foo" {
t.Errorf("namespace should be in path: %s", s) t.Errorf("namespace should be in path: %s", s)
} }
} }
@ -124,7 +124,7 @@ func TestRequestOrdersNamespaceInPath(t *testing.T) {
baseURL: &url.URL{}, baseURL: &url.URL{},
path: "/test/", path: "/test/",
}).Name("bar").Resource("baz").Namespace("foo") }).Name("bar").Resource("baz").Namespace("foo")
if s := r.finalURL(); s != "/test/namespaces/foo/baz/bar" { if s := r.URL().String(); s != "/test/namespaces/foo/baz/bar" {
t.Errorf("namespace should be in order in path: %s", s) t.Errorf("namespace should be in order in path: %s", s)
} }
} }
@ -134,7 +134,7 @@ func TestRequestOrdersSubResource(t *testing.T) {
baseURL: &url.URL{}, baseURL: &url.URL{},
path: "/test/", path: "/test/",
}).Name("bar").Resource("baz").Namespace("foo").Suffix("test").SubResource("a", "b") }).Name("bar").Resource("baz").Namespace("foo").Suffix("test").SubResource("a", "b")
if s := r.finalURL(); s != "/test/namespaces/foo/baz/bar/a/b/test" { if s := r.URL().String(); s != "/test/namespaces/foo/baz/bar/a/b/test" {
t.Errorf("namespace should be in order in path: %s", s) t.Errorf("namespace should be in order in path: %s", s)
} }
} }
@ -1028,7 +1028,7 @@ func TestUintParam(t *testing.T) {
for _, item := range table { for _, item := range table {
c := NewOrDie(&Config{}) c := NewOrDie(&Config{})
r := c.Get().AbsPath("").UintParam(item.name, item.testVal) r := c.Get().AbsPath("").UintParam(item.name, item.testVal)
if e, a := item.expectStr, r.finalURL(); e != a { if e, a := item.expectStr, r.URL().String(); e != a {
t.Errorf("expected %v, got %v", e, a) t.Errorf("expected %v, got %v", e, a)
} }
} }