use generic parameter building functions; pagination in unit tests
diff --git a/openstack/storage/v1/containers/containers.go b/openstack/storage/v1/containers/containers.go
deleted file mode 100644
index 9ae344b..0000000
--- a/openstack/storage/v1/containers/containers.go
+++ /dev/null
@@ -1,99 +0,0 @@
-package containers
-
-import (
- "fmt"
- "strings"
-
- "github.com/rackspace/gophercloud/pagination"
-)
-
-// Container is a structure that holds information related to a storage container.
-type Container map[string]interface{}
-
-// ListOpts is a structure that holds parameters for listing containers.
-type ListOpts struct {
- Full bool
- Params map[string]string
-}
-
-// CreateOpts is a structure that holds parameters for creating a container.
-type CreateOpts struct {
- Name string
- Metadata map[string]string
- Headers map[string]string
-}
-
-// DeleteOpts is a structure that holds parameters for deleting a container.
-type DeleteOpts struct {
- Name string
- Params map[string]string
-}
-
-// UpdateOpts is a structure that holds parameters for updating, creating, or deleting a
-// container's metadata.
-type UpdateOpts struct {
- Name string
- Metadata map[string]string
- Headers map[string]string
-}
-
-// GetOpts is a structure that holds parameters for getting a container's metadata.
-type GetOpts struct {
- Name string
-}
-
-// ExtractInfo is a function that takes a ListResult and returns the containers' information.
-func ExtractInfo(page pagination.Page) ([]Container, error) {
- untyped := page.(ListResult).Body.([]interface{})
- results := make([]Container, len(untyped))
- for index, each := range untyped {
- results[index] = Container(each.(map[string]interface{}))
- }
- return results, nil
-}
-
-// ExtractNames is a function that takes a ListResult and returns the containers' names.
-func ExtractNames(page pagination.Page) ([]string, error) {
- casted := page.(ListResult)
- ct := casted.Header.Get("Content-Type")
-
- switch {
- case strings.HasPrefix(ct, "application/json"):
- parsed, err := ExtractInfo(page)
- if err != nil {
- return nil, err
- }
-
- names := make([]string, 0, len(parsed))
- for _, container := range parsed {
- names = append(names, container["name"].(string))
- }
- return names, nil
- case strings.HasPrefix(ct, "text/plain"):
- names := make([]string, 0, 50)
-
- body := string(page.(ListResult).Body.([]uint8))
- for _, name := range strings.Split(body, "\n") {
- if len(name) > 0 {
- names = append(names, name)
- }
- }
-
- return names, nil
- default:
- return nil, fmt.Errorf("Cannot extract names from response with content-type: [%s]", ct)
- }
-}
-
-// ExtractMetadata is a function that takes a GetResult (of type *http.Response)
-// and returns the custom metadata associated with the container.
-func ExtractMetadata(gr GetResult) map[string]string {
- metadata := make(map[string]string)
- for k, v := range gr.Header {
- if strings.HasPrefix(k, "X-Container-Meta-") {
- key := strings.TrimPrefix(k, "X-Container-Meta-")
- metadata[key] = v[0]
- }
- }
- return metadata
-}
diff --git a/openstack/storage/v1/containers/containers_test.go b/openstack/storage/v1/containers/containers_test.go
deleted file mode 100644
index 3296bb1..0000000
--- a/openstack/storage/v1/containers/containers_test.go
+++ /dev/null
@@ -1,78 +0,0 @@
-package containers
-
-import (
- "bytes"
- "encoding/json"
- "io/ioutil"
- "net/http"
- "reflect"
- "testing"
-)
-
-
-func TestExtractContainerMetadata(t *testing.T) {
- getResult := &http.Response{}
-
- expected := map[string]string{}
-
- actual := ExtractMetadata(getResult)
-
- if !reflect.DeepEqual(expected, actual) {
- t.Errorf("Expected: %+v\nActual:%+v", expected, actual)
- }
-}
-
-func TestExtractContainerInfo(t *testing.T) {
- responseBody := `
- [
- {
- "count": 3,
- "bytes": 2000,
- "name": "artemis"
- },
- {
- "count": 1,
- "bytes": 450,
- "name": "diana"
- }
- ]
- `
-
- listResult := &http.Response{
- Body: ioutil.NopCloser(bytes.NewBufferString(responseBody)),
- }
-
- var expected []Container
- err := json.Unmarshal([]byte(responseBody), &expected)
- if err != nil {
- t.Errorf("Error unmarshaling JSON: %s", err)
- }
-
- actual, err := ExtractInfo(listResult)
- if err != nil {
- t.Errorf("Error extracting containers info: %s", err)
- }
-
- if !reflect.DeepEqual(expected, actual) {
- t.Errorf("\nExpected: %+v\nActual: %+v", expected, actual)
- }
-}
-
-func TestExtractConatinerNames(t *testing.T) {
- responseBody := "artemis\ndiana\n"
-
- listResult := &http.Response{
- Body: ioutil.NopCloser(bytes.NewBufferString(responseBody)),
- }
-
- expected := []string{"artemis", "diana"}
-
- actual, err := ExtractNames(listResult)
- if err != nil {
- t.Errorf("Error extracting container names: %s", err)
- }
-
- if !reflect.DeepEqual(expected, actual) {
- t.Errorf("Expected: %+v\nActual:%+v", expected, actual)
- }
-}
diff --git a/openstack/storage/v1/containers/requests.go b/openstack/storage/v1/containers/requests.go
index a5435a2..3a6a265 100644
--- a/openstack/storage/v1/containers/requests.go
+++ b/openstack/storage/v1/containers/requests.go
@@ -1,57 +1,39 @@
package containers
import (
- "net/http"
-
"github.com/racker/perigee"
"github.com/rackspace/gophercloud"
- "github.com/rackspace/gophercloud/openstack/utils"
"github.com/rackspace/gophercloud/pagination"
)
-// ListResult is a *http.Response that is returned from a call to the List function.
-type ListResult struct {
- pagination.MarkerPageBase
+// ListOpts is a structure that holds options for listing containers.
+type ListOpts struct {
+ Full bool
+ Limit int `q:"limit"`
+ Marker string `q:"marker"`
+ EndMarker string `q:"end_marker"`
+ Format string `q:"format"`
+ Prefix string `q:"prefix"`
+ Delimiter []byte `q:"delimiter"`
}
-// IsEmpty returns true if a ListResult contains no container names.
-func (r ListResult) IsEmpty() (bool, error) {
- names, err := ExtractNames(r)
- if err != nil {
- return true, err
- }
- return len(names) == 0, nil
-}
-
-// LastMarker returns the last container name in a ListResult.
-func (r ListResult) LastMarker() (string, error) {
- names, err := ExtractNames(r)
- if err != nil {
- return "", err
- }
- if len(names) == 0 {
- return "", nil
- }
- return names[len(names)-1], nil
-}
-
-// GetResult is a *http.Response that is returned from a call to the Get function.
-type GetResult *http.Response
-
// List is a function that retrieves all objects in a container. It also returns the details
// for the account. To extract just the container information or names, pass the ListResult
// response to the ExtractInfo or ExtractNames function, respectively.
func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager {
var headers map[string]string
- query := utils.BuildQuery(opts.Params)
+ query, err := gophercloud.BuildQueryString(opts)
+ if err != nil {
+ return pagination.Pager{Err: err}
+ }
if !opts.Full {
- headers = map[string]string{"Content-Type": "text/plain"}
+ headers = map[string]string{"Accept": "text/plain"}
}
createPage := func(r pagination.LastHTTPResponse) pagination.Page {
- p := ListResult{pagination.MarkerPageBase{LastHTTPResponse: r}}
+ p := ContainerPage{pagination.MarkerPageBase{LastHTTPResponse: r}}
p.MarkerPageBase.Owner = p
return p
}
@@ -62,13 +44,30 @@
return pager
}
-// Create is a function that creates a new container.
-func Create(c *gophercloud.ServiceClient, opts CreateOpts) (Container, error) {
- var ci Container
+// CreateOpts is a structure that holds parameters for creating a container.
+type CreateOpts struct {
+ Metadata map[string]string
+ ContainerRead string `h:"X-Container-Read"`
+ ContainerSyncTo string `h:"X-Container-Sync-To"`
+ ContainerSyncKey string `h:"X-Container-Sync-Key"`
+ ContainerWrite string `h:"X-Container-Write"`
+ ContentType string `h:"Content-Type"`
+ DetectContentType bool `h:"X-Detect-Content-Type"`
+ IfNoneMatch string `h:"If-None-Match"`
+ VersionsLocation string `h:"X-Versions-Location"`
+}
+// Create is a function that creates a new container.
+func Create(c *gophercloud.ServiceClient, containerName string, opts CreateOpts) (Container, error) {
+ var container Container
h := c.Provider.AuthenticatedHeaders()
- for k, v := range opts.Headers {
+ headers, err := gophercloud.BuildHeaders(opts)
+ if err != nil {
+ return container, err
+ }
+
+ for k, v := range headers {
h[k] = v
}
@@ -76,38 +75,49 @@
h["X-Container-Meta-"+k] = v
}
- url := containerURL(c, opts.Name)
- _, err := perigee.Request("PUT", url, perigee.Options{
+ _, err = perigee.Request("PUT", containerURL(c, containerName), perigee.Options{
MoreHeaders: h,
OkCodes: []int{201, 204},
})
if err == nil {
- ci = Container{
- "name": opts.Name,
- }
+ container = Container{"name": containerName}
}
- return ci, err
+ return container, err
}
// Delete is a function that deletes a container.
-func Delete(c *gophercloud.ServiceClient, opts DeleteOpts) error {
- h := c.Provider.AuthenticatedHeaders()
-
- query := utils.BuildQuery(opts.Params)
-
- url := containerURL(c, opts.Name) + query
- _, err := perigee.Request("DELETE", url, perigee.Options{
- MoreHeaders: h,
+func Delete(c *gophercloud.ServiceClient, containerName string) error {
+ _, err := perigee.Request("DELETE", containerURL(c, containerName), perigee.Options{
+ MoreHeaders: c.Provider.AuthenticatedHeaders(),
OkCodes: []int{204},
})
return err
}
+// UpdateOpts is a structure that holds parameters for updating, creating, or deleting a
+// container's metadata.
+type UpdateOpts struct {
+ Metadata map[string]string
+ ContainerRead string `h:"X-Container-Read"`
+ ContainerSyncTo string `h:"X-Container-Sync-To"`
+ ContainerSyncKey string `h:"X-Container-Sync-Key"`
+ ContainerWrite string `h:"X-Container-Write"`
+ ContentType string `h:"Content-Type"`
+ DetectContentType bool `h:"X-Detect-Content-Type"`
+ RemoveVersionsLocation string `h:"X-Remove-Versions-Location"`
+ VersionsLocation string `h:"X-Versions-Location"`
+}
+
// Update is a function that creates, updates, or deletes a container's metadata.
-func Update(c *gophercloud.ServiceClient, opts UpdateOpts) error {
+func Update(c *gophercloud.ServiceClient, containerName string, opts UpdateOpts) error {
h := c.Provider.AuthenticatedHeaders()
- for k, v := range opts.Headers {
+ headers, err := gophercloud.BuildHeaders(opts)
+ if err != nil {
+ return err
+ }
+
+ for k, v := range headers {
h[k] = v
}
@@ -115,8 +125,8 @@
h["X-Container-Meta-"+k] = v
}
- url := containerURL(c, opts.Name)
- _, err := perigee.Request("POST", url, perigee.Options{
+ url := containerURL(c, containerName)
+ _, err = perigee.Request("POST", url, perigee.Options{
MoreHeaders: h,
OkCodes: []int{204},
})
@@ -125,13 +135,13 @@
// Get is a function that retrieves the metadata of a container. To extract just the custom
// metadata, pass the GetResult response to the ExtractMetadata function.
-func Get(c *gophercloud.ServiceClient, opts GetOpts) (GetResult, error) {
- h := c.Provider.AuthenticatedHeaders()
-
- url := containerURL(c, opts.Name)
- resp, err := perigee.Request("HEAD", url, perigee.Options{
- MoreHeaders: h,
+func Get(c *gophercloud.ServiceClient, containerName string) GetResult {
+ var gr GetResult
+ resp, err := perigee.Request("HEAD", containerURL(c, containerName), perigee.Options{
+ MoreHeaders: c.Provider.AuthenticatedHeaders(),
OkCodes: []int{204},
})
- return &resp.HttpResponse, err
+ gr.Err = err
+ gr.Resp = &resp.HttpResponse
+ return gr
}
diff --git a/openstack/storage/v1/containers/requests_test.go b/openstack/storage/v1/containers/requests_test.go
index 871cb21..3b59e00 100644
--- a/openstack/storage/v1/containers/requests_test.go
+++ b/openstack/storage/v1/containers/requests_test.go
@@ -1,14 +1,16 @@
package containers
import (
+ "fmt"
"net/http"
"testing"
"github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/pagination"
"github.com/rackspace/gophercloud/testhelper"
)
-const (
+const (
tokenId = "abcabcabcabc"
)
@@ -29,12 +31,42 @@
testhelper.TestMethod(t, r, "GET")
testhelper.TestHeader(t, r, "X-Auth-Token", tokenId)
testhelper.TestHeader(t, r, "Accept", "application/json")
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprintf(w, `[{'count': 0,'bytes': 0,'name': 'janeausten'},{'count': 1,'bytes': 14,'name': 'marktwain'}]`)
})
client := serviceClient()
- _, err := List(client, ListOpts{Full: true})
- if err != nil {
- t.Fatalf("Unexpected error listing containers info: %v", err)
+ count := 0
+ List(client, ListOpts{Full: true}).EachPage(func(page pagination.Page) (bool, error) {
+ count++
+ actual, err := ExtractInfo(page)
+ if err != nil {
+ t.Errorf("Failed to extract container info: %v", err)
+ return false, err
+ }
+
+ expected := []Container{
+ Container{
+ "count": 0,
+ "bytes": 0,
+ "name": "janeausten",
+ },
+ Container{
+ "count": 1,
+ "bytes": 14,
+ "name": "marktwain",
+ },
+ }
+
+ testhelper.CheckDeepEquals(t, expected, actual)
+
+ return true, nil
+ })
+
+ if count != 1 {
+ t.Errorf("Expected 1 page, got %d", count)
}
}
@@ -46,12 +78,31 @@
testhelper.TestMethod(t, r, "GET")
testhelper.TestHeader(t, r, "X-Auth-Token", tokenId)
testhelper.TestHeader(t, r, "Accept", "text/plain")
+
+ w.Header().Set("Content-Type", "text/plain")
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprintf(w, "")
})
client := serviceClient()
- _, err := List(client, ListOpts{})
- if err != nil {
- t.Fatalf("Unexpected error listing containers info: %v", err)
+ count := 0
+ List(client, ListOpts{Full: false}).EachPage(func(page pagination.Page) (bool, error) {
+ count++
+ actual, err := ExtractNames(page)
+ if err != nil {
+ t.Errorf("Failed to extract container names: %v", err)
+ return false, err
+ }
+
+ expected := []string{"janeausten, marktwain"}
+
+ testhelper.CheckDeepEquals(t, expected, actual)
+
+ return true, nil
+ })
+
+ if count != 0 {
+ t.Fatalf("Expected 0 pages, got %d", count)
}
}
@@ -67,9 +118,7 @@
})
client := serviceClient()
- _, err := Create(client, CreateOpts{
- Name: "testContainer",
- })
+ _, err := Create(client, "testContainer", CreateOpts{})
if err != nil {
t.Fatalf("Unexpected error creating container: %v", err)
}
@@ -87,9 +136,7 @@
})
client := serviceClient()
- err := Delete(client, DeleteOpts{
- Name: "testContainer",
- })
+ err := Delete(client, "testContainer")
if err != nil {
t.Fatalf("Unexpected error deleting container: %v", err)
}
@@ -107,9 +154,7 @@
})
client := serviceClient()
- err := Update(client, UpdateOpts{
- Name: "testContainer",
- })
+ err := Update(client, "testContainer", UpdateOpts{})
if err != nil {
t.Fatalf("Unexpected error updating container metadata: %v", err)
}
@@ -124,12 +169,13 @@
testhelper.TestHeader(t, r, "X-Auth-Token", tokenId)
testhelper.TestHeader(t, r, "Accept", "application/json")
w.WriteHeader(http.StatusNoContent)
+ fmt.Fprintf(w, `
+
+ `)
})
client := serviceClient()
- _, err := Get(client, GetOpts{
- Name: "testContainer",
- })
+ _, err := Get(client, "testContainer").ExtractMetadata()
if err != nil {
t.Fatalf("Unexpected error getting container metadata: %v", err)
}
diff --git a/openstack/storage/v1/containers/results.go b/openstack/storage/v1/containers/results.go
new file mode 100644
index 0000000..6a93b4d
--- /dev/null
+++ b/openstack/storage/v1/containers/results.go
@@ -0,0 +1,134 @@
+package containers
+
+import (
+ "fmt"
+ "github.com/mitchellh/mapstructure"
+ "github.com/rackspace/gophercloud"
+ "github.com/rackspace/gophercloud/pagination"
+ "net/http"
+ "strings"
+)
+
+type Container map[string]interface{}
+
+type commonResult struct {
+ gophercloud.CommonResult
+}
+
+func (r GetResult) Extract() (*Container, error) {
+ if r.Err != nil {
+ return nil, r.Err
+ }
+
+ var res struct {
+ Container *Container
+ }
+
+ err := mapstructure.Decode(r.Resp, &res)
+ if err != nil {
+ return nil, fmt.Errorf("Error decoding Object Storage Container: %v", err)
+ }
+
+ return res.Container, nil
+}
+
+type CreateResult struct {
+ commonResult
+}
+
+// GetResult represents the result of a get operation.
+type GetResult struct {
+ Resp *http.Response
+ Err error
+}
+
+// UpdateResult represents the result of an update operation.
+type UpdateResult commonResult
+
+// DeleteResult represents the result of a delete operation.
+type DeleteResult commonResult
+
+// ListResult is a *http.Response that is returned from a call to the List function.
+type ContainerPage struct {
+ pagination.MarkerPageBase
+}
+
+// IsEmpty returns true if a ListResult contains no container names.
+func (r ContainerPage) IsEmpty() (bool, error) {
+ names, err := ExtractNames(r)
+ if err != nil {
+ return true, err
+ }
+ return len(names) == 0, nil
+}
+
+// LastMarker returns the last container name in a ListResult.
+func (r ContainerPage) LastMarker() (string, error) {
+ names, err := ExtractNames(r)
+ if err != nil {
+ return "", err
+ }
+ if len(names) == 0 {
+ return "", nil
+ }
+ return names[len(names)-1], nil
+}
+
+// ExtractInfo is a function that takes a ListResult and returns the containers' information.
+func ExtractInfo(page pagination.Page) ([]Container, error) {
+ untyped := page.(ContainerPage).Body.([]interface{})
+ results := make([]Container, len(untyped))
+ for index, each := range untyped {
+ results[index] = Container(each.(map[string]interface{}))
+ }
+ return results, nil
+}
+
+// ExtractNames is a function that takes a ListResult and returns the containers' names.
+func ExtractNames(page pagination.Page) ([]string, error) {
+ casted := page.(ContainerPage)
+ ct := casted.Header.Get("Content-Type")
+
+ switch {
+ case strings.HasPrefix(ct, "application/json"):
+ parsed, err := ExtractInfo(page)
+ if err != nil {
+ return nil, err
+ }
+
+ names := make([]string, 0, len(parsed))
+ for _, container := range parsed {
+ names = append(names, container["name"].(string))
+ }
+ return names, nil
+ case strings.HasPrefix(ct, "text/plain"):
+ names := make([]string, 0, 50)
+
+ body := string(page.(ContainerPage).Body.([]uint8))
+ for _, name := range strings.Split(body, "\n") {
+ if len(name) > 0 {
+ names = append(names, name)
+ }
+ }
+
+ return names, nil
+ default:
+ return nil, fmt.Errorf("Cannot extract names from response with content-type: [%s]", ct)
+ }
+}
+
+// ExtractMetadata is a function that takes a GetResult (of type *http.Response)
+// and returns the custom metadata associated with the container.
+func (gr GetResult) ExtractMetadata() (map[string]string, error) {
+ if gr.Err != nil {
+ return nil, gr.Err
+ }
+ metadata := make(map[string]string)
+ for k, v := range gr.Resp.Header {
+ if strings.HasPrefix(k, "X-Container-Meta-") {
+ key := strings.TrimPrefix(k, "X-Container-Meta-")
+ metadata[key] = v[0]
+ }
+ }
+ return metadata, nil
+}