| package pagination | 
 |  | 
 | import ( | 
 | 	"errors" | 
 | 	"fmt" | 
 | 	"net/http" | 
 | 	"reflect" | 
 | 	"strings" | 
 |  | 
 | 	"gerrit.mcp.mirantis.net/debian/gophercloud.git" | 
 | ) | 
 |  | 
 | var ( | 
 | 	// ErrPageNotAvailable is returned from a Pager when a next or previous page is requested, but does not exist. | 
 | 	ErrPageNotAvailable = errors.New("The requested page does not exist.") | 
 | ) | 
 |  | 
 | // Page must be satisfied by the result type of any resource collection. | 
 | // It allows clients to interact with the resource uniformly, regardless of whether or not or how it's paginated. | 
 | // Generally, rather than implementing this interface directly, implementors should embed one of the concrete PageBase structs, | 
 | // instead. | 
 | // Depending on the pagination strategy of a particular resource, there may be an additional subinterface that the result type | 
 | // will need to implement. | 
 | type Page interface { | 
 |  | 
 | 	// NextPageURL generates the URL for the page of data that follows this collection. | 
 | 	// Return "" if no such page exists. | 
 | 	NextPageURL() (string, error) | 
 |  | 
 | 	// IsEmpty returns true if this Page has no items in it. | 
 | 	IsEmpty() (bool, error) | 
 |  | 
 | 	// GetBody returns the Page Body. This is used in the `AllPages` method. | 
 | 	GetBody() interface{} | 
 | } | 
 |  | 
 | // Pager knows how to advance through a specific resource collection, one page at a time. | 
 | type Pager struct { | 
 | 	client *gophercloud.ServiceClient | 
 |  | 
 | 	initialURL string | 
 |  | 
 | 	createPage func(r PageResult) Page | 
 |  | 
 | 	Err error | 
 |  | 
 | 	// Headers supplies additional HTTP headers to populate on each paged request. | 
 | 	Headers map[string]string | 
 | } | 
 |  | 
 | // NewPager constructs a manually-configured pager. | 
 | // Supply the URL for the first page, a function that requests a specific page given a URL, and a function that counts a page. | 
 | func NewPager(client *gophercloud.ServiceClient, initialURL string, createPage func(r PageResult) Page) Pager { | 
 | 	return Pager{ | 
 | 		client:     client, | 
 | 		initialURL: initialURL, | 
 | 		createPage: createPage, | 
 | 	} | 
 | } | 
 |  | 
 | // WithPageCreator returns a new Pager that substitutes a different page creation function. This is | 
 | // useful for overriding List functions in delegation. | 
 | func (p Pager) WithPageCreator(createPage func(r PageResult) Page) Pager { | 
 | 	return Pager{ | 
 | 		client:     p.client, | 
 | 		initialURL: p.initialURL, | 
 | 		createPage: createPage, | 
 | 	} | 
 | } | 
 |  | 
 | func (p Pager) fetchNextPage(url string) (Page, error) { | 
 | 	resp, err := Request(p.client, p.Headers, url) | 
 | 	if err != nil { | 
 | 		return nil, err | 
 | 	} | 
 |  | 
 | 	remembered, err := PageResultFrom(resp) | 
 | 	if err != nil { | 
 | 		return nil, err | 
 | 	} | 
 |  | 
 | 	return p.createPage(remembered), nil | 
 | } | 
 |  | 
 | // EachPage iterates over each page returned by a Pager, yielding one at a time to a handler function. | 
 | // Return "false" from the handler to prematurely stop iterating. | 
 | func (p Pager) EachPage(handler func(Page) (bool, error)) error { | 
 | 	if p.Err != nil { | 
 | 		return p.Err | 
 | 	} | 
 | 	currentURL := p.initialURL | 
 | 	for { | 
 | 		currentPage, err := p.fetchNextPage(currentURL) | 
 | 		if err != nil { | 
 | 			return err | 
 | 		} | 
 |  | 
 | 		empty, err := currentPage.IsEmpty() | 
 | 		if err != nil { | 
 | 			return err | 
 | 		} | 
 | 		if empty { | 
 | 			return nil | 
 | 		} | 
 |  | 
 | 		ok, err := handler(currentPage) | 
 | 		if err != nil { | 
 | 			return err | 
 | 		} | 
 | 		if !ok { | 
 | 			return nil | 
 | 		} | 
 |  | 
 | 		currentURL, err = currentPage.NextPageURL() | 
 | 		if err != nil { | 
 | 			return err | 
 | 		} | 
 | 		if currentURL == "" { | 
 | 			return nil | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | // 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 pb := 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, v := range b { | 
 | 				// If it's a linked page, we don't want the `links`, we want the other one. | 
 | 				if !strings.HasSuffix(k, "links") { | 
 | 					// check the field's type. we only want []interface{} (which is really []map[string]interface{}) | 
 | 					switch vt := v.(type) { | 
 | 					case []interface{}: | 
 | 						key = k | 
 | 						pagesSlice = append(pagesSlice, vt...) | 
 | 					} | 
 | 				} | 
 | 			} | 
 | 			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("%T", pb) | 
 | 		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 | 
 | } |