From 433c29e0f0f079c3cb1a3e2972e6c1697428358e Mon Sep 17 00:00:00 2001 From: Pierre Prinetti Date: Mon, 22 May 2023 17:11:38 +0200 Subject: [PATCH] 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 --- query/list.go | 85 ++++++++++++++++++++++++++++++++++++++++++++++ query/list_test.go | 44 ++++++++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 query/list.go create mode 100644 query/list_test.go diff --git a/query/list.go b/query/list.go new file mode 100644 index 00000000..58c6c15a --- /dev/null +++ b/query/list.go @@ -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 +} diff --git a/query/list_test.go b/query/list_test.go new file mode 100644 index 00000000..17f8e757 --- /dev/null +++ b/query/list_test.go @@ -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 +}