Skip to content

Commit

Permalink
Provide a ListOpts helper
Browse files Browse the repository at this point in the history
With `query.ListOpts`, it should be easier to list resources based on
repeated properties. For example: get information about multiple ports
by ID with a single call.

ListOpts is currently implemented for three Network resources:
* ports
* networks
* subnets
  • Loading branch information
pierreprinetti committed May 23, 2023
1 parent de873b9 commit 433c29e
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 0 deletions.
85 changes: 85 additions & 0 deletions query/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package query

import (
"fmt"
"net/url"
)

func New() *ListOpts {
return &ListOpts{}
}

// ListOpts can be used to list multiple resources.
type ListOpts struct {
query url.Values
}

// And adds an arbitrary number of permutations of a single property to filter
// in. When a single ListOpts is called multiple times with the same property
// name, the resulting query contains the resulting intersection (AND). Note
// that how these properties are combined in OpenStack depend on the property.
// For example: passing multiple "id" behaves like an OR. Instead, passing
// multiple "tags" will only return resources that have ALL those tags. This
// helper function only combines the parameters in the most straightforward
// way; please refer to the OpenStack documented behaviour to know how these
// parameters are treated.
//
// ListOpts is currently implemented for three Network resources:
//
// * ports
// * networks
// * subnets
func (o *ListOpts) And(property string, values ...interface{}) *ListOpts {
if o.query == nil {
o.query = url.Values{}
}
if existingValues, ok := o.query[property]; ok {
// There already are values of the same property: we AND them
// with the new ones. We only keep the values that exist in
// both `o.query` AND in `values`.

// First, to avoid nested loops, we build a hashmap with the
// new values.
newValuesSet := make(map[string]struct{})
for _, newValue := range values {
newValuesSet[fmt.Sprint(newValue)] = struct{}{}
}

// intersectedValues is a slice which will contain the values
// that we want to keep. They will be at most as many as what
// we already have; that's what we set the slice capacity to.
intersectedValues := make([]string, 0, len(existingValues))

// We add each existing value to intersectedValues if and only
// if it's also present in the new set.
for _, existingValue := range existingValues {
if _, ok := newValuesSet[existingValue]; ok {
intersectedValues = append(intersectedValues, existingValue)
}
}
o.query[property] = intersectedValues
return o
}

for _, v := range values {
o.query.Add(property, fmt.Sprint(v))
}

return o
}

func (o ListOpts) String() string {
return "?" + o.query.Encode()
}

func (o ListOpts) ToPortListQuery() (string, error) {
return o.String(), nil
}

func (o ListOpts) ToNetworkListQuery() (string, error) {
return o.String(), nil
}

func (o ListOpts) ToSubnetListQuery() (string, error) {
return o.String(), nil
}
44 changes: 44 additions & 0 deletions query/list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package query_test

import (
"fmt"

"github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
"github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
"github.com/gophercloud/gophercloud/openstack/networking/v2/subnets"
"github.com/gophercloud/utils/query"
)

var _ networks.ListOptsBuilder = (*query.ListOpts)(nil)
var _ ports.ListOptsBuilder = (*query.ListOpts)(nil)
var _ subnets.ListOptsBuilder = (*query.ListOpts)(nil)

func ExampleListOpts_And_by_id() {
q := query.New().
And("id", 123, 321, 12345)
fmt.Println(q)
//Output: ?id=123&id=321&id=12345
}

func ExampleListOpts_And_by_name() {
q := query.New().
And("name", "port-1", "port-&321", "the-other-port")
fmt.Println(q)
//Output: ?name=port-1&name=port-%26321&name=the-other-port
}

func ExampleListOpts_And_by_Name_and_tag() {
q := query.New().
And("name", "port-1", "port-3").
And("tags", "my-tag")
fmt.Println(q)
//Output: ?name=port-1&name=port-3&tags=my-tag
}

func ExampleListOpts_And_by_id_twice() {
q := query.New().
And("id", 1, 2, 3).
And("id", 2, 3, 4)
fmt.Println(q)
//Output: ?id=2&id=3
}

0 comments on commit 433c29e

Please sign in to comment.