Browse code

client: add Filters type

Add a new type to use for building filter predicates for API requests,
replacing "./api/types/filters".Args in the client. Remove the now
unused api/types/filters package.

Signed-off-by: Cory Snider <csnider@mirantis.com>

Cory Snider authored on 2025/10/07 04:18:41
Showing 106 changed files
1 1
deleted file mode 100644
... ...
@@ -1,24 +0,0 @@
1
-package filters
2
-
3
-import "fmt"
4
-
5
-// invalidFilter indicates that the provided filter or its value is invalid
6
-type invalidFilter struct {
7
-	Filter string
8
-	Value  []string
9
-}
10
-
11
-func (e invalidFilter) Error() string {
12
-	msg := "invalid filter"
13
-	if e.Filter != "" {
14
-		msg += " '" + e.Filter
15
-		if e.Value != nil {
16
-			msg = fmt.Sprintf("%s=%s", msg, e.Value)
17
-		}
18
-		msg += "'"
19
-	}
20
-	return msg
21
-}
22
-
23
-// InvalidParameter marks this error as ErrInvalidParameter
24
-func (e invalidFilter) InvalidParameter() {}
25 1
deleted file mode 100644
... ...
@@ -1,65 +0,0 @@
1
-/*
2
-Package filters provides tools for encoding a mapping of keys to a set of
3
-multiple values.
4
-*/
5
-package filters
6
-
7
-import (
8
-	"encoding/json"
9
-)
10
-
11
-// Args stores a mapping of keys to a set of multiple values.
12
-type Args struct {
13
-	fields map[string]map[string]bool
14
-}
15
-
16
-// KeyValuePair are used to initialize a new Args
17
-type KeyValuePair struct {
18
-	Key   string
19
-	Value string
20
-}
21
-
22
-// Arg creates a new KeyValuePair for initializing Args
23
-func Arg(key, value string) KeyValuePair {
24
-	return KeyValuePair{Key: key, Value: value}
25
-}
26
-
27
-// NewArgs returns a new Args populated with the initial args
28
-func NewArgs(initialArgs ...KeyValuePair) Args {
29
-	args := Args{fields: map[string]map[string]bool{}}
30
-	for _, arg := range initialArgs {
31
-		args.Add(arg.Key, arg.Value)
32
-	}
33
-	return args
34
-}
35
-
36
-// MarshalJSON returns a JSON byte representation of the Args
37
-func (args Args) MarshalJSON() ([]byte, error) {
38
-	if len(args.fields) == 0 {
39
-		return []byte("{}"), nil
40
-	}
41
-	return json.Marshal(args.fields)
42
-}
43
-
44
-// ToJSON returns the Args as a JSON encoded string
45
-func ToJSON(a Args) (string, error) {
46
-	if a.Len() == 0 {
47
-		return "", nil
48
-	}
49
-	buf, err := json.Marshal(a)
50
-	return string(buf), err
51
-}
52
-
53
-// Add a new value to the set of values
54
-func (args Args) Add(key, value string) {
55
-	if _, ok := args.fields[key]; ok {
56
-		args.fields[key][value] = true
57
-	} else {
58
-		args.fields[key] = map[string]bool{value: true}
59
-	}
60
-}
61
-
62
-// Len returns the number of keys in the mapping
63
-func (args Args) Len() int {
64
-	return len(args.fields)
65
-}
66 1
deleted file mode 100644
... ...
@@ -1,62 +0,0 @@
1
-package filters
2
-
3
-import (
4
-	"encoding/json"
5
-	"testing"
6
-
7
-	"gotest.tools/v3/assert"
8
-	is "gotest.tools/v3/assert/cmp"
9
-)
10
-
11
-func TestMarshalJSON(t *testing.T) {
12
-	a := NewArgs(
13
-		Arg("created", "today"),
14
-		Arg("image.name", "ubuntu*"),
15
-		Arg("image.name", "*untu"),
16
-	)
17
-
18
-	s, err := a.MarshalJSON()
19
-	assert.Check(t, err)
20
-	const expected = `{"created":{"today":true},"image.name":{"*untu":true,"ubuntu*":true}}`
21
-	assert.Check(t, is.Equal(string(s), expected))
22
-}
23
-
24
-func TestMarshalJSONWithEmpty(t *testing.T) {
25
-	s, err := json.Marshal(NewArgs())
26
-	assert.Check(t, err)
27
-	const expected = `{}`
28
-	assert.Check(t, is.Equal(string(s), expected))
29
-}
30
-
31
-func TestToJSON(t *testing.T) {
32
-	a := NewArgs(
33
-		Arg("created", "today"),
34
-		Arg("image.name", "ubuntu*"),
35
-		Arg("image.name", "*untu"),
36
-	)
37
-
38
-	s, err := ToJSON(a)
39
-	assert.Check(t, err)
40
-	const expected = `{"created":{"today":true},"image.name":{"*untu":true,"ubuntu*":true}}`
41
-	assert.Check(t, is.Equal(s, expected))
42
-}
43
-
44
-func TestAdd(t *testing.T) {
45
-	f := NewArgs()
46
-	f.Add("status", "running")
47
-	v := f.fields["status"]
48
-	assert.Check(t, is.DeepEqual(v, []string{"running"}))
49
-
50
-	f.Add("status", "paused")
51
-	v = f.fields["status"]
52
-	assert.Check(t, is.Len(v, 2))
53
-	assert.Check(t, is.Contains(v, "running"))
54
-	assert.Check(t, is.Contains(v, "paused"))
55
-}
56
-
57
-func TestLen(t *testing.T) {
58
-	f := NewArgs()
59
-	assert.Check(t, is.Equal(f.Len(), 0))
60
-	f.Add("status", "running")
61
-	assert.Check(t, is.Equal(f.Len(), 1))
62
-}
... ...
@@ -8,7 +8,6 @@ import (
8 8
 	"strconv"
9 9
 
10 10
 	"github.com/moby/moby/api/types/build"
11
-	"github.com/moby/moby/api/types/filters"
12 11
 	"github.com/moby/moby/api/types/versions"
13 12
 )
14 13
 
... ...
@@ -18,7 +17,7 @@ type BuildCachePruneOptions struct {
18 18
 	ReservedSpace int64
19 19
 	MaxUsedSpace  int64
20 20
 	MinFreeSpace  int64
21
-	Filters       filters.Args
21
+	Filters       Filters
22 22
 }
23 23
 
24 24
 // BuildCachePruneResult holds the result from the BuildCachePrune method.
... ...
@@ -49,11 +48,7 @@ func (cli *Client) BuildCachePrune(ctx context.Context, opts BuildCachePruneOpti
49 49
 	if opts.MinFreeSpace != 0 {
50 50
 		query.Set("min-free-space", strconv.Itoa(int(opts.MinFreeSpace)))
51 51
 	}
52
-	f, err := filters.ToJSON(opts.Filters)
53
-	if err != nil {
54
-		return BuildCachePruneResult{}, fmt.Errorf("prune could not marshal filters option: %w", err)
55
-	}
56
-	query.Set("filters", f)
52
+	opts.Filters.updateURLValues(query)
57 53
 
58 54
 	resp, err := cli.post(ctx, "/build/prune", query, nil, nil)
59 55
 	defer ensureReaderClosed(resp)
... ...
@@ -8,7 +8,6 @@ import (
8 8
 	"github.com/moby/moby/api/types"
9 9
 	"github.com/moby/moby/api/types/container"
10 10
 	"github.com/moby/moby/api/types/events"
11
-	"github.com/moby/moby/api/types/filters"
12 11
 	"github.com/moby/moby/api/types/image"
13 12
 	"github.com/moby/moby/api/types/network"
14 13
 	"github.com/moby/moby/api/types/plugin"
... ...
@@ -89,7 +88,7 @@ type ContainerAPIClient interface {
89 89
 	ContainerWait(ctx context.Context, container string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error)
90 90
 	CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, container.PathStat, error)
91 91
 	CopyToContainer(ctx context.Context, container, path string, content io.Reader, options CopyToContainerOptions) error
92
-	ContainersPrune(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error)
92
+	ContainersPrune(ctx context.Context, pruneFilters Filters) (container.PruneReport, error)
93 93
 }
94 94
 
95 95
 type ExecAPIClient interface {
... ...
@@ -119,7 +118,7 @@ type ImageAPIClient interface {
119 119
 	ImageRemove(ctx context.Context, image string, options ImageRemoveOptions) ([]image.DeleteResponse, error)
120 120
 	ImageSearch(ctx context.Context, term string, options ImageSearchOptions) ([]registry.SearchResult, error)
121 121
 	ImageTag(ctx context.Context, image, ref string) error
122
-	ImagesPrune(ctx context.Context, pruneFilter filters.Args) (image.PruneReport, error)
122
+	ImagesPrune(ctx context.Context, pruneFilter Filters) (image.PruneReport, error)
123 123
 
124 124
 	ImageInspect(ctx context.Context, image string, _ ...ImageInspectOption) (image.InspectResponse, error)
125 125
 	ImageHistory(ctx context.Context, image string, _ ...ImageHistoryOption) ([]image.HistoryResponseItem, error)
... ...
@@ -136,7 +135,7 @@ type NetworkAPIClient interface {
136 136
 	NetworkInspectWithRaw(ctx context.Context, network string, options NetworkInspectOptions) (network.Inspect, []byte, error)
137 137
 	NetworkList(ctx context.Context, options NetworkListOptions) ([]network.Summary, error)
138 138
 	NetworkRemove(ctx context.Context, network string) error
139
-	NetworksPrune(ctx context.Context, pruneFilter filters.Args) (network.PruneReport, error)
139
+	NetworksPrune(ctx context.Context, pruneFilter Filters) (network.PruneReport, error)
140 140
 }
141 141
 
142 142
 // NodeAPIClient defines API client methods for the nodes
... ...
@@ -149,7 +148,7 @@ type NodeAPIClient interface {
149 149
 
150 150
 // PluginAPIClient defines API client methods for the plugins
151 151
 type PluginAPIClient interface {
152
-	PluginList(ctx context.Context, filter filters.Args) (plugin.ListResponse, error)
152
+	PluginList(ctx context.Context, filter Filters) (plugin.ListResponse, error)
153 153
 	PluginRemove(ctx context.Context, name string, options PluginRemoveOptions) error
154 154
 	PluginEnable(ctx context.Context, name string, options PluginEnableOptions) error
155 155
 	PluginDisable(ctx context.Context, name string, options PluginDisableOptions) error
... ...
@@ -201,7 +200,7 @@ type VolumeAPIClient interface {
201 201
 	VolumeInspectWithRaw(ctx context.Context, volumeID string) (volume.Volume, []byte, error)
202 202
 	VolumeList(ctx context.Context, options VolumeListOptions) (volume.ListResponse, error)
203 203
 	VolumeRemove(ctx context.Context, volumeID string, force bool) error
204
-	VolumesPrune(ctx context.Context, pruneFilter filters.Args) (volume.PruneReport, error)
204
+	VolumesPrune(ctx context.Context, pruneFilter Filters) (volume.PruneReport, error)
205 205
 	VolumeUpdate(ctx context.Context, volumeID string, version swarm.Version, options volume.UpdateOptions) error
206 206
 }
207 207
 
... ...
@@ -5,22 +5,13 @@ import (
5 5
 	"encoding/json"
6 6
 	"net/url"
7 7
 
8
-	"github.com/moby/moby/api/types/filters"
9 8
 	"github.com/moby/moby/api/types/swarm"
10 9
 )
11 10
 
12 11
 // ConfigList returns the list of configs.
13 12
 func (cli *Client) ConfigList(ctx context.Context, options ConfigListOptions) ([]swarm.Config, error) {
14 13
 	query := url.Values{}
15
-
16
-	if options.Filters.Len() > 0 {
17
-		filterJSON, err := filters.ToJSON(options.Filters)
18
-		if err != nil {
19
-			return nil, err
20
-		}
21
-
22
-		query.Set("filters", filterJSON)
23
-	}
14
+	options.Filters.updateURLValues(query)
24 15
 
25 16
 	resp, err := cli.get(ctx, "/configs", query, nil)
26 17
 	defer ensureReaderClosed(resp)
... ...
@@ -10,7 +10,6 @@ import (
10 10
 	"testing"
11 11
 
12 12
 	cerrdefs "github.com/containerd/errdefs"
13
-	"github.com/moby/moby/api/types/filters"
14 13
 	"github.com/moby/moby/api/types/swarm"
15 14
 	"gotest.tools/v3/assert"
16 15
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -41,10 +40,9 @@ func TestConfigList(t *testing.T) {
41 41
 		},
42 42
 		{
43 43
 			options: ConfigListOptions{
44
-				Filters: filters.NewArgs(
45
-					filters.Arg("label", "label1"),
46
-					filters.Arg("label", "label2"),
47
-				),
44
+				Filters: make(Filters).
45
+					Add("label", "label1").
46
+					Add("label", "label2"),
48 47
 			},
49 48
 			expectedQueryParams: map[string]string{
50 49
 				"filters": `{"label":{"label1":true,"label2":true}}`,
... ...
@@ -7,7 +7,6 @@ import (
7 7
 	"strconv"
8 8
 
9 9
 	"github.com/moby/moby/api/types/container"
10
-	"github.com/moby/moby/api/types/filters"
11 10
 )
12 11
 
13 12
 // ContainerListOptions holds parameters to list containers with.
... ...
@@ -18,7 +17,7 @@ type ContainerListOptions struct {
18 18
 	Since   string
19 19
 	Before  string
20 20
 	Limit   int
21
-	Filters filters.Args
21
+	Filters Filters
22 22
 }
23 23
 
24 24
 // ContainerList returns the list of containers in the docker host.
... ...
@@ -45,13 +44,7 @@ func (cli *Client) ContainerList(ctx context.Context, options ContainerListOptio
45 45
 		query.Set("size", "1")
46 46
 	}
47 47
 
48
-	if options.Filters.Len() > 0 {
49
-		filterJSON, err := filters.ToJSON(options.Filters)
50
-		if err != nil {
51
-			return nil, err
52
-		}
53
-		query.Set("filters", filterJSON)
54
-	}
48
+	options.Filters.updateURLValues(query)
55 49
 
56 50
 	resp, err := cli.get(ctx, "/containers/json", query, nil)
57 51
 	defer ensureReaderClosed(resp)
... ...
@@ -11,7 +11,6 @@ import (
11 11
 
12 12
 	cerrdefs "github.com/containerd/errdefs"
13 13
 	"github.com/moby/moby/api/types/container"
14
-	"github.com/moby/moby/api/types/filters"
15 14
 	"gotest.tools/v3/assert"
16 15
 	is "gotest.tools/v3/assert/cmp"
17 16
 )
... ...
@@ -86,11 +85,10 @@ func TestContainerList(t *testing.T) {
86 86
 		Size:  true,
87 87
 		All:   true,
88 88
 		Since: "container",
89
-		Filters: filters.NewArgs(
90
-			filters.Arg("label", "label1"),
91
-			filters.Arg("label", "label2"),
92
-			filters.Arg("before", "container"),
93
-		),
89
+		Filters: make(Filters).
90
+			Add("label", "label1").
91
+			Add("label", "label2").
92
+			Add("before", "container"),
94 93
 	})
95 94
 	assert.NilError(t, err)
96 95
 	assert.Check(t, is.Len(containers, 2))
... ...
@@ -4,17 +4,15 @@ import (
4 4
 	"context"
5 5
 	"encoding/json"
6 6
 	"fmt"
7
+	"net/url"
7 8
 
8 9
 	"github.com/moby/moby/api/types/container"
9
-	"github.com/moby/moby/api/types/filters"
10 10
 )
11 11
 
12 12
 // ContainersPrune requests the daemon to delete unused data
13
-func (cli *Client) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error) {
14
-	query, err := getFiltersQuery(pruneFilters)
15
-	if err != nil {
16
-		return container.PruneReport{}, err
17
-	}
13
+func (cli *Client) ContainersPrune(ctx context.Context, pruneFilters Filters) (container.PruneReport, error) {
14
+	query := url.Values{}
15
+	pruneFilters.updateURLValues(query)
18 16
 
19 17
 	resp, err := cli.post(ctx, "/containers/prune", query, nil, nil)
20 18
 	defer ensureReaderClosed(resp)
... ...
@@ -10,7 +10,6 @@ import (
10 10
 
11 11
 	cerrdefs "github.com/containerd/errdefs"
12 12
 	"github.com/moby/moby/api/types/container"
13
-	"github.com/moby/moby/api/types/filters"
14 13
 	"gotest.tools/v3/assert"
15 14
 	is "gotest.tools/v3/assert/cmp"
16 15
 )
... ...
@@ -19,7 +18,7 @@ func TestContainersPruneError(t *testing.T) {
19 19
 	client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
20 20
 	assert.NilError(t, err)
21 21
 
22
-	_, err = client.ContainersPrune(context.Background(), filters.Args{})
22
+	_, err = client.ContainersPrune(context.Background(), Filters{})
23 23
 	assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
24 24
 }
25 25
 
... ...
@@ -27,11 +26,11 @@ func TestContainersPrune(t *testing.T) {
27 27
 	const expectedURL = "/containers/prune"
28 28
 
29 29
 	listCases := []struct {
30
-		filters             filters.Args
30
+		filters             Filters
31 31
 		expectedQueryParams map[string]string
32 32
 	}{
33 33
 		{
34
-			filters: filters.Args{},
34
+			filters: Filters{},
35 35
 			expectedQueryParams: map[string]string{
36 36
 				"until":   "",
37 37
 				"filter":  "",
... ...
@@ -39,7 +38,7 @@ func TestContainersPrune(t *testing.T) {
39 39
 			},
40 40
 		},
41 41
 		{
42
-			filters: filters.NewArgs(filters.Arg("dangling", "true")),
42
+			filters: make(Filters).Add("dangling", "true"),
43 43
 			expectedQueryParams: map[string]string{
44 44
 				"until":   "",
45 45
 				"filter":  "",
... ...
@@ -47,10 +46,9 @@ func TestContainersPrune(t *testing.T) {
47 47
 			},
48 48
 		},
49 49
 		{
50
-			filters: filters.NewArgs(
51
-				filters.Arg("dangling", "true"),
52
-				filters.Arg("until", "2016-12-15T14:00"),
53
-			),
50
+			filters: make(Filters).
51
+				Add("dangling", "true").
52
+				Add("until", "2016-12-15T14:00"),
54 53
 			expectedQueryParams: map[string]string{
55 54
 				"until":   "",
56 55
 				"filter":  "",
... ...
@@ -58,7 +56,7 @@ func TestContainersPrune(t *testing.T) {
58 58
 			},
59 59
 		},
60 60
 		{
61
-			filters: filters.NewArgs(filters.Arg("dangling", "false")),
61
+			filters: make(Filters).Add("dangling", "false"),
62 62
 			expectedQueryParams: map[string]string{
63 63
 				"until":   "",
64 64
 				"filter":  "",
... ...
@@ -66,11 +64,10 @@ func TestContainersPrune(t *testing.T) {
66 66
 			},
67 67
 		},
68 68
 		{
69
-			filters: filters.NewArgs(
70
-				filters.Arg("dangling", "true"),
71
-				filters.Arg("label", "label1=foo"),
72
-				filters.Arg("label", "label2!=bar"),
73
-			),
69
+			filters: make(Filters).
70
+				Add("dangling", "true").
71
+				Add("label", "label1=foo").
72
+				Add("label", "label2!=bar"),
74 73
 			expectedQueryParams: map[string]string{
75 74
 				"until":   "",
76 75
 				"filter":  "",
77 76
new file mode 100644
... ...
@@ -0,0 +1,46 @@
0
+package client
1
+
2
+import (
3
+	"encoding/json"
4
+	"net/url"
5
+)
6
+
7
+// Filters describes a predicate for an API request.
8
+//
9
+// Each entry in the map is a filter term.
10
+// Each term is evaluated against the set of values.
11
+// A filter term is satisfied if any one of the values in the set is a match.
12
+// An item matches the filters when all terms are satisfied.
13
+//
14
+// Like all other map types in Go, the zero value is empty and read-only.
15
+type Filters map[string]map[string]bool
16
+
17
+// Add appends values to the value-set of term.
18
+//
19
+// The receiver f is returned for chaining.
20
+//
21
+//	f := make(Filters).Add("name", "foo", "bar").Add("status", "exited")
22
+func (f Filters) Add(term string, values ...string) Filters {
23
+	if _, ok := f[term]; !ok {
24
+		f[term] = make(map[string]bool)
25
+	}
26
+	for _, v := range values {
27
+		f[term][v] = true
28
+	}
29
+	return f
30
+}
31
+
32
+// updateURLValues sets the "filters" key in values to the marshalled value of
33
+// f, replacing any existing values. When f is empty, any existing "filters" key
34
+// is removed.
35
+func (f Filters) updateURLValues(values url.Values) {
36
+	if len(f) > 0 {
37
+		b, err := json.Marshal(f)
38
+		if err != nil {
39
+			panic(err) // Marshaling builtin types should never fail
40
+		}
41
+		values.Set("filters", string(b))
42
+	} else {
43
+		values.Del("filters")
44
+	}
45
+}
0 46
new file mode 100644
... ...
@@ -0,0 +1,48 @@
0
+package client
1
+
2
+import (
3
+	"net/url"
4
+	"testing"
5
+
6
+	"gotest.tools/v3/assert"
7
+	is "gotest.tools/v3/assert/cmp"
8
+)
9
+
10
+func TestFilters(t *testing.T) {
11
+	f := make(Filters).Add("foo", "bar", "baz", "bar").
12
+		Add("quux", "xyzzy").
13
+		Add("quux", "plugh")
14
+	f["lol"] = map[string]bool{"abc": true}
15
+	f.Add("lol", "def")
16
+	assert.Check(t, is.DeepEqual(f, Filters{
17
+		"foo":  {"bar": true, "baz": true},
18
+		"quux": {"xyzzy": true, "plugh": true},
19
+		"lol":  {"abc": true, "def": true},
20
+	}))
21
+}
22
+
23
+func TestFilters_UpdateURLValues(t *testing.T) {
24
+	v := url.Values{}
25
+	Filters(nil).updateURLValues(v)
26
+	assert.Check(t, is.DeepEqual(v, url.Values{}))
27
+
28
+	v = url.Values{"filters": []string{"bogus"}}
29
+	Filters(nil).updateURLValues(v)
30
+	assert.Check(t, is.DeepEqual(v, url.Values{}))
31
+
32
+	v = url.Values{}
33
+	Filters{}.updateURLValues(v)
34
+	assert.Check(t, is.DeepEqual(v, url.Values{}))
35
+
36
+	v = url.Values{"filters": []string{"bogus"}}
37
+	Filters{}.updateURLValues(v)
38
+	assert.Check(t, is.DeepEqual(v, url.Values{}))
39
+
40
+	v = url.Values{}
41
+	Filters{"foo": map[string]bool{"bar": true}}.updateURLValues(v)
42
+	assert.Check(t, is.DeepEqual(v, url.Values{"filters": []string{`{"foo":{"bar":true}}`}}))
43
+
44
+	v = url.Values{"filters": []string{"bogus"}}
45
+	Filters{"foo": map[string]bool{"bar": true}}.updateURLValues(v)
46
+	assert.Check(t, is.DeepEqual(v, url.Values{"filters": []string{`{"foo":{"bar":true}}`}}))
47
+}
... ...
@@ -5,7 +5,6 @@ import (
5 5
 	"encoding/json"
6 6
 	"net/url"
7 7
 
8
-	"github.com/moby/moby/api/types/filters"
9 8
 	"github.com/moby/moby/api/types/image"
10 9
 	"github.com/moby/moby/api/types/versions"
11 10
 )
... ...
@@ -30,13 +29,7 @@ func (cli *Client) ImageList(ctx context.Context, options ImageListOptions) ([]i
30 30
 
31 31
 	query := url.Values{}
32 32
 
33
-	if options.Filters.Len() > 0 {
34
-		filterJSON, err := filters.ToJSON(options.Filters)
35
-		if err != nil {
36
-			return images, err
37
-		}
38
-		query.Set("filters", filterJSON)
39
-	}
33
+	options.Filters.updateURLValues(query)
40 34
 	if options.All {
41 35
 		query.Set("all", "1")
42 36
 	}
... ...
@@ -1,7 +1,5 @@
1 1
 package client
2 2
 
3
-import "github.com/moby/moby/api/types/filters"
4
-
5 3
 // ImageListOptions holds parameters to list images with.
6 4
 type ImageListOptions struct {
7 5
 	// All controls whether all images in the graph are filtered, or just
... ...
@@ -9,7 +7,7 @@ type ImageListOptions struct {
9 9
 	All bool
10 10
 
11 11
 	// Filters is a JSON-encoded set of filter arguments.
12
-	Filters filters.Args
12
+	Filters Filters
13 13
 
14 14
 	// SharedSize indicates whether the shared size of images should be computed.
15 15
 	SharedSize bool
... ...
@@ -12,7 +12,6 @@ import (
12 12
 	"testing"
13 13
 
14 14
 	cerrdefs "github.com/containerd/errdefs"
15
-	"github.com/moby/moby/api/types/filters"
16 15
 	"github.com/moby/moby/api/types/image"
17 16
 	"gotest.tools/v3/assert"
18 17
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -55,11 +54,10 @@ func TestImageList(t *testing.T) {
55 55
 		},
56 56
 		{
57 57
 			options: ImageListOptions{
58
-				Filters: filters.NewArgs(
59
-					filters.Arg("label", "label1"),
60
-					filters.Arg("label", "label2"),
61
-					filters.Arg("dangling", "true"),
62
-				),
58
+				Filters: make(Filters).
59
+					Add("label", "label1").
60
+					Add("label", "label2").
61
+					Add("dangling", "true"),
63 62
 			},
64 63
 			expectedQueryParams: map[string]string{
65 64
 				"all":     "",
... ...
@@ -69,7 +67,7 @@ func TestImageList(t *testing.T) {
69 69
 		},
70 70
 		{
71 71
 			options: ImageListOptions{
72
-				Filters: filters.NewArgs(filters.Arg("dangling", "false")),
72
+				Filters: make(Filters).Add("dangling", "false"),
73 73
 			},
74 74
 			expectedQueryParams: map[string]string{
75 75
 				"all":     "",
... ...
@@ -4,17 +4,15 @@ import (
4 4
 	"context"
5 5
 	"encoding/json"
6 6
 	"fmt"
7
+	"net/url"
7 8
 
8
-	"github.com/moby/moby/api/types/filters"
9 9
 	"github.com/moby/moby/api/types/image"
10 10
 )
11 11
 
12 12
 // ImagesPrune requests the daemon to delete unused data
13
-func (cli *Client) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (image.PruneReport, error) {
14
-	query, err := getFiltersQuery(pruneFilters)
15
-	if err != nil {
16
-		return image.PruneReport{}, err
17
-	}
13
+func (cli *Client) ImagesPrune(ctx context.Context, pruneFilters Filters) (image.PruneReport, error) {
14
+	query := url.Values{}
15
+	pruneFilters.updateURLValues(query)
18 16
 
19 17
 	resp, err := cli.post(ctx, "/images/prune", query, nil, nil)
20 18
 	defer ensureReaderClosed(resp)
... ...
@@ -11,7 +11,6 @@ import (
11 11
 	cerrdefs "github.com/containerd/errdefs"
12 12
 	"github.com/moby/moby/api/types/image"
13 13
 
14
-	"github.com/moby/moby/api/types/filters"
15 14
 	"gotest.tools/v3/assert"
16 15
 	is "gotest.tools/v3/assert/cmp"
17 16
 )
... ...
@@ -20,7 +19,7 @@ func TestImagesPruneError(t *testing.T) {
20 20
 	client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
21 21
 	assert.NilError(t, err)
22 22
 
23
-	_, err = client.ImagesPrune(context.Background(), filters.NewArgs())
23
+	_, err = client.ImagesPrune(context.Background(), nil)
24 24
 	assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
25 25
 }
26 26
 
... ...
@@ -28,11 +27,11 @@ func TestImagesPrune(t *testing.T) {
28 28
 	const expectedURL = "/images/prune"
29 29
 
30 30
 	listCases := []struct {
31
-		filters             filters.Args
31
+		filters             Filters
32 32
 		expectedQueryParams map[string]string
33 33
 	}{
34 34
 		{
35
-			filters: filters.Args{},
35
+			filters: Filters{},
36 36
 			expectedQueryParams: map[string]string{
37 37
 				"until":   "",
38 38
 				"filter":  "",
... ...
@@ -40,7 +39,7 @@ func TestImagesPrune(t *testing.T) {
40 40
 			},
41 41
 		},
42 42
 		{
43
-			filters: filters.NewArgs(filters.Arg("dangling", "true")),
43
+			filters: make(Filters).Add("dangling", "true"),
44 44
 			expectedQueryParams: map[string]string{
45 45
 				"until":   "",
46 46
 				"filter":  "",
... ...
@@ -48,7 +47,7 @@ func TestImagesPrune(t *testing.T) {
48 48
 			},
49 49
 		},
50 50
 		{
51
-			filters: filters.NewArgs(filters.Arg("dangling", "false")),
51
+			filters: make(Filters).Add("dangling", "false"),
52 52
 			expectedQueryParams: map[string]string{
53 53
 				"until":   "",
54 54
 				"filter":  "",
... ...
@@ -56,11 +55,9 @@ func TestImagesPrune(t *testing.T) {
56 56
 			},
57 57
 		},
58 58
 		{
59
-			filters: filters.NewArgs(
60
-				filters.Arg("dangling", "true"),
61
-				filters.Arg("label", "label1=foo"),
62
-				filters.Arg("label", "label2!=bar"),
63
-			),
59
+			filters: make(Filters).
60
+				Add("dangling", "true").
61
+				Add("label", "label1=foo", "label2!=bar"),
64 62
 			expectedQueryParams: map[string]string{
65 63
 				"until":   "",
66 64
 				"filter":  "",
... ...
@@ -8,7 +8,6 @@ import (
8 8
 	"strconv"
9 9
 
10 10
 	cerrdefs "github.com/containerd/errdefs"
11
-	"github.com/moby/moby/api/types/filters"
12 11
 	"github.com/moby/moby/api/types/registry"
13 12
 )
14 13
 
... ...
@@ -22,13 +21,7 @@ func (cli *Client) ImageSearch(ctx context.Context, term string, options ImageSe
22 22
 		query.Set("limit", strconv.Itoa(options.Limit))
23 23
 	}
24 24
 
25
-	if options.Filters.Len() > 0 {
26
-		filterJSON, err := filters.ToJSON(options.Filters)
27
-		if err != nil {
28
-			return results, err
29
-		}
30
-		query.Set("filters", filterJSON)
31
-	}
25
+	options.Filters.updateURLValues(query)
32 26
 
33 27
 	resp, err := cli.tryImageSearch(ctx, query, options.RegistryAuth)
34 28
 	defer ensureReaderClosed(resp)
... ...
@@ -2,8 +2,6 @@ package client
2 2
 
3 3
 import (
4 4
 	"context"
5
-
6
-	"github.com/moby/moby/api/types/filters"
7 5
 )
8 6
 
9 7
 // ImageSearchOptions holds parameters to search images with.
... ...
@@ -17,6 +15,6 @@ type ImageSearchOptions struct {
17 17
 	//
18 18
 	// For details, refer to [github.com/moby/moby/api/types/registry.RequestAuthConfig].
19 19
 	PrivilegeFunc func(context.Context) (string, error)
20
-	Filters       filters.Args
20
+	Filters       Filters
21 21
 	Limit         int
22 22
 }
... ...
@@ -11,7 +11,6 @@ import (
11 11
 	"testing"
12 12
 
13 13
 	cerrdefs "github.com/containerd/errdefs"
14
-	"github.com/moby/moby/api/types/filters"
15 14
 	"github.com/moby/moby/api/types/registry"
16 15
 	"gotest.tools/v3/assert"
17 16
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -133,10 +132,7 @@ func TestImageSearchWithoutErrors(t *testing.T) {
133 133
 	}))
134 134
 	assert.NilError(t, err)
135 135
 	results, err := client.ImageSearch(context.Background(), "some-image", ImageSearchOptions{
136
-		Filters: filters.NewArgs(
137
-			filters.Arg("is-automated", "true"),
138
-			filters.Arg("stars", "3"),
139
-		),
136
+		Filters: make(Filters).Add("is-automated", "true").Add("stars", "3"),
140 137
 	})
141 138
 	assert.NilError(t, err)
142 139
 	assert.Check(t, is.Len(results, 1))
... ...
@@ -5,20 +5,13 @@ import (
5 5
 	"encoding/json"
6 6
 	"net/url"
7 7
 
8
-	"github.com/moby/moby/api/types/filters"
9 8
 	"github.com/moby/moby/api/types/network"
10 9
 )
11 10
 
12 11
 // NetworkList returns the list of networks configured in the docker host.
13 12
 func (cli *Client) NetworkList(ctx context.Context, options NetworkListOptions) ([]network.Summary, error) {
14 13
 	query := url.Values{}
15
-	if options.Filters.Len() > 0 {
16
-		filterJSON, err := filters.ToJSON(options.Filters)
17
-		if err != nil {
18
-			return nil, err
19
-		}
20
-		query.Set("filters", filterJSON)
21
-	}
14
+	options.Filters.updateURLValues(query)
22 15
 	var networkResources []network.Summary
23 16
 	resp, err := cli.get(ctx, "/networks", query, nil)
24 17
 	defer ensureReaderClosed(resp)
... ...
@@ -1,8 +1,6 @@
1 1
 package client
2 2
 
3
-import "github.com/moby/moby/api/types/filters"
4
-
5 3
 // NetworkListOptions holds parameters to filter the list of networks with.
6 4
 type NetworkListOptions struct {
7
-	Filters filters.Args
5
+	Filters Filters
8 6
 }
... ...
@@ -10,7 +10,6 @@ import (
10 10
 	"testing"
11 11
 
12 12
 	cerrdefs "github.com/containerd/errdefs"
13
-	"github.com/moby/moby/api/types/filters"
14 13
 	"github.com/moby/moby/api/types/network"
15 14
 	"gotest.tools/v3/assert"
16 15
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -37,22 +36,21 @@ func TestNetworkList(t *testing.T) {
37 37
 		},
38 38
 		{
39 39
 			options: NetworkListOptions{
40
-				Filters: filters.NewArgs(filters.Arg("dangling", "false")),
40
+				Filters: make(Filters).Add("dangling", "false"),
41 41
 			},
42 42
 			expectedFilters: `{"dangling":{"false":true}}`,
43 43
 		},
44 44
 		{
45 45
 			options: NetworkListOptions{
46
-				Filters: filters.NewArgs(filters.Arg("dangling", "true")),
46
+				Filters: make(Filters).Add("dangling", "true"),
47 47
 			},
48 48
 			expectedFilters: `{"dangling":{"true":true}}`,
49 49
 		},
50 50
 		{
51 51
 			options: NetworkListOptions{
52
-				Filters: filters.NewArgs(
53
-					filters.Arg("label", "label1"),
54
-					filters.Arg("label", "label2"),
55
-				),
52
+				Filters: make(Filters).
53
+					Add("label", "label1").
54
+					Add("label", "label2"),
56 55
 			},
57 56
 			expectedFilters: `{"label":{"label1":true,"label2":true}}`,
58 57
 		},
... ...
@@ -4,17 +4,15 @@ import (
4 4
 	"context"
5 5
 	"encoding/json"
6 6
 	"fmt"
7
+	"net/url"
7 8
 
8
-	"github.com/moby/moby/api/types/filters"
9 9
 	"github.com/moby/moby/api/types/network"
10 10
 )
11 11
 
12 12
 // NetworksPrune requests the daemon to delete unused networks
13
-func (cli *Client) NetworksPrune(ctx context.Context, pruneFilters filters.Args) (network.PruneReport, error) {
14
-	query, err := getFiltersQuery(pruneFilters)
15
-	if err != nil {
16
-		return network.PruneReport{}, err
17
-	}
13
+func (cli *Client) NetworksPrune(ctx context.Context, pruneFilters Filters) (network.PruneReport, error) {
14
+	query := url.Values{}
15
+	pruneFilters.updateURLValues(query)
18 16
 
19 17
 	resp, err := cli.post(ctx, "/networks/prune", query, nil, nil)
20 18
 	defer ensureReaderClosed(resp)
... ...
@@ -9,7 +9,6 @@ import (
9 9
 	"testing"
10 10
 
11 11
 	cerrdefs "github.com/containerd/errdefs"
12
-	"github.com/moby/moby/api/types/filters"
13 12
 	"github.com/moby/moby/api/types/network"
14 13
 	"gotest.tools/v3/assert"
15 14
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -21,7 +20,7 @@ func TestNetworksPruneError(t *testing.T) {
21 21
 	)
22 22
 	assert.NilError(t, err)
23 23
 
24
-	_, err = client.NetworksPrune(context.Background(), filters.NewArgs())
24
+	_, err = client.NetworksPrune(context.Background(), nil)
25 25
 	assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
26 26
 }
27 27
 
... ...
@@ -29,11 +28,11 @@ func TestNetworksPrune(t *testing.T) {
29 29
 	const expectedURL = "/networks/prune"
30 30
 
31 31
 	listCases := []struct {
32
-		filters             filters.Args
32
+		filters             Filters
33 33
 		expectedQueryParams map[string]string
34 34
 	}{
35 35
 		{
36
-			filters: filters.Args{},
36
+			filters: Filters{},
37 37
 			expectedQueryParams: map[string]string{
38 38
 				"until":   "",
39 39
 				"filter":  "",
... ...
@@ -41,7 +40,7 @@ func TestNetworksPrune(t *testing.T) {
41 41
 			},
42 42
 		},
43 43
 		{
44
-			filters: filters.NewArgs(filters.Arg("dangling", "true")),
44
+			filters: make(Filters).Add("dangling", "true"),
45 45
 			expectedQueryParams: map[string]string{
46 46
 				"until":   "",
47 47
 				"filter":  "",
... ...
@@ -49,7 +48,7 @@ func TestNetworksPrune(t *testing.T) {
49 49
 			},
50 50
 		},
51 51
 		{
52
-			filters: filters.NewArgs(filters.Arg("dangling", "false")),
52
+			filters: make(Filters).Add("dangling", "false"),
53 53
 			expectedQueryParams: map[string]string{
54 54
 				"until":   "",
55 55
 				"filter":  "",
... ...
@@ -57,11 +56,10 @@ func TestNetworksPrune(t *testing.T) {
57 57
 			},
58 58
 		},
59 59
 		{
60
-			filters: filters.NewArgs(
61
-				filters.Arg("dangling", "true"),
62
-				filters.Arg("label", "label1=foo"),
63
-				filters.Arg("label", "label2!=bar"),
64
-			),
60
+			filters: make(Filters).
61
+				Add("dangling", "true").
62
+				Add("label", "label1=foo").
63
+				Add("label", "label2!=bar"),
65 64
 			expectedQueryParams: map[string]string{
66 65
 				"until":   "",
67 66
 				"filter":  "",
... ...
@@ -5,23 +5,13 @@ import (
5 5
 	"encoding/json"
6 6
 	"net/url"
7 7
 
8
-	"github.com/moby/moby/api/types/filters"
9 8
 	"github.com/moby/moby/api/types/swarm"
10 9
 )
11 10
 
12 11
 // NodeList returns the list of nodes.
13 12
 func (cli *Client) NodeList(ctx context.Context, options NodeListOptions) ([]swarm.Node, error) {
14 13
 	query := url.Values{}
15
-
16
-	if options.Filters.Len() > 0 {
17
-		filterJSON, err := filters.ToJSON(options.Filters)
18
-		if err != nil {
19
-			return nil, err
20
-		}
21
-
22
-		query.Set("filters", filterJSON)
23
-	}
24
-
14
+	options.Filters.updateURLValues(query)
25 15
 	resp, err := cli.get(ctx, "/nodes", query, nil)
26 16
 	defer ensureReaderClosed(resp)
27 17
 	if err != nil {
... ...
@@ -10,7 +10,6 @@ import (
10 10
 	"testing"
11 11
 
12 12
 	cerrdefs "github.com/containerd/errdefs"
13
-	"github.com/moby/moby/api/types/filters"
14 13
 	"github.com/moby/moby/api/types/swarm"
15 14
 	"gotest.tools/v3/assert"
16 15
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -39,10 +38,8 @@ func TestNodeList(t *testing.T) {
39 39
 		},
40 40
 		{
41 41
 			options: NodeListOptions{
42
-				Filters: filters.NewArgs(
43
-					filters.Arg("label", "label1"),
44
-					filters.Arg("label", "label2"),
45
-				),
42
+				Filters: make(Filters).
43
+					Add("label", "label1", "label2"),
46 44
 			},
47 45
 			expectedQueryParams: map[string]string{
48 46
 				"filters": `{"label":{"label1":true,"label2":true}}`,
... ...
@@ -5,22 +5,15 @@ import (
5 5
 	"encoding/json"
6 6
 	"net/url"
7 7
 
8
-	"github.com/moby/moby/api/types/filters"
9 8
 	"github.com/moby/moby/api/types/plugin"
10 9
 )
11 10
 
12 11
 // PluginList returns the installed plugins
13
-func (cli *Client) PluginList(ctx context.Context, filter filters.Args) (plugin.ListResponse, error) {
12
+func (cli *Client) PluginList(ctx context.Context, filter Filters) (plugin.ListResponse, error) {
14 13
 	var plugins plugin.ListResponse
15 14
 	query := url.Values{}
16 15
 
17
-	if filter.Len() > 0 {
18
-		filterJSON, err := filters.ToJSON(filter)
19
-		if err != nil {
20
-			return plugins, err
21
-		}
22
-		query.Set("filters", filterJSON)
23
-	}
16
+	filter.updateURLValues(query)
24 17
 	resp, err := cli.get(ctx, "/plugins", query, nil)
25 18
 	defer ensureReaderClosed(resp)
26 19
 	if err != nil {
... ...
@@ -10,7 +10,6 @@ import (
10 10
 	"testing"
11 11
 
12 12
 	cerrdefs "github.com/containerd/errdefs"
13
-	"github.com/moby/moby/api/types/filters"
14 13
 	"github.com/moby/moby/api/types/plugin"
15 14
 	"gotest.tools/v3/assert"
16 15
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -20,7 +19,7 @@ func TestPluginListError(t *testing.T) {
20 20
 	client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
21 21
 	assert.NilError(t, err)
22 22
 
23
-	_, err = client.PluginList(context.Background(), filters.NewArgs())
23
+	_, err = client.PluginList(context.Background(), nil)
24 24
 	assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
25 25
 }
26 26
 
... ...
@@ -28,11 +27,10 @@ func TestPluginList(t *testing.T) {
28 28
 	const expectedURL = "/plugins"
29 29
 
30 30
 	listCases := []struct {
31
-		filters             filters.Args
31
+		filters             Filters
32 32
 		expectedQueryParams map[string]string
33 33
 	}{
34 34
 		{
35
-			filters: filters.NewArgs(),
36 35
 			expectedQueryParams: map[string]string{
37 36
 				"all":     "",
38 37
 				"filter":  "",
... ...
@@ -40,7 +38,7 @@ func TestPluginList(t *testing.T) {
40 40
 			},
41 41
 		},
42 42
 		{
43
-			filters: filters.NewArgs(filters.Arg("enabled", "true")),
43
+			filters: make(Filters).Add("enabled", "true"),
44 44
 			expectedQueryParams: map[string]string{
45 45
 				"all":     "",
46 46
 				"filter":  "",
... ...
@@ -48,10 +46,7 @@ func TestPluginList(t *testing.T) {
48 48
 			},
49 49
 		},
50 50
 		{
51
-			filters: filters.NewArgs(
52
-				filters.Arg("capability", "volumedriver"),
53
-				filters.Arg("capability", "authz"),
54
-			),
51
+			filters: make(Filters).Add("capability", "volumedriver", "authz"),
55 52
 			expectedQueryParams: map[string]string{
56 53
 				"all":     "",
57 54
 				"filter":  "",
... ...
@@ -5,7 +5,6 @@ import (
5 5
 	"encoding/json"
6 6
 	"net/url"
7 7
 
8
-	"github.com/moby/moby/api/types/filters"
9 8
 	"github.com/moby/moby/api/types/swarm"
10 9
 )
11 10
 
... ...
@@ -13,15 +12,7 @@ import (
13 13
 func (cli *Client) SecretList(ctx context.Context, options SecretListOptions) ([]swarm.Secret, error) {
14 14
 	query := url.Values{}
15 15
 
16
-	if options.Filters.Len() > 0 {
17
-		filterJSON, err := filters.ToJSON(options.Filters)
18
-		if err != nil {
19
-			return nil, err
20
-		}
21
-
22
-		query.Set("filters", filterJSON)
23
-	}
24
-
16
+	options.Filters.updateURLValues(query)
25 17
 	resp, err := cli.get(ctx, "/secrets", query, nil)
26 18
 	defer ensureReaderClosed(resp)
27 19
 	if err != nil {
... ...
@@ -1,8 +1,6 @@
1 1
 package client
2 2
 
3
-import "github.com/moby/moby/api/types/filters"
4
-
5 3
 // SecretListOptions holds parameters to list secrets
6 4
 type SecretListOptions struct {
7
-	Filters filters.Args
5
+	Filters Filters
8 6
 }
... ...
@@ -10,7 +10,6 @@ import (
10 10
 	"testing"
11 11
 
12 12
 	cerrdefs "github.com/containerd/errdefs"
13
-	"github.com/moby/moby/api/types/filters"
14 13
 	"github.com/moby/moby/api/types/swarm"
15 14
 	"gotest.tools/v3/assert"
16 15
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -39,10 +38,7 @@ func TestSecretList(t *testing.T) {
39 39
 		},
40 40
 		{
41 41
 			options: SecretListOptions{
42
-				Filters: filters.NewArgs(
43
-					filters.Arg("label", "label1"),
44
-					filters.Arg("label", "label2"),
45
-				),
42
+				Filters: make(Filters).Add("label", "label1", "label2"),
46 43
 			},
47 44
 			expectedQueryParams: map[string]string{
48 45
 				"filters": `{"label":{"label1":true,"label2":true}}`,
... ...
@@ -5,7 +5,6 @@ import (
5 5
 	"encoding/json"
6 6
 	"net/url"
7 7
 
8
-	"github.com/moby/moby/api/types/filters"
9 8
 	"github.com/moby/moby/api/types/swarm"
10 9
 )
11 10
 
... ...
@@ -13,14 +12,7 @@ import (
13 13
 func (cli *Client) ServiceList(ctx context.Context, options ServiceListOptions) ([]swarm.Service, error) {
14 14
 	query := url.Values{}
15 15
 
16
-	if options.Filters.Len() > 0 {
17
-		filterJSON, err := filters.ToJSON(options.Filters)
18
-		if err != nil {
19
-			return nil, err
20
-		}
21
-
22
-		query.Set("filters", filterJSON)
23
-	}
16
+	options.Filters.updateURLValues(query)
24 17
 
25 18
 	if options.Status {
26 19
 		query.Set("status", "true")
... ...
@@ -10,7 +10,6 @@ import (
10 10
 	"testing"
11 11
 
12 12
 	cerrdefs "github.com/containerd/errdefs"
13
-	"github.com/moby/moby/api/types/filters"
14 13
 	"github.com/moby/moby/api/types/swarm"
15 14
 	"gotest.tools/v3/assert"
16 15
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -39,10 +38,7 @@ func TestServiceList(t *testing.T) {
39 39
 		},
40 40
 		{
41 41
 			options: ServiceListOptions{
42
-				Filters: filters.NewArgs(
43
-					filters.Arg("label", "label1"),
44
-					filters.Arg("label", "label2"),
45
-				),
42
+				Filters: make(Filters).Add("label", "label1", "label2"),
46 43
 			},
47 44
 			expectedQueryParams: map[string]string{
48 45
 				"filters": `{"label":{"label1":true,"label2":true}}`,
... ...
@@ -1,8 +1,6 @@
1 1
 package client
2 2
 
3
-import "github.com/moby/moby/api/types/filters"
4
-
5 3
 // ConfigListOptions holds parameters to list configs
6 4
 type ConfigListOptions struct {
7
-	Filters filters.Args
5
+	Filters Filters
8 6
 }
... ...
@@ -1,8 +1,6 @@
1 1
 package client
2 2
 
3
-import "github.com/moby/moby/api/types/filters"
4
-
5 3
 // NodeListOptions holds parameters to list nodes with.
6 4
 type NodeListOptions struct {
7
-	Filters filters.Args
5
+	Filters Filters
8 6
 }
... ...
@@ -1,10 +1,8 @@
1 1
 package client
2 2
 
3
-import "github.com/moby/moby/api/types/filters"
4
-
5 3
 // ServiceListOptions holds parameters to list services with.
6 4
 type ServiceListOptions struct {
7
-	Filters filters.Args
5
+	Filters Filters
8 6
 
9 7
 	// Status indicates whether the server should include the service task
10 8
 	// count of running and desired tasks.
... ...
@@ -1,8 +1,6 @@
1 1
 package client
2 2
 
3
-import "github.com/moby/moby/api/types/filters"
4
-
5 3
 // TaskListOptions holds parameters to list tasks with.
6 4
 type TaskListOptions struct {
7
-	Filters filters.Args
5
+	Filters Filters
8 6
 }
... ...
@@ -8,7 +8,6 @@ import (
8 8
 
9 9
 	"github.com/moby/moby/api/types"
10 10
 	"github.com/moby/moby/api/types/events"
11
-	"github.com/moby/moby/api/types/filters"
12 11
 	"github.com/moby/moby/client/internal"
13 12
 	"github.com/moby/moby/client/internal/timestamp"
14 13
 )
... ...
@@ -17,7 +16,7 @@ import (
17 17
 type EventsListOptions struct {
18 18
 	Since   string
19 19
 	Until   string
20
-	Filters filters.Args
20
+	Filters Filters
21 21
 }
22 22
 
23 23
 // Events returns a stream of events in the daemon. It's up to the caller to close the stream
... ...
@@ -100,13 +99,7 @@ func buildEventsQueryParams(options EventsListOptions) (url.Values, error) {
100 100
 		query.Set("until", ts)
101 101
 	}
102 102
 
103
-	if options.Filters.Len() > 0 {
104
-		filterJSON, err := filters.ToJSON(options.Filters)
105
-		if err != nil {
106
-			return nil, err
107
-		}
108
-		query.Set("filters", filterJSON)
109
-	}
103
+	options.Filters.updateURLValues(query)
110 104
 
111 105
 	return query, nil
112 106
 }
... ...
@@ -12,7 +12,6 @@ import (
12 12
 
13 13
 	cerrdefs "github.com/containerd/errdefs"
14 14
 	"github.com/moby/moby/api/types/events"
15
-	"github.com/moby/moby/api/types/filters"
16 15
 	"gotest.tools/v3/assert"
17 16
 	is "gotest.tools/v3/assert/cmp"
18 17
 )
... ...
@@ -55,7 +54,7 @@ func TestEventsErrorFromServer(t *testing.T) {
55 55
 func TestEvents(t *testing.T) {
56 56
 	const expectedURL = "/events"
57 57
 
58
-	fltrs := filters.NewArgs(filters.Arg("type", string(events.ContainerEventType)))
58
+	fltrs := make(Filters).Add("type", string(events.ContainerEventType))
59 59
 	expectedFiltersJSON := fmt.Sprintf(`{"type":{%q:true}}`, events.ContainerEventType)
60 60
 
61 61
 	eventsCases := []struct {
... ...
@@ -5,7 +5,6 @@ import (
5 5
 	"encoding/json"
6 6
 	"net/url"
7 7
 
8
-	"github.com/moby/moby/api/types/filters"
9 8
 	"github.com/moby/moby/api/types/swarm"
10 9
 )
11 10
 
... ...
@@ -13,14 +12,7 @@ import (
13 13
 func (cli *Client) TaskList(ctx context.Context, options TaskListOptions) ([]swarm.Task, error) {
14 14
 	query := url.Values{}
15 15
 
16
-	if options.Filters.Len() > 0 {
17
-		filterJSON, err := filters.ToJSON(options.Filters)
18
-		if err != nil {
19
-			return nil, err
20
-		}
21
-
22
-		query.Set("filters", filterJSON)
23
-	}
16
+	options.Filters.updateURLValues(query)
24 17
 
25 18
 	resp, err := cli.get(ctx, "/tasks", query, nil)
26 19
 	defer ensureReaderClosed(resp)
... ...
@@ -10,7 +10,6 @@ import (
10 10
 	"testing"
11 11
 
12 12
 	cerrdefs "github.com/containerd/errdefs"
13
-	"github.com/moby/moby/api/types/filters"
14 13
 	"github.com/moby/moby/api/types/swarm"
15 14
 	"gotest.tools/v3/assert"
16 15
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -39,10 +38,7 @@ func TestTaskList(t *testing.T) {
39 39
 		},
40 40
 		{
41 41
 			options: TaskListOptions{
42
-				Filters: filters.NewArgs(
43
-					filters.Arg("label", "label1"),
44
-					filters.Arg("label", "label2"),
45
-				),
42
+				Filters: make(Filters).Add("label", "label1", "label2"),
46 43
 			},
47 44
 			expectedQueryParams: map[string]string{
48 45
 				"filters": `{"label":{"label1":true,"label2":true}}`,
... ...
@@ -3,11 +3,9 @@ package client
3 3
 import (
4 4
 	"encoding/json"
5 5
 	"fmt"
6
-	"net/url"
7 6
 	"strings"
8 7
 
9 8
 	cerrdefs "github.com/containerd/errdefs"
10
-	"github.com/moby/moby/api/types/filters"
11 9
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
12 10
 )
13 11
 
... ...
@@ -28,20 +26,6 @@ func trimID(objType, id string) (string, error) {
28 28
 	return id, nil
29 29
 }
30 30
 
31
-// getFiltersQuery returns a url query with "filters" query term, based on the
32
-// filters provided.
33
-func getFiltersQuery(f filters.Args) (url.Values, error) {
34
-	query := url.Values{}
35
-	if f.Len() > 0 {
36
-		filterJSON, err := filters.ToJSON(f)
37
-		if err != nil {
38
-			return query, err
39
-		}
40
-		query.Set("filters", filterJSON)
41
-	}
42
-	return query, nil
43
-}
44
-
45 31
 // encodePlatforms marshals the given platform(s) to JSON format, to
46 32
 // be used for query-parameters for filtering / selecting platforms.
47 33
 func encodePlatforms(platform ...ocispec.Platform) ([]string, error) {
... ...
@@ -5,7 +5,6 @@ import (
5 5
 	"encoding/json"
6 6
 	"net/url"
7 7
 
8
-	"github.com/moby/moby/api/types/filters"
9 8
 	"github.com/moby/moby/api/types/volume"
10 9
 )
11 10
 
... ...
@@ -13,13 +12,7 @@ import (
13 13
 func (cli *Client) VolumeList(ctx context.Context, options VolumeListOptions) (volume.ListResponse, error) {
14 14
 	query := url.Values{}
15 15
 
16
-	if options.Filters.Len() > 0 {
17
-		filterJSON, err := filters.ToJSON(options.Filters)
18
-		if err != nil {
19
-			return volume.ListResponse{}, err
20
-		}
21
-		query.Set("filters", filterJSON)
22
-	}
16
+	options.Filters.updateURLValues(query)
23 17
 	resp, err := cli.get(ctx, "/volumes", query, nil)
24 18
 	defer ensureReaderClosed(resp)
25 19
 	if err != nil {
... ...
@@ -1,8 +1,6 @@
1 1
 package client
2 2
 
3
-import "github.com/moby/moby/api/types/filters"
4
-
5 3
 // VolumeListOptions holds parameters to list volumes.
6 4
 type VolumeListOptions struct {
7
-	Filters filters.Args
5
+	Filters Filters
8 6
 }
... ...
@@ -10,7 +10,6 @@ import (
10 10
 	"testing"
11 11
 
12 12
 	cerrdefs "github.com/containerd/errdefs"
13
-	"github.com/moby/moby/api/types/filters"
14 13
 	"github.com/moby/moby/api/types/volume"
15 14
 	"gotest.tools/v3/assert"
16 15
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -28,23 +27,19 @@ func TestVolumeList(t *testing.T) {
28 28
 	const expectedURL = "/volumes"
29 29
 
30 30
 	listCases := []struct {
31
-		filters         filters.Args
31
+		filters         Filters
32 32
 		expectedFilters string
33 33
 	}{
34 34
 		{
35
-			filters:         filters.NewArgs(),
36 35
 			expectedFilters: "",
37 36
 		}, {
38
-			filters:         filters.NewArgs(filters.Arg("dangling", "false")),
37
+			filters:         make(Filters).Add("dangling", "false"),
39 38
 			expectedFilters: `{"dangling":{"false":true}}`,
40 39
 		}, {
41
-			filters:         filters.NewArgs(filters.Arg("dangling", "true")),
40
+			filters:         make(Filters).Add("dangling", "true"),
42 41
 			expectedFilters: `{"dangling":{"true":true}}`,
43 42
 		}, {
44
-			filters: filters.NewArgs(
45
-				filters.Arg("label", "label1"),
46
-				filters.Arg("label", "label2"),
47
-			),
43
+			filters:         make(Filters).Add("label", "label1", "label2"),
48 44
 			expectedFilters: `{"label":{"label1":true,"label2":true}}`,
49 45
 		},
50 46
 	}
... ...
@@ -4,17 +4,15 @@ import (
4 4
 	"context"
5 5
 	"encoding/json"
6 6
 	"fmt"
7
+	"net/url"
7 8
 
8
-	"github.com/moby/moby/api/types/filters"
9 9
 	"github.com/moby/moby/api/types/volume"
10 10
 )
11 11
 
12 12
 // VolumesPrune requests the daemon to delete unused data
13
-func (cli *Client) VolumesPrune(ctx context.Context, pruneFilters filters.Args) (volume.PruneReport, error) {
14
-	query, err := getFiltersQuery(pruneFilters)
15
-	if err != nil {
16
-		return volume.PruneReport{}, err
17
-	}
13
+func (cli *Client) VolumesPrune(ctx context.Context, pruneFilters Filters) (volume.PruneReport, error) {
14
+	query := url.Values{}
15
+	pruneFilters.updateURLValues(query)
18 16
 
19 17
 	resp, err := cli.post(ctx, "/volumes/prune", query, nil, nil)
20 18
 	defer ensureReaderClosed(resp)
... ...
@@ -8,7 +8,6 @@ import (
8 8
 	"testing"
9 9
 
10 10
 	cerrdefs "github.com/containerd/errdefs"
11
-	"github.com/moby/moby/api/types/filters"
12 11
 	"github.com/moby/moby/api/types/swarm"
13 12
 	"github.com/moby/moby/client"
14 13
 	"gotest.tools/v3/assert"
... ...
@@ -105,7 +104,7 @@ func (d *Daemon) CheckRunningTaskNetworks(ctx context.Context) func(t *testing.T
105 105
 		defer cli.Close()
106 106
 
107 107
 		tasks, err := cli.TaskList(ctx, client.TaskListOptions{
108
-			Filters: filters.NewArgs(filters.Arg("desired-state", "running")),
108
+			Filters: make(client.Filters).Add("desired-state", "running"),
109 109
 		})
110 110
 		assert.NilError(t, err)
111 111
 
... ...
@@ -126,7 +125,7 @@ func (d *Daemon) CheckRunningTaskImages(ctx context.Context) func(t *testing.T)
126 126
 		defer cli.Close()
127 127
 
128 128
 		tasks, err := cli.TaskList(ctx, client.TaskListOptions{
129
-			Filters: filters.NewArgs(filters.Arg("desired-state", "running")),
129
+			Filters: make(client.Filters).Add("desired-state", "running"),
130 130
 		})
131 131
 		assert.NilError(t, err)
132 132
 
... ...
@@ -17,7 +17,6 @@ import (
17 17
 	cerrdefs "github.com/containerd/errdefs"
18 18
 	"github.com/moby/moby/api/types/build"
19 19
 	"github.com/moby/moby/api/types/events"
20
-	"github.com/moby/moby/api/types/filters"
21 20
 	"github.com/moby/moby/api/types/image"
22 21
 	"github.com/moby/moby/client"
23 22
 	"github.com/moby/moby/client/pkg/jsonmessage"
... ...
@@ -122,9 +121,9 @@ func TestBuildWithRemoveAndForceRemove(t *testing.T) {
122 122
 	}
123 123
 }
124 124
 
125
-func buildContainerIdsFilter(buildOutput io.Reader) (filters.Args, error) {
125
+func buildContainerIdsFilter(buildOutput io.Reader) (client.Filters, error) {
126 126
 	const intermediateContainerPrefix = " ---> Running in "
127
-	filter := filters.NewArgs()
127
+	filter := client.Filters{}
128 128
 
129 129
 	dec := json.NewDecoder(buildOutput)
130 130
 	for {
... ...
@@ -10,7 +10,6 @@ import (
10 10
 
11 11
 	cerrdefs "github.com/containerd/errdefs"
12 12
 	"github.com/moby/moby/api/pkg/stdcopy"
13
-	"github.com/moby/moby/api/types/filters"
14 13
 	swarmtypes "github.com/moby/moby/api/types/swarm"
15 14
 	"github.com/moby/moby/client"
16 15
 	"github.com/moby/moby/v2/integration/internal/swarm"
... ...
@@ -76,32 +75,32 @@ func TestConfigList(t *testing.T) {
76 76
 
77 77
 	testCases := []struct {
78 78
 		desc     string
79
-		filters  filters.Args
79
+		filters  client.Filters
80 80
 		expected []string
81 81
 	}{
82 82
 		{
83 83
 			desc:     "test filter by name",
84
-			filters:  filters.NewArgs(filters.Arg("name", testName0)),
84
+			filters:  make(client.Filters).Add("name", testName0),
85 85
 			expected: []string{testName0},
86 86
 		},
87 87
 		{
88 88
 			desc:     "test filter by id",
89
-			filters:  filters.NewArgs(filters.Arg("id", config1ID)),
89
+			filters:  make(client.Filters).Add("id", config1ID),
90 90
 			expected: []string{testName1},
91 91
 		},
92 92
 		{
93 93
 			desc:     "test filter by label key only",
94
-			filters:  filters.NewArgs(filters.Arg("label", "type")),
94
+			filters:  make(client.Filters).Add("label", "type"),
95 95
 			expected: testNames,
96 96
 		},
97 97
 		{
98 98
 			desc:     "test filter by label key=value " + testName0,
99
-			filters:  filters.NewArgs(filters.Arg("label", "type=test")),
99
+			filters:  make(client.Filters).Add("label", "type=test"),
100 100
 			expected: []string{testName0},
101 101
 		},
102 102
 		{
103 103
 			desc:     "test filter by label key=value " + testName1,
104
-			filters:  filters.NewArgs(filters.Arg("label", "type=production")),
104
+			filters:  make(client.Filters).Add("label", "type=production"),
105 105
 			expected: []string{testName1},
106 106
 		},
107 107
 	}
... ...
@@ -5,7 +5,6 @@ import (
5 5
 	"strings"
6 6
 	"testing"
7 7
 
8
-	"github.com/moby/moby/api/types/filters"
9 8
 	"github.com/moby/moby/client"
10 9
 	"github.com/moby/moby/client/pkg/jsonmessage"
11 10
 	"github.com/moby/moby/v2/integration/internal/container"
... ...
@@ -45,7 +44,7 @@ func TestExportContainerAndImportImage(t *testing.T) {
45 45
 	assert.NilError(t, err)
46 46
 
47 47
 	images, err := apiClient.ImageList(ctx, client.ImageListOptions{
48
-		Filters: filters.NewArgs(filters.Arg("reference", reference)),
48
+		Filters: make(client.Filters).Add("reference", reference),
49 49
 	})
50 50
 	assert.NilError(t, err)
51 51
 	assert.Check(t, is.Equal(jm.Status, images[0].ID))
... ...
@@ -4,7 +4,6 @@ import (
4 4
 	"os"
5 5
 	"testing"
6 6
 
7
-	"github.com/moby/moby/api/types/filters"
8 7
 	"github.com/moby/moby/client"
9 8
 	"github.com/moby/moby/v2/integration/internal/container"
10 9
 	"gotest.tools/v3/assert"
... ...
@@ -44,7 +43,7 @@ func TestLinksContainerNames(t *testing.T) {
44 44
 	container.Run(ctx, t, apiClient, container.WithName(containerB), container.WithLinks(containerA+":"+containerA))
45 45
 
46 46
 	containers, err := apiClient.ContainerList(ctx, client.ContainerListOptions{
47
-		Filters: filters.NewArgs(filters.Arg("name", containerA)),
47
+		Filters: make(client.Filters).Add("name", containerA),
48 48
 	})
49 49
 	assert.NilError(t, err)
50 50
 	assert.Check(t, is.Equal(1, len(containers)))
... ...
@@ -7,7 +7,6 @@ import (
7 7
 	"time"
8 8
 
9 9
 	containertypes "github.com/moby/moby/api/types/container"
10
-	"github.com/moby/moby/api/types/filters"
11 10
 	"github.com/moby/moby/api/types/versions"
12 11
 	"github.com/moby/moby/client"
13 12
 	"github.com/moby/moby/v2/integration/internal/container"
... ...
@@ -68,7 +67,7 @@ func TestContainerList_Annotations(t *testing.T) {
68 68
 
69 69
 			containers, err := apiClient.ContainerList(ctx, client.ContainerListOptions{
70 70
 				All:     true,
71
-				Filters: filters.NewArgs(filters.Arg("id", id)),
71
+				Filters: make(client.Filters).Add("id", id),
72 72
 			})
73 73
 			assert.NilError(t, err)
74 74
 			assert.Assert(t, is.Len(containers, 1))
... ...
@@ -104,7 +103,7 @@ func TestContainerList_Filter(t *testing.T) {
104 104
 		ctx := testutil.StartSpan(ctx, t)
105 105
 		results, err := apiClient.ContainerList(ctx, client.ContainerListOptions{
106 106
 			All:     true,
107
-			Filters: filters.NewArgs(filters.Arg("since", top)),
107
+			Filters: make(client.Filters).Add("since", top),
108 108
 		})
109 109
 		assert.NilError(t, err)
110 110
 		assert.Check(t, is.Contains(containerIDs(results), next))
... ...
@@ -114,7 +113,7 @@ func TestContainerList_Filter(t *testing.T) {
114 114
 		ctx := testutil.StartSpan(ctx, t)
115 115
 		results, err := apiClient.ContainerList(ctx, client.ContainerListOptions{
116 116
 			All:     true,
117
-			Filters: filters.NewArgs(filters.Arg("before", top)),
117
+			Filters: make(client.Filters).Add("before", top),
118 118
 		})
119 119
 		assert.NilError(t, err)
120 120
 		assert.Check(t, is.Contains(containerIDs(results), prev))
... ...
@@ -152,7 +151,7 @@ func pollForHealthStatusSummary(ctx context.Context, apiClient client.APIClient,
152 152
 	return func(log poll.LogT) poll.Result {
153 153
 		containers, err := apiClient.ContainerList(ctx, client.ContainerListOptions{
154 154
 			All:     true,
155
-			Filters: filters.NewArgs(filters.Arg("id", containerID)),
155
+			Filters: make(client.Filters).Add("id", containerID),
156 156
 		})
157 157
 		if err != nil {
158 158
 			return poll.Error(err)
... ...
@@ -7,7 +7,6 @@ import (
7 7
 
8 8
 	cerrdefs "github.com/containerd/errdefs"
9 9
 	"github.com/moby/moby/api/types/events"
10
-	"github.com/moby/moby/api/types/filters"
11 10
 	"github.com/moby/moby/client"
12 11
 	"github.com/moby/moby/v2/integration/internal/container"
13 12
 	"github.com/moby/moby/v2/internal/testutil/request"
... ...
@@ -43,7 +42,7 @@ func TestPause(t *testing.T) {
43 43
 	messages, errs := apiClient.Events(ctx, client.EventsListOptions{
44 44
 		Since:   since,
45 45
 		Until:   until,
46
-		Filters: filters.NewArgs(filters.Arg(string(events.ContainerEventType), cID)),
46
+		Filters: make(client.Filters).Add(string(events.ContainerEventType), cID),
47 47
 	})
48 48
 	assert.Check(t, is.DeepEqual([]events.Action{events.ActionPause, events.ActionUnPause}, getEventActions(t, messages, errs)))
49 49
 }
... ...
@@ -9,7 +9,6 @@ import (
9 9
 
10 10
 	"github.com/moby/moby/api/types/container"
11 11
 	"github.com/moby/moby/api/types/events"
12
-	"github.com/moby/moby/api/types/filters"
13 12
 	"github.com/moby/moby/client"
14 13
 	testContainer "github.com/moby/moby/v2/integration/internal/container"
15 14
 	"github.com/moby/moby/v2/internal/testutil"
... ...
@@ -242,10 +241,7 @@ func TestContainerRestartWithCancelledRequest(t *testing.T) {
242 242
 
243 243
 	// Start listening for events.
244 244
 	messages, errs := apiClient.Events(ctx, client.EventsListOptions{
245
-		Filters: filters.NewArgs(
246
-			filters.Arg("container", cID),
247
-			filters.Arg("event", string(events.ActionRestart)),
248
-		),
245
+		Filters: make(client.Filters).Add("container", cID).Add("event", string(events.ActionRestart)),
249 246
 	})
250 247
 
251 248
 	// Make restart request, but cancel the request before the container
... ...
@@ -8,7 +8,6 @@ import (
8 8
 	"time"
9 9
 
10 10
 	"github.com/google/go-cmp/cmp/cmpopts"
11
-	"github.com/moby/moby/api/types/filters"
12 11
 	"github.com/moby/moby/api/types/image"
13 12
 	"github.com/moby/moby/api/types/versions"
14 13
 	"github.com/moby/moby/client"
... ...
@@ -43,12 +42,8 @@ func TestImagesFilterMultiReference(t *testing.T) {
43 43
 		assert.NilError(t, err)
44 44
 	}
45 45
 
46
-	filter := filters.NewArgs()
47
-	filter.Add("reference", repoTags[0])
48
-	filter.Add("reference", repoTags[1])
49
-	filter.Add("reference", repoTags[2])
50 46
 	options := client.ImageListOptions{
51
-		Filters: filter,
47
+		Filters: make(client.Filters).Add("reference", repoTags[:3]...),
52 48
 	}
53 49
 	images, err := apiClient.ImageList(ctx, options)
54 50
 	assert.NilError(t, err)
... ...
@@ -89,12 +84,11 @@ func TestImagesFilterUntil(t *testing.T) {
89 89
 	assert.NilError(t, err)
90 90
 	laterUntil := laterImage.Created
91 91
 
92
-	filter := filters.NewArgs(
93
-		filters.Arg("since", imgs[0]),
94
-		filters.Arg("until", olderUntil),
95
-		filters.Arg("until", laterUntil),
96
-		filters.Arg("before", imgs[len(imgs)-1]),
97
-	)
92
+	filter := make(client.Filters).
93
+		Add("since", imgs[0]).
94
+		Add("until", olderUntil).
95
+		Add("until", laterUntil).
96
+		Add("before", imgs[len(imgs)-1])
98 97
 	list, err := apiClient.ImageList(ctx, client.ImageListOptions{Filters: filter})
99 98
 	assert.NilError(t, err)
100 99
 
... ...
@@ -125,10 +119,9 @@ func TestImagesFilterBeforeSince(t *testing.T) {
125 125
 		imgs[i] = id.ID
126 126
 	}
127 127
 
128
-	filter := filters.NewArgs(
129
-		filters.Arg("since", imgs[0]),
130
-		filters.Arg("before", imgs[len(imgs)-1]),
131
-	)
128
+	filter := make(client.Filters).
129
+		Add("since", imgs[0]).
130
+		Add("before", imgs[len(imgs)-1])
132 131
 	list, err := apiClient.ImageList(ctx, client.ImageListOptions{Filters: filter})
133 132
 	assert.NilError(t, err)
134 133
 
... ...
@@ -155,31 +148,31 @@ func TestAPIImagesFilters(t *testing.T) {
155 155
 
156 156
 	testcases := []struct {
157 157
 		name             string
158
-		filters          []filters.KeyValuePair
158
+		filters          client.Filters
159 159
 		expectedImages   int
160 160
 		expectedRepoTags int
161 161
 	}{
162 162
 		{
163 163
 			name:             "repository regex",
164
-			filters:          []filters.KeyValuePair{filters.Arg("reference", "utest*/*")},
164
+			filters:          make(client.Filters).Add("reference", "utest*/*"),
165 165
 			expectedImages:   1,
166 166
 			expectedRepoTags: 2,
167 167
 		},
168 168
 		{
169 169
 			name:             "image name regex",
170
-			filters:          []filters.KeyValuePair{filters.Arg("reference", "utest*")},
170
+			filters:          make(client.Filters).Add("reference", "utest*"),
171 171
 			expectedImages:   1,
172 172
 			expectedRepoTags: 1,
173 173
 		},
174 174
 		{
175 175
 			name:             "image name without a tag",
176
-			filters:          []filters.KeyValuePair{filters.Arg("reference", "utest")},
176
+			filters:          make(client.Filters).Add("reference", "utest"),
177 177
 			expectedImages:   1,
178 178
 			expectedRepoTags: 1,
179 179
 		},
180 180
 		{
181 181
 			name:             "registry port regex",
182
-			filters:          []filters.KeyValuePair{filters.Arg("reference", "*5000*/*")},
182
+			filters:          make(client.Filters).Add("reference", "*5000*/*"),
183 183
 			expectedImages:   1,
184 184
 			expectedRepoTags: 1,
185 185
 		},
... ...
@@ -191,7 +184,7 @@ func TestAPIImagesFilters(t *testing.T) {
191 191
 
192 192
 			ctx := testutil.StartSpan(ctx, t)
193 193
 			images, err := apiClient.ImageList(ctx, client.ImageListOptions{
194
-				Filters: filters.NewArgs(tc.filters...),
194
+				Filters: tc.filters,
195 195
 			})
196 196
 			assert.Check(t, err)
197 197
 			assert.Assert(t, is.Len(images, tc.expectedImages))
... ...
@@ -4,7 +4,6 @@ import (
4 4
 	"strings"
5 5
 	"testing"
6 6
 
7
-	"github.com/moby/moby/api/types/filters"
8 7
 	"github.com/moby/moby/api/types/image"
9 8
 	"github.com/moby/moby/client"
10 9
 	"github.com/moby/moby/v2/integration/internal/container"
... ...
@@ -40,7 +39,7 @@ func TestPruneDontDeleteUsedDangling(t *testing.T) {
40 40
 		container.WithImage(danglingID),
41 41
 		container.WithCmd("sleep", "60"))
42 42
 
43
-	pruned, err := apiClient.ImagesPrune(ctx, filters.NewArgs(filters.Arg("dangling", "true")))
43
+	pruned, err := apiClient.ImagesPrune(ctx, make(client.Filters).Add("dangling", "true"))
44 44
 	assert.NilError(t, err)
45 45
 
46 46
 	for _, deleted := range pruned.ImagesDeleted {
... ...
@@ -88,7 +87,7 @@ func TestPruneLexographicalOrder(t *testing.T) {
88 88
 	cid := container.Create(ctx, t, apiClient, container.WithImage(id))
89 89
 	defer container.Remove(ctx, t, apiClient, cid, client.ContainerRemoveOptions{Force: true})
90 90
 
91
-	pruned, err := apiClient.ImagesPrune(ctx, filters.NewArgs(filters.Arg("dangling", "false")))
91
+	pruned, err := apiClient.ImagesPrune(ctx, make(client.Filters).Add("dangling", "false"))
92 92
 	assert.NilError(t, err)
93 93
 
94 94
 	assert.Check(t, is.Len(pruned.ImagesDeleted, len(tags)))
... ...
@@ -218,7 +217,7 @@ func TestPruneDontDeleteUsedImage(t *testing.T) {
218 218
 				defer container.Remove(ctx, t, apiClient, cid, client.ContainerRemoveOptions{Force: true})
219 219
 
220 220
 				// dangling=false also prunes unused images
221
-				pruned, err := apiClient.ImagesPrune(ctx, filters.NewArgs(filters.Arg("dangling", "false")))
221
+				pruned, err := apiClient.ImagesPrune(ctx, make(client.Filters).Add("dangling", "false"))
222 222
 				assert.NilError(t, err)
223 223
 
224 224
 				env.check(t, apiClient, pruned)
... ...
@@ -6,7 +6,6 @@ import (
6 6
 	"testing"
7 7
 	"time"
8 8
 
9
-	"github.com/moby/moby/api/types/filters"
10 9
 	swarmtypes "github.com/moby/moby/api/types/swarm"
11 10
 	"github.com/moby/moby/client"
12 11
 	"github.com/moby/moby/v2/internal/testutil/daemon"
... ...
@@ -206,10 +205,9 @@ func GetRunningTasks(ctx context.Context, t *testing.T, c client.ServiceAPIClien
206 206
 	t.Helper()
207 207
 
208 208
 	tasks, err := c.TaskList(ctx, client.TaskListOptions{
209
-		Filters: filters.NewArgs(
210
-			filters.Arg("service", serviceID),
211
-			filters.Arg("desired-state", "running"),
212
-		),
209
+		Filters: make(client.Filters).
210
+			Add("service", serviceID).
211
+			Add("desired-state", "running"),
213 212
 	})
214 213
 
215 214
 	assert.NilError(t, err)
... ...
@@ -4,7 +4,6 @@ import (
4 4
 	"context"
5 5
 	"fmt"
6 6
 
7
-	"github.com/moby/moby/api/types/filters"
8 7
 	swarmtypes "github.com/moby/moby/api/types/swarm"
9 8
 	"github.com/moby/moby/client"
10 9
 	"gotest.tools/v3/poll"
... ...
@@ -14,9 +13,7 @@ import (
14 14
 func NoTasksForService(ctx context.Context, apiClient client.ServiceAPIClient, serviceID string) func(log poll.LogT) poll.Result {
15 15
 	return func(log poll.LogT) poll.Result {
16 16
 		tasks, err := apiClient.TaskList(ctx, client.TaskListOptions{
17
-			Filters: filters.NewArgs(
18
-				filters.Arg("service", serviceID),
19
-			),
17
+			Filters: make(client.Filters).Add("service", serviceID),
20 18
 		})
21 19
 		if err == nil {
22 20
 			if len(tasks) == 0 {
... ...
@@ -50,10 +47,8 @@ func NoTasks(ctx context.Context, apiClient client.ServiceAPIClient) func(log po
50 50
 // RunningTasksCount verifies there are `instances` tasks running for `serviceID`
51 51
 func RunningTasksCount(ctx context.Context, apiClient client.ServiceAPIClient, serviceID string, instances uint64) func(log poll.LogT) poll.Result {
52 52
 	return func(log poll.LogT) poll.Result {
53
-		filter := filters.NewArgs()
54
-		filter.Add("service", serviceID)
55 53
 		tasks, err := apiClient.TaskList(ctx, client.TaskListOptions{
56
-			Filters: filter,
54
+			Filters: make(client.Filters).Add("service", serviceID),
57 55
 		})
58 56
 		var running int
59 57
 		var taskError string
... ...
@@ -90,7 +85,7 @@ func RunningTasksCount(ctx context.Context, apiClient client.ServiceAPIClient, s
90 90
 // completed additionally, while polling, it verifies that the job never
91 91
 // exceeds MaxConcurrent running tasks
92 92
 func JobComplete(ctx context.Context, apiClient client.ServiceAPIClient, service swarmtypes.Service) func(log poll.LogT) poll.Result {
93
-	filter := filters.NewArgs(filters.Arg("service", service.ID))
93
+	filter := make(client.Filters).Add("service", service.ID)
94 94
 
95 95
 	var jobIteration swarmtypes.Version
96 96
 	if service.JobStatus != nil {
... ...
@@ -165,7 +160,7 @@ func JobComplete(ctx context.Context, apiClient client.ServiceAPIClient, service
165 165
 func HasLeader(ctx context.Context, apiClient client.NodeAPIClient) func(log poll.LogT) poll.Result {
166 166
 	return func(log poll.LogT) poll.Result {
167 167
 		nodes, err := apiClient.NodeList(ctx, client.NodeListOptions{
168
-			Filters: filters.NewArgs(filters.Arg("role", "manager")),
168
+			Filters: make(client.Filters).Add("role", "manager"),
169 169
 		})
170 170
 		if err != nil {
171 171
 			return poll.Error(err)
... ...
@@ -5,7 +5,6 @@ import (
5 5
 	"strconv"
6 6
 	"testing"
7 7
 
8
-	"github.com/moby/moby/api/types/filters"
9 8
 	networktypes "github.com/moby/moby/api/types/network"
10 9
 	"github.com/moby/moby/client"
11 10
 	"github.com/moby/moby/v2/integration/internal/network"
... ...
@@ -166,7 +165,7 @@ func TestInspectNetwork(t *testing.T) {
166 166
 
167 167
 	leaderID := func() string {
168 168
 		ls, err := c1.NodeList(ctx, client.NodeListOptions{
169
-			Filters: filters.NewArgs(filters.Arg("role", "manager")),
169
+			Filters: make(client.Filters).Add("role", "manager"),
170 170
 		})
171 171
 		assert.NilError(t, err)
172 172
 		for _, node := range ls {
... ...
@@ -6,7 +6,6 @@ import (
6 6
 	"net/http"
7 7
 	"testing"
8 8
 
9
-	"github.com/moby/moby/api/types/filters"
10 9
 	networktypes "github.com/moby/moby/api/types/network"
11 10
 	"github.com/moby/moby/client"
12 11
 	"github.com/moby/moby/v2/internal/testutil"
... ...
@@ -132,7 +131,7 @@ func TestAPINetworkFilter(t *testing.T) {
132 132
 	apiClient := testEnv.APIClient()
133 133
 
134 134
 	networks, err := apiClient.NetworkList(ctx, client.NetworkListOptions{
135
-		Filters: filters.NewArgs(filters.Arg("name", networkName)),
135
+		Filters: make(client.Filters).Add("name", networkName),
136 136
 	})
137 137
 
138 138
 	assert.NilError(t, err)
... ...
@@ -8,7 +8,6 @@ import (
8 8
 	"io"
9 9
 	"testing"
10 10
 
11
-	"github.com/moby/moby/api/types/filters"
12 11
 	"github.com/moby/moby/api/types/volume"
13 12
 	"github.com/moby/moby/client"
14 13
 	"github.com/moby/moby/v2/integration/internal/container"
... ...
@@ -111,7 +110,7 @@ func TestAuthZPluginV2RejectVolumeRequests(t *testing.T) {
111 111
 	assert.Assert(t, err != nil)
112 112
 	assert.ErrorContains(t, err, fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag))
113 113
 
114
-	_, err = c.VolumesPrune(ctx, filters.Args{})
114
+	_, err = c.VolumesPrune(ctx, nil)
115 115
 	assert.Assert(t, err != nil)
116 116
 	assert.ErrorContains(t, err, fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag))
117 117
 }
... ...
@@ -10,7 +10,6 @@ import (
10 10
 
11 11
 	cerrdefs "github.com/containerd/errdefs"
12 12
 	"github.com/moby/moby/api/pkg/stdcopy"
13
-	"github.com/moby/moby/api/types/filters"
14 13
 	swarmtypes "github.com/moby/moby/api/types/swarm"
15 14
 	"github.com/moby/moby/client"
16 15
 	"github.com/moby/moby/v2/integration/internal/swarm"
... ...
@@ -73,30 +72,30 @@ func TestSecretList(t *testing.T) {
73 73
 	assert.Check(t, is.DeepEqual(secretNamesFromList(entries), testNames))
74 74
 
75 75
 	testCases := []struct {
76
-		filters  filters.Args
76
+		filters  client.Filters
77 77
 		expected []string
78 78
 	}{
79 79
 		// test filter by name `secret ls --filter name=xxx`
80 80
 		{
81
-			filters:  filters.NewArgs(filters.Arg("name", testName0)),
81
+			filters:  make(client.Filters).Add("name", testName0),
82 82
 			expected: []string{testName0},
83 83
 		},
84 84
 		// test filter by id `secret ls --filter id=xxx`
85 85
 		{
86
-			filters:  filters.NewArgs(filters.Arg("id", secret1ID)),
86
+			filters:  make(client.Filters).Add("id", secret1ID),
87 87
 			expected: []string{testName1},
88 88
 		},
89 89
 		// test filter by label `secret ls --filter label=xxx`
90 90
 		{
91
-			filters:  filters.NewArgs(filters.Arg("label", "type")),
91
+			filters:  make(client.Filters).Add("label", "type"),
92 92
 			expected: testNames,
93 93
 		},
94 94
 		{
95
-			filters:  filters.NewArgs(filters.Arg("label", "type=test")),
95
+			filters:  make(client.Filters).Add("label", "type=test"),
96 96
 			expected: []string{testName0},
97 97
 		},
98 98
 		{
99
-			filters:  filters.NewArgs(filters.Arg("label", "type=production")),
99
+			filters:  make(client.Filters).Add("label", "type=production"),
100 100
 			expected: []string{testName1},
101 101
 		},
102 102
 	}
... ...
@@ -9,7 +9,6 @@ import (
9 9
 
10 10
 	cerrdefs "github.com/containerd/errdefs"
11 11
 	"github.com/moby/moby/api/types/container"
12
-	"github.com/moby/moby/api/types/filters"
13 12
 	swarmtypes "github.com/moby/moby/api/types/swarm"
14 13
 	"github.com/moby/moby/client"
15 14
 	"github.com/moby/moby/v2/integration/internal/network"
... ...
@@ -65,7 +64,7 @@ func testServiceCreateInit(ctx context.Context, daemonEnabled bool) func(t *test
65 65
 func inspectServiceContainer(ctx context.Context, t *testing.T, apiClient client.APIClient, serviceID string) container.InspectResponse {
66 66
 	t.Helper()
67 67
 	containers, err := apiClient.ContainerList(ctx, client.ContainerListOptions{
68
-		Filters: filters.NewArgs(filters.Arg("label", "com.docker.swarm.service.id="+serviceID)),
68
+		Filters: make(client.Filters).Add("label", "com.docker.swarm.service.id="+serviceID),
69 69
 	})
70 70
 	assert.NilError(t, err)
71 71
 	assert.Check(t, is.Len(containers, 1))
... ...
@@ -368,7 +367,7 @@ func TestCreateServiceSysctls(t *testing.T) {
368 368
 
369 369
 		// get all tasks of the service, so we can get the container
370 370
 		tasks, err := apiClient.TaskList(ctx, client.TaskListOptions{
371
-			Filters: filters.NewArgs(filters.Arg("service", serviceID)),
371
+			Filters: make(client.Filters).Add("service", serviceID),
372 372
 		})
373 373
 		assert.NilError(t, err)
374 374
 		assert.Check(t, is.Equal(len(tasks), 1))
... ...
@@ -438,7 +437,7 @@ func TestCreateServiceCapabilities(t *testing.T) {
438 438
 
439 439
 	// get all tasks of the service, so we can get the container
440 440
 	tasks, err := apiClient.TaskList(ctx, client.TaskListOptions{
441
-		Filters: filters.NewArgs(filters.Arg("service", serviceID)),
441
+		Filters: make(client.Filters).Add("service", serviceID),
442 442
 	})
443 443
 	assert.NilError(t, err)
444 444
 	assert.Check(t, is.Equal(len(tasks), 1))
... ...
@@ -4,7 +4,6 @@ import (
4 4
 	"fmt"
5 5
 	"testing"
6 6
 
7
-	"github.com/moby/moby/api/types/filters"
8 7
 	swarmtypes "github.com/moby/moby/api/types/swarm"
9 8
 	"github.com/moby/moby/client"
10 9
 	"github.com/moby/moby/v2/integration/internal/swarm"
... ...
@@ -54,7 +53,7 @@ func TestServiceListWithStatuses(t *testing.T) {
54 54
 		// bespoke closure right here.
55 55
 		poll.WaitOn(t, func(log poll.LogT) poll.Result {
56 56
 			tasks, err := apiClient.TaskList(ctx, client.TaskListOptions{
57
-				Filters: filters.NewArgs(filters.Arg("service", id)),
57
+				Filters: make(client.Filters).Add("service", id),
58 58
 			})
59 59
 
60 60
 			running := 0
... ...
@@ -6,7 +6,6 @@ import (
6 6
 	"strings"
7 7
 	"testing"
8 8
 
9
-	"github.com/moby/moby/api/types/filters"
10 9
 	swarmtypes "github.com/moby/moby/api/types/swarm"
11 10
 	"github.com/moby/moby/client"
12 11
 	"github.com/moby/moby/v2/integration/internal/swarm"
... ...
@@ -68,7 +67,7 @@ func TestServicePlugin(t *testing.T) {
68 68
 
69 69
 	// test that environment variables are passed from plugin service to plugin instance
70 70
 	service := d1.GetService(ctx, t, id)
71
-	tasks := d1.GetServiceTasks(ctx, t, service.Spec.Annotations.Name, filters.Arg("runtime", "plugin"))
71
+	tasks := d1.GetServiceTasksWithFilters(ctx, t, service.Spec.Annotations.Name, make(client.Filters).Add("runtime", "plugin"))
72 72
 	if len(tasks) == 0 {
73 73
 		t.Log("No tasks found for plugin service")
74 74
 		t.Fail()
... ...
@@ -5,7 +5,6 @@ import (
5 5
 	"testing"
6 6
 
7 7
 	"github.com/moby/moby/api/types/container"
8
-	"github.com/moby/moby/api/types/filters"
9 8
 	swarmtypes "github.com/moby/moby/api/types/swarm"
10 9
 	"github.com/moby/moby/client"
11 10
 	"github.com/moby/moby/v2/integration/internal/network"
... ...
@@ -325,10 +324,7 @@ func TestServiceUpdatePidsLimit(t *testing.T) {
325 325
 func getServiceTaskContainer(ctx context.Context, t *testing.T, cli client.APIClient, serviceID string) container.InspectResponse {
326 326
 	t.Helper()
327 327
 	tasks, err := cli.TaskList(ctx, client.TaskListOptions{
328
-		Filters: filters.NewArgs(
329
-			filters.Arg("service", serviceID),
330
-			filters.Arg("desired-state", "running"),
331
-		),
328
+		Filters: make(client.Filters).Add("service", serviceID).Add("desired-state", "running"),
332 329
 	})
333 330
 	assert.NilError(t, err)
334 331
 	assert.Assert(t, len(tasks) > 0)
... ...
@@ -9,7 +9,6 @@ import (
9 9
 	"time"
10 10
 
11 11
 	"github.com/moby/moby/api/types/events"
12
-	"github.com/moby/moby/api/types/filters"
13 12
 	"github.com/moby/moby/api/types/mount"
14 13
 	"github.com/moby/moby/api/types/volume"
15 14
 	"github.com/moby/moby/client"
... ...
@@ -33,10 +32,7 @@ func TestEventsExecDie(t *testing.T) {
33 33
 	assert.NilError(t, err)
34 34
 
35 35
 	msg, errs := apiClient.Events(ctx, client.EventsListOptions{
36
-		Filters: filters.NewArgs(
37
-			filters.Arg("container", cID),
38
-			filters.Arg("event", string(events.ActionExecDie)),
39
-		),
36
+		Filters: make(client.Filters).Add("container", cID).Add("event", string(events.ActionExecDie)),
40 37
 	})
41 38
 
42 39
 	err = apiClient.ContainerExecStart(ctx, id.ID, client.ExecStartOptions{
... ...
@@ -108,11 +104,10 @@ func TestEventsVolumeCreate(t *testing.T) {
108 108
 	_, err := apiClient.VolumeCreate(ctx, volume.CreateOptions{Name: volName})
109 109
 	assert.NilError(t, err)
110 110
 
111
-	filter := filters.NewArgs(
112
-		filters.Arg("type", "volume"),
113
-		filters.Arg("event", "create"),
114
-		filters.Arg("volume", volName),
115
-	)
111
+	filter := make(client.Filters).
112
+		Add("type", "volume").
113
+		Add("event", "create").
114
+		Add("volume", volName)
116 115
 	messages, errs := apiClient.Events(ctx, client.EventsListOptions{
117 116
 		Since:   since,
118 117
 		Until:   request.DaemonUnixTime(ctx, t, apiClient, testEnv),
... ...
@@ -10,7 +10,6 @@ import (
10 10
 
11 11
 	cerrdefs "github.com/containerd/errdefs"
12 12
 	"github.com/google/go-cmp/cmp/cmpopts"
13
-	"github.com/moby/moby/api/types/filters"
14 13
 	"github.com/moby/moby/api/types/volume"
15 14
 	"github.com/moby/moby/client"
16 15
 	"github.com/moby/moby/v2/integration/internal/build"
... ...
@@ -271,7 +270,7 @@ func TestVolumePruneAnonymous(t *testing.T) {
271 271
 	assert.NilError(t, err)
272 272
 
273 273
 	// Prune anonymous volumes
274
-	pruneReport, err := apiClient.VolumesPrune(ctx, filters.Args{})
274
+	pruneReport, err := apiClient.VolumesPrune(ctx, nil)
275 275
 	assert.NilError(t, err)
276 276
 	assert.Check(t, is.Equal(len(pruneReport.VolumesDeleted), 1))
277 277
 	assert.Check(t, is.Equal(pruneReport.VolumesDeleted[0], v.Name))
... ...
@@ -283,7 +282,7 @@ func TestVolumePruneAnonymous(t *testing.T) {
283 283
 	_, err = apiClient.VolumeCreate(ctx, volume.CreateOptions{})
284 284
 	assert.NilError(t, err)
285 285
 
286
-	pruneReport, err = apiClient.VolumesPrune(ctx, filters.NewArgs(filters.Arg("all", "1")))
286
+	pruneReport, err = apiClient.VolumesPrune(ctx, make(client.Filters).Add("all", "1"))
287 287
 	assert.NilError(t, err)
288 288
 	assert.Check(t, is.Equal(len(pruneReport.VolumesDeleted), 2))
289 289
 
... ...
@@ -298,7 +297,7 @@ func TestVolumePruneAnonymous(t *testing.T) {
298 298
 	vNamed, err = apiClient.VolumeCreate(ctx, volume.CreateOptions{Name: "test-api141"})
299 299
 	assert.NilError(t, err)
300 300
 
301
-	pruneReport, err = clientOld.VolumesPrune(ctx, filters.Args{})
301
+	pruneReport, err = clientOld.VolumesPrune(ctx, nil)
302 302
 	assert.NilError(t, err)
303 303
 	assert.Check(t, is.Equal(len(pruneReport.VolumesDeleted), 2))
304 304
 	assert.Check(t, is.Contains(pruneReport.VolumesDeleted, v.Name))
... ...
@@ -333,7 +332,7 @@ VOLUME ` + volDest
333 333
 	err = apiClient.ContainerRemove(ctx, id, client.ContainerRemoveOptions{})
334 334
 	assert.NilError(t, err)
335 335
 
336
-	pruneReport, err := apiClient.VolumesPrune(ctx, filters.Args{})
336
+	pruneReport, err := apiClient.VolumesPrune(ctx, nil)
337 337
 	assert.NilError(t, err)
338 338
 	assert.Assert(t, is.Contains(pruneReport.VolumesDeleted, volumeName))
339 339
 }
... ...
@@ -2,10 +2,11 @@ package daemon
2 2
 
3 3
 import (
4 4
 	"context"
5
+	"maps"
6
+	"slices"
5 7
 	"testing"
6 8
 	"time"
7 9
 
8
-	"github.com/moby/moby/api/types/filters"
9 10
 	"github.com/moby/moby/api/types/swarm"
10 11
 	"github.com/moby/moby/client"
11 12
 	"gotest.tools/v3/assert"
... ...
@@ -50,17 +51,19 @@ func (d *Daemon) GetService(ctx context.Context, t testing.TB, id string) *swarm
50 50
 }
51 51
 
52 52
 // GetServiceTasks returns the swarm tasks for the specified service
53
-func (d *Daemon) GetServiceTasks(ctx context.Context, t testing.TB, service string, additionalFilters ...filters.KeyValuePair) []swarm.Task {
53
+func (d *Daemon) GetServiceTasks(ctx context.Context, t testing.TB, service string) []swarm.Task {
54
+	return d.GetServiceTasksWithFilters(ctx, t, service, nil)
55
+}
56
+
57
+// GetServiceTasksWithFilters returns the swarm tasks for the specified service with additional filters
58
+func (d *Daemon) GetServiceTasksWithFilters(ctx context.Context, t testing.TB, service string, additionalFilters client.Filters) []swarm.Task {
54 59
 	t.Helper()
55 60
 	cli := d.NewClientT(t)
56 61
 	defer cli.Close()
57 62
 
58
-	filterArgs := filters.NewArgs(
59
-		filters.Arg("desired-state", "running"),
60
-		filters.Arg("service", service),
61
-	)
62
-	for _, filter := range additionalFilters {
63
-		filterArgs.Add(filter.Key, filter.Value)
63
+	filterArgs := make(client.Filters).Add("desired-state", "running").Add("service", service)
64
+	for term, values := range additionalFilters {
65
+		filterArgs.Add(term, slices.Collect(maps.Keys(values))...)
64 66
 	}
65 67
 
66 68
 	options := client.TaskListOptions{
... ...
@@ -7,7 +7,6 @@ import (
7 7
 
8 8
 	cerrdefs "github.com/containerd/errdefs"
9 9
 	"github.com/moby/moby/api/types/container"
10
-	"github.com/moby/moby/api/types/filters"
11 10
 	"github.com/moby/moby/api/types/network"
12 11
 	"github.com/moby/moby/client"
13 12
 	"go.opentelemetry.io/otel"
... ...
@@ -53,7 +52,7 @@ func unpauseAllContainers(ctx context.Context, t testing.TB, client client.Conta
53 53
 func getPausedContainers(ctx context.Context, t testing.TB, apiClient client.ContainerAPIClient) []container.Summary {
54 54
 	t.Helper()
55 55
 	containers, err := apiClient.ContainerList(ctx, client.ContainerListOptions{
56
-		Filters: filters.NewArgs(filters.Arg("status", "paused")),
56
+		Filters: make(client.Filters).Add("status", "paused"),
57 57
 		All:     true,
58 58
 	})
59 59
 	assert.Check(t, err, "failed to list containers")
... ...
@@ -163,7 +162,7 @@ func deleteAllNetworks(ctx context.Context, t testing.TB, c client.NetworkAPICli
163 163
 
164 164
 func deleteAllPlugins(ctx context.Context, t testing.TB, c client.PluginAPIClient, protectedPlugins map[string]struct{}) {
165 165
 	t.Helper()
166
-	plugins, err := c.PluginList(ctx, filters.Args{})
166
+	plugins, err := c.PluginList(ctx, nil)
167 167
 	// Docker EE does not allow cluster-wide plugin management.
168 168
 	if cerrdefs.IsNotImplemented(err) {
169 169
 		return
... ...
@@ -10,7 +10,6 @@ import (
10 10
 	"testing"
11 11
 
12 12
 	"github.com/moby/moby/api/types"
13
-	"github.com/moby/moby/api/types/filters"
14 13
 	"github.com/moby/moby/api/types/system"
15 14
 	"github.com/moby/moby/client"
16 15
 	"github.com/moby/moby/v2/internal/testutil/fixtures/load"
... ...
@@ -200,10 +199,9 @@ func (e *Execution) UsingSnapshotter() bool {
200 200
 func (e *Execution) HasExistingImage(t testing.TB, reference string) bool {
201 201
 	imageList, err := e.APIClient().ImageList(context.Background(), client.ImageListOptions{
202 202
 		All: true,
203
-		Filters: filters.NewArgs(
204
-			filters.Arg("dangling", "false"),
205
-			filters.Arg("reference", reference),
206
-		),
203
+		Filters: make(client.Filters).
204
+			Add("dangling", "false").
205
+			Add("reference", reference),
207 206
 	})
208 207
 	assert.NilError(t, err, "failed to list images")
209 208
 
... ...
@@ -5,7 +5,6 @@ import (
5 5
 	"testing"
6 6
 
7 7
 	cerrdefs "github.com/containerd/errdefs"
8
-	"github.com/moby/moby/api/types/filters"
9 8
 	"github.com/moby/moby/api/types/image"
10 9
 	"github.com/moby/moby/client"
11 10
 	"github.com/moby/moby/v2/internal/testutil"
... ...
@@ -117,7 +116,7 @@ func getExistingImages(ctx context.Context, t testing.TB, testEnv *Execution) []
117 117
 	apiClient := testEnv.APIClient()
118 118
 	imageList, err := apiClient.ImageList(ctx, client.ImageListOptions{
119 119
 		All:     true,
120
-		Filters: filters.NewArgs(filters.Arg("dangling", "false")),
120
+		Filters: make(client.Filters).Add("dangling", "false"),
121 121
 	})
122 122
 	assert.NilError(t, err, "failed to list images")
123 123
 
... ...
@@ -195,7 +194,7 @@ func ProtectPlugins(ctx context.Context, t testing.TB, testEnv *Execution) {
195 195
 func getExistingPlugins(ctx context.Context, t testing.TB, testEnv *Execution) []string {
196 196
 	t.Helper()
197 197
 	client := testEnv.APIClient()
198
-	pluginList, err := client.PluginList(ctx, filters.Args{})
198
+	pluginList, err := client.PluginList(ctx, nil)
199 199
 	// Docker EE does not allow cluster-wide plugin management.
200 200
 	if cerrdefs.IsNotImplemented(err) {
201 201
 		return []string{}
202 202
deleted file mode 100644
... ...
@@ -1,24 +0,0 @@
1
-package filters
2
-
3
-import "fmt"
4
-
5
-// invalidFilter indicates that the provided filter or its value is invalid
6
-type invalidFilter struct {
7
-	Filter string
8
-	Value  []string
9
-}
10
-
11
-func (e invalidFilter) Error() string {
12
-	msg := "invalid filter"
13
-	if e.Filter != "" {
14
-		msg += " '" + e.Filter
15
-		if e.Value != nil {
16
-			msg = fmt.Sprintf("%s=%s", msg, e.Value)
17
-		}
18
-		msg += "'"
19
-	}
20
-	return msg
21
-}
22
-
23
-// InvalidParameter marks this error as ErrInvalidParameter
24
-func (e invalidFilter) InvalidParameter() {}
25 1
deleted file mode 100644
... ...
@@ -1,65 +0,0 @@
1
-/*
2
-Package filters provides tools for encoding a mapping of keys to a set of
3
-multiple values.
4
-*/
5
-package filters
6
-
7
-import (
8
-	"encoding/json"
9
-)
10
-
11
-// Args stores a mapping of keys to a set of multiple values.
12
-type Args struct {
13
-	fields map[string]map[string]bool
14
-}
15
-
16
-// KeyValuePair are used to initialize a new Args
17
-type KeyValuePair struct {
18
-	Key   string
19
-	Value string
20
-}
21
-
22
-// Arg creates a new KeyValuePair for initializing Args
23
-func Arg(key, value string) KeyValuePair {
24
-	return KeyValuePair{Key: key, Value: value}
25
-}
26
-
27
-// NewArgs returns a new Args populated with the initial args
28
-func NewArgs(initialArgs ...KeyValuePair) Args {
29
-	args := Args{fields: map[string]map[string]bool{}}
30
-	for _, arg := range initialArgs {
31
-		args.Add(arg.Key, arg.Value)
32
-	}
33
-	return args
34
-}
35
-
36
-// MarshalJSON returns a JSON byte representation of the Args
37
-func (args Args) MarshalJSON() ([]byte, error) {
38
-	if len(args.fields) == 0 {
39
-		return []byte("{}"), nil
40
-	}
41
-	return json.Marshal(args.fields)
42
-}
43
-
44
-// ToJSON returns the Args as a JSON encoded string
45
-func ToJSON(a Args) (string, error) {
46
-	if a.Len() == 0 {
47
-		return "", nil
48
-	}
49
-	buf, err := json.Marshal(a)
50
-	return string(buf), err
51
-}
52
-
53
-// Add a new value to the set of values
54
-func (args Args) Add(key, value string) {
55
-	if _, ok := args.fields[key]; ok {
56
-		args.fields[key][value] = true
57
-	} else {
58
-		args.fields[key] = map[string]bool{value: true}
59
-	}
60
-}
61
-
62
-// Len returns the number of keys in the mapping
63
-func (args Args) Len() int {
64
-	return len(args.fields)
65
-}
... ...
@@ -8,7 +8,6 @@ import (
8 8
 	"strconv"
9 9
 
10 10
 	"github.com/moby/moby/api/types/build"
11
-	"github.com/moby/moby/api/types/filters"
12 11
 	"github.com/moby/moby/api/types/versions"
13 12
 )
14 13
 
... ...
@@ -18,7 +17,7 @@ type BuildCachePruneOptions struct {
18 18
 	ReservedSpace int64
19 19
 	MaxUsedSpace  int64
20 20
 	MinFreeSpace  int64
21
-	Filters       filters.Args
21
+	Filters       Filters
22 22
 }
23 23
 
24 24
 // BuildCachePruneResult holds the result from the BuildCachePrune method.
... ...
@@ -49,11 +48,7 @@ func (cli *Client) BuildCachePrune(ctx context.Context, opts BuildCachePruneOpti
49 49
 	if opts.MinFreeSpace != 0 {
50 50
 		query.Set("min-free-space", strconv.Itoa(int(opts.MinFreeSpace)))
51 51
 	}
52
-	f, err := filters.ToJSON(opts.Filters)
53
-	if err != nil {
54
-		return BuildCachePruneResult{}, fmt.Errorf("prune could not marshal filters option: %w", err)
55
-	}
56
-	query.Set("filters", f)
52
+	opts.Filters.updateURLValues(query)
57 53
 
58 54
 	resp, err := cli.post(ctx, "/build/prune", query, nil, nil)
59 55
 	defer ensureReaderClosed(resp)
... ...
@@ -8,7 +8,6 @@ import (
8 8
 	"github.com/moby/moby/api/types"
9 9
 	"github.com/moby/moby/api/types/container"
10 10
 	"github.com/moby/moby/api/types/events"
11
-	"github.com/moby/moby/api/types/filters"
12 11
 	"github.com/moby/moby/api/types/image"
13 12
 	"github.com/moby/moby/api/types/network"
14 13
 	"github.com/moby/moby/api/types/plugin"
... ...
@@ -89,7 +88,7 @@ type ContainerAPIClient interface {
89 89
 	ContainerWait(ctx context.Context, container string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error)
90 90
 	CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, container.PathStat, error)
91 91
 	CopyToContainer(ctx context.Context, container, path string, content io.Reader, options CopyToContainerOptions) error
92
-	ContainersPrune(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error)
92
+	ContainersPrune(ctx context.Context, pruneFilters Filters) (container.PruneReport, error)
93 93
 }
94 94
 
95 95
 type ExecAPIClient interface {
... ...
@@ -119,7 +118,7 @@ type ImageAPIClient interface {
119 119
 	ImageRemove(ctx context.Context, image string, options ImageRemoveOptions) ([]image.DeleteResponse, error)
120 120
 	ImageSearch(ctx context.Context, term string, options ImageSearchOptions) ([]registry.SearchResult, error)
121 121
 	ImageTag(ctx context.Context, image, ref string) error
122
-	ImagesPrune(ctx context.Context, pruneFilter filters.Args) (image.PruneReport, error)
122
+	ImagesPrune(ctx context.Context, pruneFilter Filters) (image.PruneReport, error)
123 123
 
124 124
 	ImageInspect(ctx context.Context, image string, _ ...ImageInspectOption) (image.InspectResponse, error)
125 125
 	ImageHistory(ctx context.Context, image string, _ ...ImageHistoryOption) ([]image.HistoryResponseItem, error)
... ...
@@ -136,7 +135,7 @@ type NetworkAPIClient interface {
136 136
 	NetworkInspectWithRaw(ctx context.Context, network string, options NetworkInspectOptions) (network.Inspect, []byte, error)
137 137
 	NetworkList(ctx context.Context, options NetworkListOptions) ([]network.Summary, error)
138 138
 	NetworkRemove(ctx context.Context, network string) error
139
-	NetworksPrune(ctx context.Context, pruneFilter filters.Args) (network.PruneReport, error)
139
+	NetworksPrune(ctx context.Context, pruneFilter Filters) (network.PruneReport, error)
140 140
 }
141 141
 
142 142
 // NodeAPIClient defines API client methods for the nodes
... ...
@@ -149,7 +148,7 @@ type NodeAPIClient interface {
149 149
 
150 150
 // PluginAPIClient defines API client methods for the plugins
151 151
 type PluginAPIClient interface {
152
-	PluginList(ctx context.Context, filter filters.Args) (plugin.ListResponse, error)
152
+	PluginList(ctx context.Context, filter Filters) (plugin.ListResponse, error)
153 153
 	PluginRemove(ctx context.Context, name string, options PluginRemoveOptions) error
154 154
 	PluginEnable(ctx context.Context, name string, options PluginEnableOptions) error
155 155
 	PluginDisable(ctx context.Context, name string, options PluginDisableOptions) error
... ...
@@ -201,7 +200,7 @@ type VolumeAPIClient interface {
201 201
 	VolumeInspectWithRaw(ctx context.Context, volumeID string) (volume.Volume, []byte, error)
202 202
 	VolumeList(ctx context.Context, options VolumeListOptions) (volume.ListResponse, error)
203 203
 	VolumeRemove(ctx context.Context, volumeID string, force bool) error
204
-	VolumesPrune(ctx context.Context, pruneFilter filters.Args) (volume.PruneReport, error)
204
+	VolumesPrune(ctx context.Context, pruneFilter Filters) (volume.PruneReport, error)
205 205
 	VolumeUpdate(ctx context.Context, volumeID string, version swarm.Version, options volume.UpdateOptions) error
206 206
 }
207 207
 
... ...
@@ -5,22 +5,13 @@ import (
5 5
 	"encoding/json"
6 6
 	"net/url"
7 7
 
8
-	"github.com/moby/moby/api/types/filters"
9 8
 	"github.com/moby/moby/api/types/swarm"
10 9
 )
11 10
 
12 11
 // ConfigList returns the list of configs.
13 12
 func (cli *Client) ConfigList(ctx context.Context, options ConfigListOptions) ([]swarm.Config, error) {
14 13
 	query := url.Values{}
15
-
16
-	if options.Filters.Len() > 0 {
17
-		filterJSON, err := filters.ToJSON(options.Filters)
18
-		if err != nil {
19
-			return nil, err
20
-		}
21
-
22
-		query.Set("filters", filterJSON)
23
-	}
14
+	options.Filters.updateURLValues(query)
24 15
 
25 16
 	resp, err := cli.get(ctx, "/configs", query, nil)
26 17
 	defer ensureReaderClosed(resp)
... ...
@@ -7,7 +7,6 @@ import (
7 7
 	"strconv"
8 8
 
9 9
 	"github.com/moby/moby/api/types/container"
10
-	"github.com/moby/moby/api/types/filters"
11 10
 )
12 11
 
13 12
 // ContainerListOptions holds parameters to list containers with.
... ...
@@ -18,7 +17,7 @@ type ContainerListOptions struct {
18 18
 	Since   string
19 19
 	Before  string
20 20
 	Limit   int
21
-	Filters filters.Args
21
+	Filters Filters
22 22
 }
23 23
 
24 24
 // ContainerList returns the list of containers in the docker host.
... ...
@@ -45,13 +44,7 @@ func (cli *Client) ContainerList(ctx context.Context, options ContainerListOptio
45 45
 		query.Set("size", "1")
46 46
 	}
47 47
 
48
-	if options.Filters.Len() > 0 {
49
-		filterJSON, err := filters.ToJSON(options.Filters)
50
-		if err != nil {
51
-			return nil, err
52
-		}
53
-		query.Set("filters", filterJSON)
54
-	}
48
+	options.Filters.updateURLValues(query)
55 49
 
56 50
 	resp, err := cli.get(ctx, "/containers/json", query, nil)
57 51
 	defer ensureReaderClosed(resp)
... ...
@@ -4,17 +4,15 @@ import (
4 4
 	"context"
5 5
 	"encoding/json"
6 6
 	"fmt"
7
+	"net/url"
7 8
 
8 9
 	"github.com/moby/moby/api/types/container"
9
-	"github.com/moby/moby/api/types/filters"
10 10
 )
11 11
 
12 12
 // ContainersPrune requests the daemon to delete unused data
13
-func (cli *Client) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (container.PruneReport, error) {
14
-	query, err := getFiltersQuery(pruneFilters)
15
-	if err != nil {
16
-		return container.PruneReport{}, err
17
-	}
13
+func (cli *Client) ContainersPrune(ctx context.Context, pruneFilters Filters) (container.PruneReport, error) {
14
+	query := url.Values{}
15
+	pruneFilters.updateURLValues(query)
18 16
 
19 17
 	resp, err := cli.post(ctx, "/containers/prune", query, nil, nil)
20 18
 	defer ensureReaderClosed(resp)
21 19
new file mode 100644
... ...
@@ -0,0 +1,46 @@
0
+package client
1
+
2
+import (
3
+	"encoding/json"
4
+	"net/url"
5
+)
6
+
7
+// Filters describes a predicate for an API request.
8
+//
9
+// Each entry in the map is a filter term.
10
+// Each term is evaluated against the set of values.
11
+// A filter term is satisfied if any one of the values in the set is a match.
12
+// An item matches the filters when all terms are satisfied.
13
+//
14
+// Like all other map types in Go, the zero value is empty and read-only.
15
+type Filters map[string]map[string]bool
16
+
17
+// Add appends values to the value-set of term.
18
+//
19
+// The receiver f is returned for chaining.
20
+//
21
+//	f := make(Filters).Add("name", "foo", "bar").Add("status", "exited")
22
+func (f Filters) Add(term string, values ...string) Filters {
23
+	if _, ok := f[term]; !ok {
24
+		f[term] = make(map[string]bool)
25
+	}
26
+	for _, v := range values {
27
+		f[term][v] = true
28
+	}
29
+	return f
30
+}
31
+
32
+// updateURLValues sets the "filters" key in values to the marshalled value of
33
+// f, replacing any existing values. When f is empty, any existing "filters" key
34
+// is removed.
35
+func (f Filters) updateURLValues(values url.Values) {
36
+	if len(f) > 0 {
37
+		b, err := json.Marshal(f)
38
+		if err != nil {
39
+			panic(err) // Marshaling builtin types should never fail
40
+		}
41
+		values.Set("filters", string(b))
42
+	} else {
43
+		values.Del("filters")
44
+	}
45
+}
... ...
@@ -5,7 +5,6 @@ import (
5 5
 	"encoding/json"
6 6
 	"net/url"
7 7
 
8
-	"github.com/moby/moby/api/types/filters"
9 8
 	"github.com/moby/moby/api/types/image"
10 9
 	"github.com/moby/moby/api/types/versions"
11 10
 )
... ...
@@ -30,13 +29,7 @@ func (cli *Client) ImageList(ctx context.Context, options ImageListOptions) ([]i
30 30
 
31 31
 	query := url.Values{}
32 32
 
33
-	if options.Filters.Len() > 0 {
34
-		filterJSON, err := filters.ToJSON(options.Filters)
35
-		if err != nil {
36
-			return images, err
37
-		}
38
-		query.Set("filters", filterJSON)
39
-	}
33
+	options.Filters.updateURLValues(query)
40 34
 	if options.All {
41 35
 		query.Set("all", "1")
42 36
 	}
... ...
@@ -1,7 +1,5 @@
1 1
 package client
2 2
 
3
-import "github.com/moby/moby/api/types/filters"
4
-
5 3
 // ImageListOptions holds parameters to list images with.
6 4
 type ImageListOptions struct {
7 5
 	// All controls whether all images in the graph are filtered, or just
... ...
@@ -9,7 +7,7 @@ type ImageListOptions struct {
9 9
 	All bool
10 10
 
11 11
 	// Filters is a JSON-encoded set of filter arguments.
12
-	Filters filters.Args
12
+	Filters Filters
13 13
 
14 14
 	// SharedSize indicates whether the shared size of images should be computed.
15 15
 	SharedSize bool
... ...
@@ -4,17 +4,15 @@ import (
4 4
 	"context"
5 5
 	"encoding/json"
6 6
 	"fmt"
7
+	"net/url"
7 8
 
8
-	"github.com/moby/moby/api/types/filters"
9 9
 	"github.com/moby/moby/api/types/image"
10 10
 )
11 11
 
12 12
 // ImagesPrune requests the daemon to delete unused data
13
-func (cli *Client) ImagesPrune(ctx context.Context, pruneFilters filters.Args) (image.PruneReport, error) {
14
-	query, err := getFiltersQuery(pruneFilters)
15
-	if err != nil {
16
-		return image.PruneReport{}, err
17
-	}
13
+func (cli *Client) ImagesPrune(ctx context.Context, pruneFilters Filters) (image.PruneReport, error) {
14
+	query := url.Values{}
15
+	pruneFilters.updateURLValues(query)
18 16
 
19 17
 	resp, err := cli.post(ctx, "/images/prune", query, nil, nil)
20 18
 	defer ensureReaderClosed(resp)
... ...
@@ -8,7 +8,6 @@ import (
8 8
 	"strconv"
9 9
 
10 10
 	cerrdefs "github.com/containerd/errdefs"
11
-	"github.com/moby/moby/api/types/filters"
12 11
 	"github.com/moby/moby/api/types/registry"
13 12
 )
14 13
 
... ...
@@ -22,13 +21,7 @@ func (cli *Client) ImageSearch(ctx context.Context, term string, options ImageSe
22 22
 		query.Set("limit", strconv.Itoa(options.Limit))
23 23
 	}
24 24
 
25
-	if options.Filters.Len() > 0 {
26
-		filterJSON, err := filters.ToJSON(options.Filters)
27
-		if err != nil {
28
-			return results, err
29
-		}
30
-		query.Set("filters", filterJSON)
31
-	}
25
+	options.Filters.updateURLValues(query)
32 26
 
33 27
 	resp, err := cli.tryImageSearch(ctx, query, options.RegistryAuth)
34 28
 	defer ensureReaderClosed(resp)
... ...
@@ -2,8 +2,6 @@ package client
2 2
 
3 3
 import (
4 4
 	"context"
5
-
6
-	"github.com/moby/moby/api/types/filters"
7 5
 )
8 6
 
9 7
 // ImageSearchOptions holds parameters to search images with.
... ...
@@ -17,6 +15,6 @@ type ImageSearchOptions struct {
17 17
 	//
18 18
 	// For details, refer to [github.com/moby/moby/api/types/registry.RequestAuthConfig].
19 19
 	PrivilegeFunc func(context.Context) (string, error)
20
-	Filters       filters.Args
20
+	Filters       Filters
21 21
 	Limit         int
22 22
 }
... ...
@@ -5,20 +5,13 @@ import (
5 5
 	"encoding/json"
6 6
 	"net/url"
7 7
 
8
-	"github.com/moby/moby/api/types/filters"
9 8
 	"github.com/moby/moby/api/types/network"
10 9
 )
11 10
 
12 11
 // NetworkList returns the list of networks configured in the docker host.
13 12
 func (cli *Client) NetworkList(ctx context.Context, options NetworkListOptions) ([]network.Summary, error) {
14 13
 	query := url.Values{}
15
-	if options.Filters.Len() > 0 {
16
-		filterJSON, err := filters.ToJSON(options.Filters)
17
-		if err != nil {
18
-			return nil, err
19
-		}
20
-		query.Set("filters", filterJSON)
21
-	}
14
+	options.Filters.updateURLValues(query)
22 15
 	var networkResources []network.Summary
23 16
 	resp, err := cli.get(ctx, "/networks", query, nil)
24 17
 	defer ensureReaderClosed(resp)
... ...
@@ -1,8 +1,6 @@
1 1
 package client
2 2
 
3
-import "github.com/moby/moby/api/types/filters"
4
-
5 3
 // NetworkListOptions holds parameters to filter the list of networks with.
6 4
 type NetworkListOptions struct {
7
-	Filters filters.Args
5
+	Filters Filters
8 6
 }
... ...
@@ -4,17 +4,15 @@ import (
4 4
 	"context"
5 5
 	"encoding/json"
6 6
 	"fmt"
7
+	"net/url"
7 8
 
8
-	"github.com/moby/moby/api/types/filters"
9 9
 	"github.com/moby/moby/api/types/network"
10 10
 )
11 11
 
12 12
 // NetworksPrune requests the daemon to delete unused networks
13
-func (cli *Client) NetworksPrune(ctx context.Context, pruneFilters filters.Args) (network.PruneReport, error) {
14
-	query, err := getFiltersQuery(pruneFilters)
15
-	if err != nil {
16
-		return network.PruneReport{}, err
17
-	}
13
+func (cli *Client) NetworksPrune(ctx context.Context, pruneFilters Filters) (network.PruneReport, error) {
14
+	query := url.Values{}
15
+	pruneFilters.updateURLValues(query)
18 16
 
19 17
 	resp, err := cli.post(ctx, "/networks/prune", query, nil, nil)
20 18
 	defer ensureReaderClosed(resp)
... ...
@@ -5,23 +5,13 @@ import (
5 5
 	"encoding/json"
6 6
 	"net/url"
7 7
 
8
-	"github.com/moby/moby/api/types/filters"
9 8
 	"github.com/moby/moby/api/types/swarm"
10 9
 )
11 10
 
12 11
 // NodeList returns the list of nodes.
13 12
 func (cli *Client) NodeList(ctx context.Context, options NodeListOptions) ([]swarm.Node, error) {
14 13
 	query := url.Values{}
15
-
16
-	if options.Filters.Len() > 0 {
17
-		filterJSON, err := filters.ToJSON(options.Filters)
18
-		if err != nil {
19
-			return nil, err
20
-		}
21
-
22
-		query.Set("filters", filterJSON)
23
-	}
24
-
14
+	options.Filters.updateURLValues(query)
25 15
 	resp, err := cli.get(ctx, "/nodes", query, nil)
26 16
 	defer ensureReaderClosed(resp)
27 17
 	if err != nil {
... ...
@@ -5,22 +5,15 @@ import (
5 5
 	"encoding/json"
6 6
 	"net/url"
7 7
 
8
-	"github.com/moby/moby/api/types/filters"
9 8
 	"github.com/moby/moby/api/types/plugin"
10 9
 )
11 10
 
12 11
 // PluginList returns the installed plugins
13
-func (cli *Client) PluginList(ctx context.Context, filter filters.Args) (plugin.ListResponse, error) {
12
+func (cli *Client) PluginList(ctx context.Context, filter Filters) (plugin.ListResponse, error) {
14 13
 	var plugins plugin.ListResponse
15 14
 	query := url.Values{}
16 15
 
17
-	if filter.Len() > 0 {
18
-		filterJSON, err := filters.ToJSON(filter)
19
-		if err != nil {
20
-			return plugins, err
21
-		}
22
-		query.Set("filters", filterJSON)
23
-	}
16
+	filter.updateURLValues(query)
24 17
 	resp, err := cli.get(ctx, "/plugins", query, nil)
25 18
 	defer ensureReaderClosed(resp)
26 19
 	if err != nil {
... ...
@@ -5,7 +5,6 @@ import (
5 5
 	"encoding/json"
6 6
 	"net/url"
7 7
 
8
-	"github.com/moby/moby/api/types/filters"
9 8
 	"github.com/moby/moby/api/types/swarm"
10 9
 )
11 10
 
... ...
@@ -13,15 +12,7 @@ import (
13 13
 func (cli *Client) SecretList(ctx context.Context, options SecretListOptions) ([]swarm.Secret, error) {
14 14
 	query := url.Values{}
15 15
 
16
-	if options.Filters.Len() > 0 {
17
-		filterJSON, err := filters.ToJSON(options.Filters)
18
-		if err != nil {
19
-			return nil, err
20
-		}
21
-
22
-		query.Set("filters", filterJSON)
23
-	}
24
-
16
+	options.Filters.updateURLValues(query)
25 17
 	resp, err := cli.get(ctx, "/secrets", query, nil)
26 18
 	defer ensureReaderClosed(resp)
27 19
 	if err != nil {
... ...
@@ -1,8 +1,6 @@
1 1
 package client
2 2
 
3
-import "github.com/moby/moby/api/types/filters"
4
-
5 3
 // SecretListOptions holds parameters to list secrets
6 4
 type SecretListOptions struct {
7
-	Filters filters.Args
5
+	Filters Filters
8 6
 }
... ...
@@ -5,7 +5,6 @@ import (
5 5
 	"encoding/json"
6 6
 	"net/url"
7 7
 
8
-	"github.com/moby/moby/api/types/filters"
9 8
 	"github.com/moby/moby/api/types/swarm"
10 9
 )
11 10
 
... ...
@@ -13,14 +12,7 @@ import (
13 13
 func (cli *Client) ServiceList(ctx context.Context, options ServiceListOptions) ([]swarm.Service, error) {
14 14
 	query := url.Values{}
15 15
 
16
-	if options.Filters.Len() > 0 {
17
-		filterJSON, err := filters.ToJSON(options.Filters)
18
-		if err != nil {
19
-			return nil, err
20
-		}
21
-
22
-		query.Set("filters", filterJSON)
23
-	}
16
+	options.Filters.updateURLValues(query)
24 17
 
25 18
 	if options.Status {
26 19
 		query.Set("status", "true")
... ...
@@ -1,8 +1,6 @@
1 1
 package client
2 2
 
3
-import "github.com/moby/moby/api/types/filters"
4
-
5 3
 // ConfigListOptions holds parameters to list configs
6 4
 type ConfigListOptions struct {
7
-	Filters filters.Args
5
+	Filters Filters
8 6
 }
... ...
@@ -1,8 +1,6 @@
1 1
 package client
2 2
 
3
-import "github.com/moby/moby/api/types/filters"
4
-
5 3
 // NodeListOptions holds parameters to list nodes with.
6 4
 type NodeListOptions struct {
7
-	Filters filters.Args
5
+	Filters Filters
8 6
 }
... ...
@@ -1,10 +1,8 @@
1 1
 package client
2 2
 
3
-import "github.com/moby/moby/api/types/filters"
4
-
5 3
 // ServiceListOptions holds parameters to list services with.
6 4
 type ServiceListOptions struct {
7
-	Filters filters.Args
5
+	Filters Filters
8 6
 
9 7
 	// Status indicates whether the server should include the service task
10 8
 	// count of running and desired tasks.
... ...
@@ -1,8 +1,6 @@
1 1
 package client
2 2
 
3
-import "github.com/moby/moby/api/types/filters"
4
-
5 3
 // TaskListOptions holds parameters to list tasks with.
6 4
 type TaskListOptions struct {
7
-	Filters filters.Args
5
+	Filters Filters
8 6
 }
... ...
@@ -8,7 +8,6 @@ import (
8 8
 
9 9
 	"github.com/moby/moby/api/types"
10 10
 	"github.com/moby/moby/api/types/events"
11
-	"github.com/moby/moby/api/types/filters"
12 11
 	"github.com/moby/moby/client/internal"
13 12
 	"github.com/moby/moby/client/internal/timestamp"
14 13
 )
... ...
@@ -17,7 +16,7 @@ import (
17 17
 type EventsListOptions struct {
18 18
 	Since   string
19 19
 	Until   string
20
-	Filters filters.Args
20
+	Filters Filters
21 21
 }
22 22
 
23 23
 // Events returns a stream of events in the daemon. It's up to the caller to close the stream
... ...
@@ -100,13 +99,7 @@ func buildEventsQueryParams(options EventsListOptions) (url.Values, error) {
100 100
 		query.Set("until", ts)
101 101
 	}
102 102
 
103
-	if options.Filters.Len() > 0 {
104
-		filterJSON, err := filters.ToJSON(options.Filters)
105
-		if err != nil {
106
-			return nil, err
107
-		}
108
-		query.Set("filters", filterJSON)
109
-	}
103
+	options.Filters.updateURLValues(query)
110 104
 
111 105
 	return query, nil
112 106
 }
... ...
@@ -5,7 +5,6 @@ import (
5 5
 	"encoding/json"
6 6
 	"net/url"
7 7
 
8
-	"github.com/moby/moby/api/types/filters"
9 8
 	"github.com/moby/moby/api/types/swarm"
10 9
 )
11 10
 
... ...
@@ -13,14 +12,7 @@ import (
13 13
 func (cli *Client) TaskList(ctx context.Context, options TaskListOptions) ([]swarm.Task, error) {
14 14
 	query := url.Values{}
15 15
 
16
-	if options.Filters.Len() > 0 {
17
-		filterJSON, err := filters.ToJSON(options.Filters)
18
-		if err != nil {
19
-			return nil, err
20
-		}
21
-
22
-		query.Set("filters", filterJSON)
23
-	}
16
+	options.Filters.updateURLValues(query)
24 17
 
25 18
 	resp, err := cli.get(ctx, "/tasks", query, nil)
26 19
 	defer ensureReaderClosed(resp)
... ...
@@ -3,11 +3,9 @@ package client
3 3
 import (
4 4
 	"encoding/json"
5 5
 	"fmt"
6
-	"net/url"
7 6
 	"strings"
8 7
 
9 8
 	cerrdefs "github.com/containerd/errdefs"
10
-	"github.com/moby/moby/api/types/filters"
11 9
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
12 10
 )
13 11
 
... ...
@@ -28,20 +26,6 @@ func trimID(objType, id string) (string, error) {
28 28
 	return id, nil
29 29
 }
30 30
 
31
-// getFiltersQuery returns a url query with "filters" query term, based on the
32
-// filters provided.
33
-func getFiltersQuery(f filters.Args) (url.Values, error) {
34
-	query := url.Values{}
35
-	if f.Len() > 0 {
36
-		filterJSON, err := filters.ToJSON(f)
37
-		if err != nil {
38
-			return query, err
39
-		}
40
-		query.Set("filters", filterJSON)
41
-	}
42
-	return query, nil
43
-}
44
-
45 31
 // encodePlatforms marshals the given platform(s) to JSON format, to
46 32
 // be used for query-parameters for filtering / selecting platforms.
47 33
 func encodePlatforms(platform ...ocispec.Platform) ([]string, error) {
... ...
@@ -5,7 +5,6 @@ import (
5 5
 	"encoding/json"
6 6
 	"net/url"
7 7
 
8
-	"github.com/moby/moby/api/types/filters"
9 8
 	"github.com/moby/moby/api/types/volume"
10 9
 )
11 10
 
... ...
@@ -13,13 +12,7 @@ import (
13 13
 func (cli *Client) VolumeList(ctx context.Context, options VolumeListOptions) (volume.ListResponse, error) {
14 14
 	query := url.Values{}
15 15
 
16
-	if options.Filters.Len() > 0 {
17
-		filterJSON, err := filters.ToJSON(options.Filters)
18
-		if err != nil {
19
-			return volume.ListResponse{}, err
20
-		}
21
-		query.Set("filters", filterJSON)
22
-	}
16
+	options.Filters.updateURLValues(query)
23 17
 	resp, err := cli.get(ctx, "/volumes", query, nil)
24 18
 	defer ensureReaderClosed(resp)
25 19
 	if err != nil {
... ...
@@ -1,8 +1,6 @@
1 1
 package client
2 2
 
3
-import "github.com/moby/moby/api/types/filters"
4
-
5 3
 // VolumeListOptions holds parameters to list volumes.
6 4
 type VolumeListOptions struct {
7
-	Filters filters.Args
5
+	Filters Filters
8 6
 }
... ...
@@ -4,17 +4,15 @@ import (
4 4
 	"context"
5 5
 	"encoding/json"
6 6
 	"fmt"
7
+	"net/url"
7 8
 
8
-	"github.com/moby/moby/api/types/filters"
9 9
 	"github.com/moby/moby/api/types/volume"
10 10
 )
11 11
 
12 12
 // VolumesPrune requests the daemon to delete unused data
13
-func (cli *Client) VolumesPrune(ctx context.Context, pruneFilters filters.Args) (volume.PruneReport, error) {
14
-	query, err := getFiltersQuery(pruneFilters)
15
-	if err != nil {
16
-		return volume.PruneReport{}, err
17
-	}
13
+func (cli *Client) VolumesPrune(ctx context.Context, pruneFilters Filters) (volume.PruneReport, error) {
14
+	query := url.Values{}
15
+	pruneFilters.updateURLValues(query)
18 16
 
19 17
 	resp, err := cli.post(ctx, "/volumes/prune", query, nil, nil)
20 18
 	defer ensureReaderClosed(resp)
... ...
@@ -951,7 +951,6 @@ github.com/moby/moby/api/types/checkpoint
951 951
 github.com/moby/moby/api/types/common
952 952
 github.com/moby/moby/api/types/container
953 953
 github.com/moby/moby/api/types/events
954
-github.com/moby/moby/api/types/filters
955 954
 github.com/moby/moby/api/types/image
956 955
 github.com/moby/moby/api/types/jsonstream
957 956
 github.com/moby/moby/api/types/mount