Raphaël Badin has proposed merging lp:~rvb/maas/gomaasapi-baseURL into lp:~maas-maintainers/maas/gomaasapi.
Commit message: Move BaseURL into the Client; add the AppendSlash utility to make sure we generate Django-compatible urls. Requested reviews: MAAS Maintainers (maas-maintainers) For more details, see: https://code.launchpad.net/~rvb/maas/gomaasapi-baseURL/+merge/145180 - Base URL is now a field in the Client objects (and not the Server object [i.e. a maasobject representing a MAAS server]). - use url.URL internally to manipulate url objects. - AppendSlash allows us, when using GetSubObject, to create urls ending with a slash. This is to keep Django [as configured by MAAS] happy (see https://docs.djangoproject.com/en/dev/ref/settings/#append-slash for details). -- https://code.launchpad.net/~rvb/maas/gomaasapi-baseURL/+merge/145180 Your team MAAS Maintainers is requested to review the proposed merge of lp:~rvb/maas/gomaasapi-baseURL into lp:~maas-maintainers/maas/gomaasapi.
=== modified file 'client.go' --- client.go 2013-01-28 08:18:15 +0000 +++ client.go 2013-01-28 13:30:59 +0000 @@ -13,7 +13,8 @@ ) type Client struct { - Signer OAuthSigner + BaseURL *url.URL + Signer OAuthSigner } const ( @@ -37,7 +38,11 @@ return body, nil } -func (client Client) Get(URL string, operation string, parameters url.Values) ([]byte, error) { +func (client Client) GetURL(URI *url.URL) *url.URL { + return client.BaseURL.ResolveReference(URI) +} + +func (client Client) Get(URI *url.URL, operation string, parameters url.Values) ([]byte, error) { opParameter := parameters.Get(operationParamName) if opParameter != "" { errString := fmt.Sprintf("The parameters contain a value for '%s' which is reserved parameter.") @@ -46,8 +51,9 @@ if operation != "" { parameters.Set(operationParamName, operation) } - queryUrl := URL + "?" + parameters.Encode() - request, err := http.NewRequest("GET", queryUrl, nil) + queryUrl := client.GetURL(URI) + queryUrl.RawQuery = parameters.Encode() + request, err := http.NewRequest("GET", queryUrl.String(), nil) if err != nil { return nil, err } @@ -55,30 +61,29 @@ } // nonIdempotentRequest is a utility method to issue a PUT or a POST request. -func (client Client) nonIdempotentRequest(method string, URL string, parameters url.Values) ([]byte, error) { - request, err := http.NewRequest(method, URL, strings.NewReader(string(parameters.Encode()))) +func (client Client) nonIdempotentRequest(method string, URI *url.URL, parameters url.Values) ([]byte, error) { + URL := client.GetURL(URI) + request, err := http.NewRequest(method, URL.String(), strings.NewReader(string(parameters.Encode()))) if err != nil { return nil, err } request.Header.Set("Content-Type", "application/x-www-form-urlencoded") - if err != nil { - return nil, err - } return client.dispatchRequest(request) } -func (client Client) Post(URL string, operation string, parameters url.Values) ([]byte, error) { +func (client Client) Post(URI *url.URL, operation string, parameters url.Values) ([]byte, error) { queryParams := url.Values{operationParamName: {operation}} - queryURL := URL + "?" + queryParams.Encode() - return client.nonIdempotentRequest("POST", queryURL, parameters) -} - -func (client Client) Put(URL string, parameters url.Values) ([]byte, error) { - return client.nonIdempotentRequest("PUT", URL, parameters) -} - -func (client Client) Delete(URL string) error { - request, err := http.NewRequest("DELETE", URL, strings.NewReader("")) + URI.RawQuery = queryParams.Encode() + return client.nonIdempotentRequest("POST", URI, parameters) +} + +func (client Client) Put(URI *url.URL, parameters url.Values) ([]byte, error) { + return client.nonIdempotentRequest("PUT", URI, parameters) +} + +func (client Client) Delete(URI *url.URL) error { + URL := client.GetURL(URI) + request, err := http.NewRequest("DELETE", URL.String(), strings.NewReader("")) if err != nil { return err } @@ -99,14 +104,18 @@ var _ OAuthSigner = (*anonSigner)(nil) // NewAnonymousClient creates a client that issues anonymous requests. -func NewAnonymousClient() (*Client, error) { - return &Client{Signer: &anonSigner{}}, nil +func NewAnonymousClient(BaseURL string) (*Client, error) { + parsedBaseURL, err := url.Parse(BaseURL) + if err != nil { + return nil, err + } + return &Client{Signer: &anonSigner{}, BaseURL: parsedBaseURL}, nil } // NewAuthenticatedClient parses the given MAAS API key into the individual // OAuth tokens and creates an Client that will use these tokens to sign the // requests it issues. -func NewAuthenticatedClient(apiKey string) (*Client, error) { +func NewAuthenticatedClient(BaseURL string, apiKey string) (*Client, error) { elements := strings.Split(apiKey, ":") if len(elements) != 3 { errString := "Invalid API key. The format of the key must be \"<consumer secret>:<token key>:<token secret>\"." @@ -124,5 +133,9 @@ if err != nil { return nil, err } - return &Client{Signer: signer}, nil + parsedBaseURL, err := url.Parse(BaseURL) + if err != nil { + return nil, err + } + return &Client{Signer: signer, BaseURL: parsedBaseURL}, nil } === modified file 'client_test.go' --- client_test.go 2013-01-25 17:49:25 +0000 +++ client_test.go 2013-01-28 13:30:59 +0000 @@ -13,9 +13,9 @@ func (suite *GomaasapiTestSuite) TestClientdispatchRequestReturnsError(c *C) { URI := "/some/url/?param1=test" expectedResult := "expected:result" - client, _ := NewAnonymousClient() server := newSingleServingServer(URI, expectedResult, http.StatusBadRequest) defer server.Close() + client, _ := NewAnonymousClient(server.URL) request, err := http.NewRequest("GET", server.URL+URI, nil) result, err := client.dispatchRequest(request) @@ -27,9 +27,9 @@ func (suite *GomaasapiTestSuite) TestClientdispatchRequestSignsRequest(c *C) { URI := "/some/url/?param1=test" expectedResult := "expected:result" - client, _ := NewAuthenticatedClient("the:api:key") server := newSingleServingServer(URI, expectedResult, http.StatusOK) defer server.Close() + client, _ := NewAuthenticatedClient(server.URL, "the:api:key") request, err := http.NewRequest("GET", server.URL+URI, nil) result, err := client.dispatchRequest(request) @@ -40,44 +40,44 @@ } func (suite *GomaasapiTestSuite) TestClientGetFormatsGetParameters(c *C) { - URI := "/some/url" + URI, _ := url.Parse("/some/url") expectedResult := "expected:result" - client, _ := NewAnonymousClient() params := url.Values{"test": {"123"}} - fullURI := URI + "?test=123" + fullURI := URI.String() + "?test=123" server := newSingleServingServer(fullURI, expectedResult, http.StatusOK) defer server.Close() + client, _ := NewAnonymousClient(server.URL) - result, err := client.Get(server.URL+URI, "", params) + result, err := client.Get(URI, "", params) c.Check(err, IsNil) c.Check(string(result), Equals, expectedResult) } func (suite *GomaasapiTestSuite) TestClientGetFormatsOperationAsGetParameter(c *C) { - URI := "/some/url" + URI, _ := url.Parse("/some/url") expectedResult := "expected:result" - client, _ := NewAnonymousClient() - fullURI := URI + "?op=list" + fullURI := URI.String() + "?op=list" server := newSingleServingServer(fullURI, expectedResult, http.StatusOK) defer server.Close() + client, _ := NewAnonymousClient(server.URL) - result, err := client.Get(server.URL+URI, "list", url.Values{}) + result, err := client.Get(URI, "list", url.Values{}) c.Check(err, IsNil) c.Check(string(result), Equals, expectedResult) } func (suite *GomaasapiTestSuite) TestClientPostSendsRequest(c *C) { - URI := "/some/url" + URI, _ := url.Parse("/some/url") expectedResult := "expected:result" - client, _ := NewAnonymousClient() - fullURI := URI + "?op=list" + fullURI := URI.String() + "?op=list" params := url.Values{"test": {"123"}} server := newSingleServingServer(fullURI, expectedResult, http.StatusOK) defer server.Close() + client, _ := NewAnonymousClient(server.URL) - result, err := client.Post(server.URL+URI, "list", params) + result, err := client.Post(URI, "list", params) c.Check(err, IsNil) c.Check(string(result), Equals, expectedResult) @@ -85,14 +85,14 @@ } func (suite *GomaasapiTestSuite) TestClientPutSendsRequest(c *C) { - URI := "/some/url" + URI, _ := url.Parse("/some/url") expectedResult := "expected:result" - client, _ := NewAnonymousClient() params := url.Values{"test": {"123"}} - server := newSingleServingServer(URI, expectedResult, http.StatusOK) + server := newSingleServingServer(URI.String(), expectedResult, http.StatusOK) defer server.Close() + client, _ := NewAnonymousClient(server.URL) - result, err := client.Put(server.URL+URI, params) + result, err := client.Put(URI, params) c.Check(err, IsNil) c.Check(string(result), Equals, expectedResult) @@ -100,13 +100,13 @@ } func (suite *GomaasapiTestSuite) TestClientDeleteSendsRequest(c *C) { - URI := "/some/url" + URI, _ := url.Parse("/some/url") expectedResult := "expected:result" - client, _ := NewAnonymousClient() - server := newSingleServingServer(URI, expectedResult, http.StatusOK) + server := newSingleServingServer(URI.String(), expectedResult, http.StatusOK) defer server.Close() + client, _ := NewAnonymousClient(server.URL) - err := client.Delete(server.URL + URI) + err := client.Delete(URI) c.Check(err, IsNil) } @@ -120,7 +120,7 @@ keyElements := []string{consumerKey, tokenKey, tokenSecret} apiKey := strings.Join(keyElements, ":") - client, err := NewAuthenticatedClient(apiKey) + client, err := NewAuthenticatedClient("http://example.com/api", apiKey) c.Check(err, IsNil) signer := client.Signer.(_PLAINTEXTOAuthSigner) @@ -130,7 +130,7 @@ } func (suite *GomaasapiTestSuite) TestNewAuthenticatedClientFailsIfInvalidKey(c *C) { - client, err := NewAuthenticatedClient("invalid-key") + client, err := NewAuthenticatedClient("", "invalid-key") c.Check(err, ErrorMatches, "Invalid API key.*") c.Check(client, IsNil) === modified file 'example/live_example.go' --- example/live_example.go 2013-01-28 08:18:15 +0000 +++ example/live_example.go 2013-01-28 13:30:59 +0000 @@ -26,14 +26,14 @@ } func main() { - authClient, err := gomaasapi.NewAuthenticatedClient(apiKey) + authClient, err := gomaasapi.NewAuthenticatedClient(apiURL, apiKey) if err != nil { panic(err) } - maas, err := gomaasapi.NewMAAS(apiURL, *authClient) + maas, err := gomaasapi.NewMAAS(*authClient) - nodeListing := maas.GetSubObject("/nodes/") + nodeListing := maas.GetSubObject("nodes") // List nodes. fmt.Println("Fetching list of nodes...") @@ -46,7 +46,8 @@ for index, nodeObj := range listNodes { node, _ := nodeObj.GetMAASObject() hostname, _ := node.GetField("hostname") - fmt.Printf("Node #%d is named '%v' (%v)\n", index, hostname, node.URL()) + nodeURL, _ := node.URL() + fmt.Printf("Node #%d is named '%v' (%v)\n", index, hostname, nodeURL) } // Create a node. @@ -58,7 +59,8 @@ } newNode, _ := newNodeObj.GetMAASObject() newNodeName, _ := newNode.GetField("hostname") - fmt.Printf("New node created: %s (%s)\n", newNodeName, newNode.URL()) + newNodeURL, _ := newNode.URL() + fmt.Printf("New node created: %s (%s)\n", newNodeName, newNodeURL) // Update the new node. fmt.Println("Updating the new node...") === modified file 'jsonobject.go' --- jsonobject.go 2013-01-25 17:49:25 +0000 +++ jsonobject.go 2013-01-28 13:30:59 +0000 @@ -66,7 +66,7 @@ // JSONObject (with the appropriate implementation of course). // This function is recursive. Maps and arrays are deep-copied, with each // individual value being converted to a JSONObject type. -func maasify(client Client, baseURL string, value interface{}) JSONObject { +func maasify(client Client, value interface{}) JSONObject { if value == nil { return nil } @@ -81,19 +81,19 @@ original := value.(map[string]interface{}) result := make(map[string]JSONObject, len(original)) for key, value := range original { - result[key] = maasify(client, baseURL, value) + result[key] = maasify(client, value) } if _, ok := result[resource_uri]; ok { // If the map contains "resource-uri", we can treat // it as a MAAS object. - return jsonMAASObject{result, client, baseURL} + return jsonMAASObject{result, client} } return jsonMap(result) case []interface{}: original := value.([]interface{}) result := make([]JSONObject, len(original)) for index, value := range original { - result[index] = maasify(client, baseURL, value) + result[index] = maasify(client, value) } return jsonArray(result) case bool: @@ -104,13 +104,13 @@ } // Parse a JSON blob into a JSONObject. -func Parse(client Client, baseURL string, input []byte) (JSONObject, error) { +func Parse(client Client, input []byte) (JSONObject, error) { var obj interface{} err := json.Unmarshal(input, &obj) if err != nil { return nil, err } - return maasify(client, baseURL, obj), nil + return maasify(client, obj), nil } // Return error value for failed type conversion. === modified file 'jsonobject_test.go' --- jsonobject_test.go 2013-01-25 17:49:25 +0000 +++ jsonobject_test.go 2013-01-28 13:30:59 +0000 @@ -9,37 +9,37 @@ // maasify() converts nil. func (suite *GomaasapiTestSuite) TestMaasifyConvertsNil(c *C) { - c.Check(maasify(Client{}, "", nil), Equals, nil) + c.Check(maasify(Client{}, nil), Equals, nil) } // maasify() converts strings. func (suite *GomaasapiTestSuite) TestMaasifyConvertsString(c *C) { const text = "Hello" - c.Check(string(maasify(Client{}, "", text).(jsonString)), Equals, text) + c.Check(string(maasify(Client{}, text).(jsonString)), Equals, text) } // maasify() converts float64 numbers. func (suite *GomaasapiTestSuite) TestMaasifyConvertsNumber(c *C) { const number = 3.1415926535 - c.Check(float64(maasify(Client{}, "", number).(jsonFloat64)), Equals, number) + c.Check(float64(maasify(Client{}, number).(jsonFloat64)), Equals, number) } // Any number converts to float64, even integers. func (suite *GomaasapiTestSuite) TestMaasifyConvertsIntegralNumber(c *C) { const number = 1 - c.Check(float64(maasify(Client{}, "", number).(jsonFloat64)), Equals, float64(number)) + c.Check(float64(maasify(Client{}, number).(jsonFloat64)), Equals, float64(number)) } // maasify() converts array slices. func (suite *GomaasapiTestSuite) TestMaasifyConvertsArray(c *C) { original := []interface{}{3.0, 2.0, 1.0} - output := maasify(Client{}, "", original).(jsonArray) + output := maasify(Client{}, original).(jsonArray) c.Check(len(output), Equals, len(original)) } // When maasify() converts an array slice, the result contains JSONObjects. func (suite *GomaasapiTestSuite) TestMaasifyArrayContainsJSONObjects(c *C) { - arr := maasify(Client{}, "", []interface{}{9.9}).(jsonArray) + arr := maasify(Client{}, []interface{}{9.9}).(jsonArray) var entry JSONObject entry = arr[0] c.Check((float64)(entry.(jsonFloat64)), Equals, 9.9) @@ -48,13 +48,13 @@ // maasify() converts maps. func (suite *GomaasapiTestSuite) TestMaasifyConvertsMap(c *C) { original := map[string]interface{}{"1": "one", "2": "two", "3": "three"} - output := maasify(Client{}, "", original).(jsonMap) + output := maasify(Client{}, original).(jsonMap) c.Check(len(output), Equals, len(original)) } // When maasify() converts a map, the result contains JSONObjects. func (suite *GomaasapiTestSuite) TestMaasifyMapContainsJSONObjects(c *C) { - mp := maasify(Client{}, "", map[string]interface{}{"key": "value"}).(jsonMap) + mp := maasify(Client{}, map[string]interface{}{"key": "value"}).(jsonMap) var entry JSONObject entry = mp["key"] c.Check((string)(entry.(jsonString)), Equals, "value") @@ -66,67 +66,59 @@ "resource_uri": "http://example.com/foo", "size": "3", } - output := maasify(Client{}, "", original).(jsonMAASObject) + output := maasify(Client{}, original).(jsonMAASObject) c.Check(len(output.jsonMap), Equals, len(original)) c.Check((string)(output.jsonMap["size"].(jsonString)), Equals, "3") } -// maasify() passes its info (client and baseURL) to a MAASObject it creates. +// maasify() passes its client to a MAASObject it creates. func (suite *GomaasapiTestSuite) TestMaasifyPassesInfoToMAASObject(c *C) { client := Client{} original := map[string]interface{}{"resource_uri": "/foo"} - baseURL := "http://example.com" - output := maasify(client, baseURL, original).(jsonMAASObject) + output := maasify(client, original).(jsonMAASObject) c.Check(output.client, Equals, client) - c.Check(output.baseURL, Equals, baseURL) } -// maasify() passes its info (client and baseURL) into an array of MAASObjects it creates. +// maasify() passes its client into an array of MAASObjects it creates. func (suite *GomaasapiTestSuite) TestMaasifyPassesInfoIntoArray(c *C) { client := Client{} obj := map[string]interface{}{"resource_uri": "/foo"} - baseURL := "http://example.com" list := []interface{}{obj} - output := maasify(client, baseURL, list).(jsonArray) + output := maasify(client, list).(jsonArray) c.Check(output[0].(jsonMAASObject).client, Equals, client) - c.Check(output[0].(jsonMAASObject).baseURL, Equals, baseURL) } -// maasify() passes its info (client and baseURL) into a map of MAASObjects it creates. +// maasify() passes its client into a map of MAASObjects it creates. func (suite *GomaasapiTestSuite) TestMaasifyPassesInfoIntoMap(c *C) { client := Client{} obj := map[string]interface{}{"resource_uri": "/foo"} - baseURL := "http://example.com" mp := map[string]interface{}{"key": obj} - output := maasify(client, baseURL, mp).(jsonMap) + output := maasify(client, mp).(jsonMap) c.Check(output["key"].(jsonMAASObject).client, Equals, client) - c.Check(output["key"].(jsonMAASObject).baseURL, Equals, baseURL) } -// maasify() passes its info (client and baseURL) all the way down into any -// MAASObjects in the object structure it creates. +// maasify() passes its client all the way down into any MAASObjects in the +// object structure it creates. func (suite *GomaasapiTestSuite) TestMaasifyPassesInfoAllTheWay(c *C) { client := Client{} obj := map[string]interface{}{"resource_uri": "/foo"} - baseURL := "http://example.com" mp := map[string]interface{}{"key": obj} list := []interface{}{mp} - output := maasify(client, baseURL, list).(jsonArray) + output := maasify(client, list).(jsonArray) maasobj := output[0].(jsonMap)["key"] c.Check(maasobj.(jsonMAASObject).client, Equals, client) - c.Check(maasobj.(jsonMAASObject).baseURL, Equals, baseURL) } // maasify() converts Booleans. func (suite *GomaasapiTestSuite) TestMaasifyConvertsBool(c *C) { - c.Check(bool(maasify(Client{}, "", true).(jsonBool)), Equals, true) - c.Check(bool(maasify(Client{}, "", false).(jsonBool)), Equals, false) + c.Check(bool(maasify(Client{}, true).(jsonBool)), Equals, true) + c.Check(bool(maasify(Client{}, false).(jsonBool)), Equals, false) } // Parse takes you from a JSON blob to a JSONObject. func (suite *GomaasapiTestSuite) TestParseMaasifiesJSONBlob(c *C) { blob := []byte("[12]") - obj, err := Parse(Client{}, "", blob) + obj, err := Parse(Client{}, blob) c.Check(err, IsNil) c.Check(float64(obj.(jsonArray)[0].(jsonFloat64)), Equals, 12.0) } === modified file 'maasobject.go' --- maasobject.go 2013-01-28 08:18:15 +0000 +++ maasobject.go 2013-01-28 13:30:59 +0000 @@ -18,9 +18,11 @@ // Utility method to extract a string field from this MAAS object. GetField(name string) (string, error) + // URL for this MAAS object. + URL() (*url.URL, error) // Resource URI for this MAAS object. - URL() string - // Retrieve the MAAS object located at thisObject.URL()+name. + URI() (*url.URL, error) + // Retrieve the MAAS object located at thisObject.URI()+name. GetSubObject(name string) MAASObject // Retrieve this MAAS object. Get() (MAASObject, error) @@ -43,8 +45,7 @@ // jsonMAASObject implements both JSONObject and MAASObject. type jsonMAASObject struct { jsonMap - client Client - baseURL string + client Client } var _ JSONObject = (*jsonMAASObject)(nil) @@ -65,39 +66,48 @@ return obj.jsonMap[name].GetString() } -func (obj jsonMAASObject) _URI() (string, error) { +func (obj jsonMAASObject) URI() (*url.URL, error) { contents, err := obj.GetMap() if err != nil { panic("Unexpected failure converting jsonMAASObject to maasMap.") } - return contents[resource_uri].GetString() + urlString, err := contents[resource_uri].GetString() + if err != nil { + return &url.URL{}, err + } + return url.Parse(urlString) } -func (obj jsonMAASObject) URL() string { - uri, err := obj._URI() +func (obj jsonMAASObject) URL() (*url.URL, error) { + uri, err := obj.URI() if err != nil { - panic("Unexpected failure reading jsonMAASObject's URL.") + return &url.URL{}, err } - return obj.baseURL + uri + return obj.client.GetURL(uri), nil } func (obj jsonMAASObject) GetSubObject(name string) MAASObject { - uri, err := obj._URI() + uri, err := obj.URI() if err != nil { panic("Unexpected failure reading jsonMAASObject's URL.") } - input := map[string]JSONObject{resource_uri: jsonString(uri + name)} - return jsonMAASObject{jsonMap: jsonMap(input), client: obj.client, baseURL: obj.baseURL} + uri.Path = AppendSlash(JoinURLs(uri.Path, name)) + input := map[string]JSONObject{resource_uri: jsonString(uri.String())} + return jsonMAASObject{jsonMap: jsonMap(input), client: obj.client} } var NotImplemented = errors.New("Not implemented") func (obj jsonMAASObject) Get() (MAASObject, error) { - result, err := obj.client.Get(obj.URL(), "", url.Values{}) - if err != nil { - return nil, err - } - jsonObj, err := Parse(obj.client, obj.baseURL, result) + uri, err := obj.URI() + if err != nil { + return nil, err + } + result, err := obj.client.Get(uri, "", url.Values{}) + if err != nil { + return nil, err + } + jsonObj, err := Parse(obj.client, result) if err != nil { return nil, err } @@ -105,19 +115,27 @@ } func (obj jsonMAASObject) Post(params url.Values) (JSONObject, error) { - result, err := obj.client.Post(obj.URL(), "", params) - if err != nil { - return nil, err - } - return Parse(obj.client, obj.baseURL, result) + uri, err := obj.URI() + if err != nil { + return nil, err + } + result, err := obj.client.Post(uri, "", params) + if err != nil { + return nil, err + } + return Parse(obj.client, result) } func (obj jsonMAASObject) Update(params url.Values) (MAASObject, error) { - result, err := obj.client.Put(obj.URL(), params) - if err != nil { - return nil, err - } - jsonObj, err := Parse(obj.client, obj.baseURL, result) + uri, err := obj.URI() + if err != nil { + return nil, err + } + result, err := obj.client.Put(uri, params) + if err != nil { + return nil, err + } + jsonObj, err := Parse(obj.client, result) if err != nil { return nil, err } @@ -125,21 +143,33 @@ } func (obj jsonMAASObject) Delete() error { - return obj.client.Delete(obj.URL()) + uri, err := obj.URI() + if err != nil { + return err + } + return obj.client.Delete(uri) } func (obj jsonMAASObject) CallGet(operation string, params url.Values) (JSONObject, error) { - result, err := obj.client.Get(obj.URL(), operation, params) - if err != nil { - return nil, err - } - return Parse(obj.client, obj.baseURL, result) + uri, err := obj.URI() + if err != nil { + return nil, err + } + result, err := obj.client.Get(uri, operation, params) + if err != nil { + return nil, err + } + return Parse(obj.client, result) } func (obj jsonMAASObject) CallPost(operation string, params url.Values) (JSONObject, error) { - result, err := obj.client.Post(obj.URL(), operation, params) - if err != nil { - return nil, err - } - return Parse(obj.client, obj.baseURL, result) + uri, err := obj.URI() + if err != nil { + return nil, err + } + result, err := obj.client.Post(uri, operation, params) + if err != nil { + return nil, err + } + return Parse(obj.client, result) } === modified file 'maasobject_test.go' --- maasobject_test.go 2013-01-28 08:18:15 +0000 +++ maasobject_test.go 2013-01-28 13:30:59 +0000 @@ -7,6 +7,7 @@ "fmt" . "launchpad.net/gocheck" "math/rand" + "net/url" ) func makeFakeResourceURI() string { @@ -45,19 +46,33 @@ } func (suite *GomaasapiTestSuite) TestURL(c *C) { + baseURL, _ := url.Parse("http://example.com/") uri := "http://example.com/a/resource" + resourceURL, _ := url.Parse(uri) input := map[string]JSONObject{resource_uri: jsonString(uri)} - obj := jsonMAASObject{jsonMap: jsonMap(input)} - c.Check(obj.URL(), Equals, uri) + client := Client{BaseURL: baseURL} + obj := jsonMAASObject{jsonMap: jsonMap(input), client: client} + + URL, err := obj.URL() + + c.Check(err, IsNil) + c.Check(URL, DeepEquals, resourceURL) } func (suite *GomaasapiTestSuite) TestGetSubObject(c *C) { - uri := "http://example.com/a/resource" + uri := "http://example.com/a/resource/" input := map[string]JSONObject{resource_uri: jsonString(uri)} obj := jsonMAASObject{jsonMap: jsonMap(input)} subName := "/test" + subObj := obj.GetSubObject(subName) - c.Check(subObj.URL(), Equals, uri+subName) + subURL, err := subObj.URL() + + c.Check(err, IsNil) + // uri ends with a slash and subName starts with one, but the two paths + // should be concatenated as "http://example.com/a/resource/test/". + expectedSubURL, _ := url.Parse("http://example.com/a/resource/test/") + c.Check(subURL, DeepEquals, expectedSubURL) } func (suite *GomaasapiTestSuite) TestGetField(c *C) { === modified file 'server.go' --- server.go 2013-01-28 08:18:15 +0000 +++ server.go 2013-01-28 13:30:59 +0000 @@ -3,19 +3,8 @@ package gomaasapi -import ( - "fmt" - "net/url" -) - // NewMAAS returns an interface to the MAAS API as a MAASObject. -func NewMAAS(URL string, client Client) (MAASObject, error) { - parsed, err := url.Parse(URL) - if err != nil { - return nil, err - } - baseURL := fmt.Sprintf("%s://%s", parsed.Scheme, parsed.Host) - resourceURI := parsed.Path - input := map[string]JSONObject{resource_uri: jsonString(resourceURI)} - return jsonMAASObject{jsonMap: jsonMap(input), client: client, baseURL: baseURL}, nil +func NewMAAS(client Client) (MAASObject, error) { + input := map[string]JSONObject{resource_uri: jsonString(client.BaseURL.String())} + return jsonMAASObject{jsonMap: jsonMap(input), client: client}, nil } === modified file 'server_test.go' --- server_test.go 2013-01-28 08:18:15 +0000 +++ server_test.go 2013-01-28 13:30:59 +0000 @@ -5,16 +5,16 @@ import ( . "launchpad.net/gocheck" + "net/url" ) -func (suite *GomaasapiTestSuite) TestNewMAASParsesURL(c *C) { - maas, err := NewMAAS("https://server.com:888/path/to/api", Client{}) - - c.Check(err, IsNil) - c.Check(maas.URL(), Equals, "https://server.com:888/path/to/api") - jsonObj := maas.(jsonMAASObject) - uri, err := jsonObj._URI() - c.Check(err, IsNil) - c.Check(uri, Equals, "/path/to/api") - c.Check(jsonObj.baseURL, Equals, "https://server.com:888") +func (suite *GomaasapiTestSuite) TestNewMAASUsesBaseURLFromClient(c *C) { + baseURLString := "https://server.com:888/path/to/api" + baseURL, _ := url.Parse(baseURLString) + client := Client{BaseURL: baseURL} + maas, err := NewMAAS(client) + c.Check(err, IsNil) + URL, err := maas.URL() + c.Check(err, IsNil) + c.Check(URL, DeepEquals, baseURL) } === modified file 'util.go' --- util.go 2013-01-28 09:37:10 +0000 +++ util.go 2013-01-28 13:30:59 +0000 @@ -14,3 +14,15 @@ func JoinURLs(baseURL, path string) string { return strings.TrimRight(baseURL, "/") + "/" + strings.TrimLeft(path, "/") } + +// AppendSlash appends a slash at the end of the given string unless there +// already is one. +// This is used to create the kind of normalized URLs that Django expects. +// (to avoid Django's redirection when an URL does not ends with a slash.) +func AppendSlash(URL string) string { + length := len(URL) + if length > 0 && string(URL[length-1]) == "/" { + return URL + } + return URL + "/" +} === modified file 'util_test.go' --- util_test.go 2013-01-28 09:15:13 +0000 +++ util_test.go 2013-01-28 13:30:59 +0000 @@ -18,3 +18,15 @@ func (suite *GomaasapiTestSuite) TestJoinURLsNormalizesDoubleSlash(c *C) { c.Check(JoinURLs("http://example.com/base/", "/szot"), Equals, "http://example.com/base/szot") } + +func (suite *GomaasapiTestSuite) TestAppendSlashAppendsSlashIfMissing(c *C) { + c.Check(AppendSlash("test"), Equals, "test/") +} + +func (suite *GomaasapiTestSuite) TestAppendSlashDoesNotAppendsIfPresent(c *C) { + c.Check(AppendSlash("test/"), Equals, "test/") +} + +func (suite *GomaasapiTestSuite) TestAppendSlashReturnsSlashIfEmpty(c *C) { + c.Check(AppendSlash(""), Equals, "/") +}
_______________________________________________ Mailing list: https://launchpad.net/~launchpad-reviewers Post to : launchpad-reviewers@lists.launchpad.net Unsubscribe : https://launchpad.net/~launchpad-reviewers More help : https://help.launchpad.net/ListHelp