openapi/pager.go (206 lines of code) (raw):

// Copyright (c) 2009-present, Alibaba Cloud All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package openapi import ( "encoding/json" "fmt" "math" "strconv" "strings" "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" "github.com/aliyun/aliyun-cli/v3/cli" "github.com/aliyun/aliyun-cli/v3/i18n" jmespath "github.com/jmespath/go-jmespath" ) var PagerFlag = &cli.Flag{Category: "caller", Name: "pager", Hidden: false, AssignedMode: cli.AssignedRepeatable, Aliases: []string{"--all-pages"}, Short: i18n.T( "use `--pager` to merge pages for pageable APIs", "使用 `--pager` 在访问分页的API时合并结果分页"), Fields: []cli.Field{ {Key: "path", Required: false}, {Key: "PageNumber", DefaultValue: "PageNumber", Short: i18n.T(" PageNumber", "指定PageNumber的属性")}, {Key: "PageSize", DefaultValue: "PageSize", Short: i18n.T("PageSize", "")}, {Key: "TotalCount", DefaultValue: "TotalCount", Short: i18n.T("TotalCount", "")}, {Key: "NextToken", DefaultValue: "NextToken", Short: i18n.T("NextToken", "")}, }, ExcludeWith: []string{WaiterFlag.Name}, } type Pager struct { PageNumberFlag string PageSizeFlag string NextTokenFlag string PageNumberExpr string PageSizeExpr string TotalCountExpr string NextTokenExpr string PageSize int totalCount int currentPageNumber int nextTokenMode bool nextToken string collectionPath string results []interface{} } func GetPager() *Pager { if !PagerFlag.IsAssigned() { return nil } pager := &Pager{} pageNumberFlagTemp, _ := PagerFlag.GetFieldValue("PageNumber") tempStr := strings.Split(pageNumberFlagTemp, ".") pager.PageNumberFlag = tempStr[len(tempStr)-1] pageSizeFlagTemp, _ := PagerFlag.GetFieldValue("PageSize") tempStr = strings.Split(pageSizeFlagTemp, ".") pager.PageSizeFlag = tempStr[len(tempStr)-1] pager.PageNumberExpr, _ = PagerFlag.GetFieldValue("PageNumber") pager.PageSizeExpr, _ = PagerFlag.GetFieldValue("PageSize") pager.TotalCountExpr, _ = PagerFlag.GetFieldValue("TotalCount") nextTokenFlagTemp, _ := PagerFlag.GetFieldValue("NextToken") tempStr = strings.Split(nextTokenFlagTemp, ".") pager.NextTokenFlag = tempStr[len(tempStr)-1] pager.NextTokenExpr = nextTokenFlagTemp pager.collectionPath, _ = PagerFlag.GetFieldValue("path") return pager } func (a *Pager) CallWith(invoker Invoker) (string, error) { for { resp, err := invoker.Call() if err != nil { return "", err } err = a.FeedResponse(resp.GetHttpContentString()) if err != nil { return "", fmt.Errorf("call failed %s", err) } if !a.HasMore() { break } a.MoveNextPage(invoker.getRequest()) } return a.GetResponseCollection(), nil } func (a *Pager) HasMore() bool { if a.nextTokenMode { return a.nextToken != "" } pages := int(math.Ceil(float64(a.totalCount) / float64(a.PageSize))) return a.currentPageNumber < pages } func (a *Pager) GetResponseCollection() string { root := make(map[string]interface{}) current := make(map[string]interface{}) path := a.collectionPath l := strings.Index(path, ".") tempSlice := strings.Split(path, ".") if l > 0 { tempSlice = tempSlice[len(tempSlice)-2:] root[tempSlice[0]] = current } else { root = current } key := strings.TrimSuffix(tempSlice[len(tempSlice)-1], "[]") current[key] = a.results s, err := json.Marshal(root) if err != nil { panic(err) } return string(s) } func (a *Pager) FeedResponse(body string) error { var j interface{} err := json.Unmarshal([]byte(body), &j) if err != nil { return fmt.Errorf("unmarshal %s", err.Error()) } a.nextToken = "" if a.NextTokenExpr != "" { a.nextTokenMode = true // allow to ignore NextToken mode if val, err := jmespath.Search(a.NextTokenExpr, j); err == nil { if nextToken, ok := val.(string); ok { a.nextToken = nextToken } } else { return fmt.Errorf("jmespath: '%s' failed %s", a.NextTokenExpr, err) } } if !a.nextTokenMode { if total, err := jmespath.Search(a.TotalCountExpr, j); err == nil { var totalCount float64 if strCount, ok := total.(string); ok { totalCount, _ = strconv.ParseFloat(strCount, 64) } else { totalCount = total.(float64) } a.totalCount = int(totalCount) } else { return fmt.Errorf("jmespath: '%s' failed %s", a.TotalCountExpr, err) } if pageNumber, err := jmespath.Search(a.PageNumberExpr, j); err == nil { var currentPageNumber float64 if strpageNumber, ok := pageNumber.(string); ok { currentPageNumber, _ = strconv.ParseFloat(strpageNumber, 64) } else { currentPageNumber = pageNumber.(float64) } a.currentPageNumber = int(currentPageNumber) } else { return fmt.Errorf("jmespath: '%s' failed %s", a.PageNumberExpr, err) } if pageSize, err := jmespath.Search(a.PageSizeExpr, j); err == nil { var PageSize float64 if strpageSize, ok := pageSize.(string); ok { PageSize, _ = strconv.ParseFloat(strpageSize, 64) } else { PageSize = pageSize.(float64) } a.PageSize = int(PageSize) } else { return fmt.Errorf("jmespath: '%s' failed %s", a.PageSizeExpr, err) } } if a.collectionPath == "" { p2 := a.detectArrayPath(j) if p2 == "" { return fmt.Errorf("can't auto recognize collections path: you need add `--pager path=[jmespath]` to assign manually") } a.collectionPath = p2 } a.mergeCollections(j) return nil } func (a *Pager) MoveNextPage(request *requests.CommonRequest) { if a.nextTokenMode { request.QueryParams[a.NextTokenFlag] = a.nextToken } else { a.currentPageNumber = a.currentPageNumber + 1 // cli.Printf("Move to page %d", a.currentPageNumber) request.QueryParams[a.PageNumberFlag] = strconv.Itoa(a.currentPageNumber) } } func (a *Pager) mergeCollections(body interface{}) error { ar, err := jmespath.Search(a.collectionPath, body) if err != nil { return fmt.Errorf("jmespath search failed: %s", err.Error()) } else if ar == nil { return fmt.Errorf("jmespath result empty: %s", a.collectionPath) } a.results = append(a.results, ar.([]interface{})...) return nil } func (a *Pager) detectArrayPath(d interface{}) string { m, ok := d.(map[string]interface{}) if !ok { return "" } for k, v := range m { // t.Logf("%v %v\n", k, v) if m2, ok := v.(map[string]interface{}); ok { for k2, v2 := range m2 { if _, ok := v2.([]interface{}); ok { return fmt.Sprintf("%s.%s[]", k, k2) } } } } return "" }