// NextPageURL extracts the pagination structure from a JSON response and returns the "next" link, if one is present. // It assumes that the links are available in a "links" element of the top-level response object. // If this is not the case, override NextPageURL on your result type. func (current LinkedPageBase) NextPageURL() (string, error) { var path []string var key string if current.LinkPath == nil { path = []string{"links", "next"} } else { path = current.LinkPath } submap, ok := current.Body.(map[string]interface{}) if !ok { err := gophercloud.ErrUnexpectedType{} err.Expected = "map[string]interface{}" err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body)) return "", err } for { key, path = path[0], path[1:len(path)] value, ok := submap[key] if !ok { return "", nil } if len(path) > 0 { submap, ok = value.(map[string]interface{}) if !ok { err := gophercloud.ErrUnexpectedType{} err.Expected = "map[string]interface{}" err.Actual = fmt.Sprintf("%v", reflect.TypeOf(value)) return "", err } } else { if value == nil { // Actual null element. return "", nil } url, ok := value.(string) if !ok { err := gophercloud.ErrUnexpectedType{} err.Expected = "string" err.Actual = fmt.Sprintf("%v", reflect.TypeOf(value)) return "", err } return url, nil } } }
// IsEmpty satisifies the IsEmpty method of the Page interface func (current SinglePageBase) IsEmpty() (bool, error) { if b, ok := current.Body.([]interface{}); ok { return len(b) == 0, nil } err := gophercloud.ErrUnexpectedType{} err.Expected = "[]interface{}" err.Actual = fmt.Sprintf("%v", reflect.TypeOf(current.Body)) return true, err }
// AllPages returns all the pages from a `List` operation in a single page, // allowing the user to retrieve all the pages at once. func (p Pager) AllPages() (Page, error) { // pagesSlice holds all the pages until they get converted into as Page Body. var pagesSlice []interface{} // body will contain the final concatenated Page body. var body reflect.Value // Grab a test page to ascertain the page body type. testPage, err := p.fetchNextPage(p.initialURL) if err != nil { return nil, err } // Store the page type so we can use reflection to create a new mega-page of // that type. pageType := reflect.TypeOf(testPage) // if it's a single page, just return the testPage (first page) if _, found := pageType.FieldByName("SinglePageBase"); found { return testPage, nil } // Switch on the page body type. Recognized types are `map[string]interface{}`, // `[]byte`, and `[]interface{}`. switch testPage.GetBody().(type) { case map[string]interface{}: // key is the map key for the page body if the body type is `map[string]interface{}`. var key string // Iterate over the pages to concatenate the bodies. err = p.EachPage(func(page Page) (bool, error) { b := page.GetBody().(map[string]interface{}) for k := range b { // If it's a linked page, we don't want the `links`, we want the other one. if !strings.HasSuffix(k, "links") { key = k } } switch keyType := b[key].(type) { case map[string]interface{}: pagesSlice = append(pagesSlice, keyType) case []interface{}: pagesSlice = append(pagesSlice, b[key].([]interface{})...) default: return false, fmt.Errorf("Unsupported page body type: %+v", keyType) } return true, nil }) if err != nil { return nil, err } // Set body to value of type `map[string]interface{}` body = reflect.MakeMap(reflect.MapOf(reflect.TypeOf(key), reflect.TypeOf(pagesSlice))) body.SetMapIndex(reflect.ValueOf(key), reflect.ValueOf(pagesSlice)) case []byte: // Iterate over the pages to concatenate the bodies. err = p.EachPage(func(page Page) (bool, error) { b := page.GetBody().([]byte) pagesSlice = append(pagesSlice, b) // seperate pages with a comma pagesSlice = append(pagesSlice, []byte{10}) return true, nil }) if err != nil { return nil, err } if len(pagesSlice) > 0 { // Remove the trailing comma. pagesSlice = pagesSlice[:len(pagesSlice)-1] } var b []byte // Combine the slice of slices in to a single slice. for _, slice := range pagesSlice { b = append(b, slice.([]byte)...) } // Set body to value of type `bytes`. body = reflect.New(reflect.TypeOf(b)).Elem() body.SetBytes(b) case []interface{}: // Iterate over the pages to concatenate the bodies. err = p.EachPage(func(page Page) (bool, error) { b := page.GetBody().([]interface{}) pagesSlice = append(pagesSlice, b...) return true, nil }) if err != nil { return nil, err } // Set body to value of type `[]interface{}` body = reflect.MakeSlice(reflect.TypeOf(pagesSlice), len(pagesSlice), len(pagesSlice)) for i, s := range pagesSlice { body.Index(i).Set(reflect.ValueOf(s)) } default: err := gophercloud.ErrUnexpectedType{} err.Expected = "map[string]interface{}/[]byte/[]interface{}" err.Actual = fmt.Sprintf("%v", reflect.TypeOf(testPage.GetBody())) return nil, err } // Each `Extract*` function is expecting a specific type of page coming back, // otherwise the type assertion in those functions will fail. pageType is needed // to create a type in this method that has the same type that the `Extract*` // function is expecting and set the Body of that object to the concatenated // pages. page := reflect.New(pageType) // Set the page body to be the concatenated pages. page.Elem().FieldByName("Body").Set(body) // Set any additional headers that were pass along. The `objectstorage` pacakge, // for example, passes a Content-Type header. h := make(http.Header) for k, v := range p.Headers { h.Add(k, v) } page.Elem().FieldByName("Header").Set(reflect.ValueOf(h)) // Type assert the page to a Page interface so that the type assertion in the // `Extract*` methods will work. return page.Elem().Interface().(Page), err }