Skip to content

Commit 433c29e

Browse files
Provide a ListOpts helper
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
1 parent de873b9 commit 433c29e

File tree

2 files changed

+129
-0
lines changed

2 files changed

+129
-0
lines changed

query/list.go

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package query
2+
3+
import (
4+
"fmt"
5+
"net/url"
6+
)
7+
8+
func New() *ListOpts {
9+
return &ListOpts{}
10+
}
11+
12+
// ListOpts can be used to list multiple resources.
13+
type ListOpts struct {
14+
query url.Values
15+
}
16+
17+
// And adds an arbitrary number of permutations of a single property to filter
18+
// in. When a single ListOpts is called multiple times with the same property
19+
// name, the resulting query contains the resulting intersection (AND). Note
20+
// that how these properties are combined in OpenStack depend on the property.
21+
// For example: passing multiple "id" behaves like an OR. Instead, passing
22+
// multiple "tags" will only return resources that have ALL those tags. This
23+
// helper function only combines the parameters in the most straightforward
24+
// way; please refer to the OpenStack documented behaviour to know how these
25+
// parameters are treated.
26+
//
27+
// ListOpts is currently implemented for three Network resources:
28+
//
29+
// * ports
30+
// * networks
31+
// * subnets
32+
func (o *ListOpts) And(property string, values ...interface{}) *ListOpts {
33+
if o.query == nil {
34+
o.query = url.Values{}
35+
}
36+
if existingValues, ok := o.query[property]; ok {
37+
// There already are values of the same property: we AND them
38+
// with the new ones. We only keep the values that exist in
39+
// both `o.query` AND in `values`.
40+
41+
// First, to avoid nested loops, we build a hashmap with the
42+
// new values.
43+
newValuesSet := make(map[string]struct{})
44+
for _, newValue := range values {
45+
newValuesSet[fmt.Sprint(newValue)] = struct{}{}
46+
}
47+
48+
// intersectedValues is a slice which will contain the values
49+
// that we want to keep. They will be at most as many as what
50+
// we already have; that's what we set the slice capacity to.
51+
intersectedValues := make([]string, 0, len(existingValues))
52+
53+
// We add each existing value to intersectedValues if and only
54+
// if it's also present in the new set.
55+
for _, existingValue := range existingValues {
56+
if _, ok := newValuesSet[existingValue]; ok {
57+
intersectedValues = append(intersectedValues, existingValue)
58+
}
59+
}
60+
o.query[property] = intersectedValues
61+
return o
62+
}
63+
64+
for _, v := range values {
65+
o.query.Add(property, fmt.Sprint(v))
66+
}
67+
68+
return o
69+
}
70+
71+
func (o ListOpts) String() string {
72+
return "?" + o.query.Encode()
73+
}
74+
75+
func (o ListOpts) ToPortListQuery() (string, error) {
76+
return o.String(), nil
77+
}
78+
79+
func (o ListOpts) ToNetworkListQuery() (string, error) {
80+
return o.String(), nil
81+
}
82+
83+
func (o ListOpts) ToSubnetListQuery() (string, error) {
84+
return o.String(), nil
85+
}

query/list_test.go

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package query_test
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
7+
"github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
8+
"github.com/gophercloud/gophercloud/openstack/networking/v2/subnets"
9+
"github.com/gophercloud/utils/query"
10+
)
11+
12+
var _ networks.ListOptsBuilder = (*query.ListOpts)(nil)
13+
var _ ports.ListOptsBuilder = (*query.ListOpts)(nil)
14+
var _ subnets.ListOptsBuilder = (*query.ListOpts)(nil)
15+
16+
func ExampleListOpts_And_by_id() {
17+
q := query.New().
18+
And("id", 123, 321, 12345)
19+
fmt.Println(q)
20+
//Output: ?id=123&id=321&id=12345
21+
}
22+
23+
func ExampleListOpts_And_by_name() {
24+
q := query.New().
25+
And("name", "port-1", "port-&321", "the-other-port")
26+
fmt.Println(q)
27+
//Output: ?name=port-1&name=port-%26321&name=the-other-port
28+
}
29+
30+
func ExampleListOpts_And_by_Name_and_tag() {
31+
q := query.New().
32+
And("name", "port-1", "port-3").
33+
And("tags", "my-tag")
34+
fmt.Println(q)
35+
//Output: ?name=port-1&name=port-3&tags=my-tag
36+
}
37+
38+
func ExampleListOpts_And_by_id_twice() {
39+
q := query.New().
40+
And("id", 1, 2, 3).
41+
And("id", 2, 3, 4)
42+
fmt.Println(q)
43+
//Output: ?id=2&id=3
44+
}

0 commit comments

Comments
 (0)