1/**
2 * Copyright 2016 IBM Corp.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *    http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17// See reference at https://sldn.softlayer.com/article/object-filters.
18// Examples in the README.md file and in the examples directory.
19package filter
20
21import (
22	"encoding/json"
23	"fmt"
24	"strings"
25)
26
27type Filter struct {
28	Path string
29	Op   string
30	Opts map[string]interface{}
31	Val  interface{}
32}
33
34type Filters []Filter
35
36// Returns an array of Filters that you can later call .Build() on.
37func New(args ...Filter) Filters {
38	return args
39}
40
41// This is like calling New().Build().
42// Returns a JSON string that can be used as the object filter.
43func Build(args ...Filter) string {
44	filters := Filters{}
45
46	for _, arg := range args {
47		filters = append(filters, arg)
48	}
49
50	return filters.Build()
51}
52
53// This creates a new Filter. The path is a dot-delimited path down
54// to the attribute this filter is for. The second value parameter
55// is optional.
56func Path(path string, val ...interface{}) Filter {
57	if len(val) > 0 {
58		return Filter{Path: path, Val: val[0]}
59	}
60
61	return Filter{Path: path}
62}
63
64// Builds the filter string in JSON format
65func (fs Filters) Build() string {
66	// Loops around filters,
67	// splitting path on '.' and looping around path pieces.
68	// Idea is to create a map/tree like map[string]interface{}.
69	// Every component in the path is a node to create in the tree.
70	// Once we get to the leaf, we set the operation.
71	// map[string]interface{}{"operation": op+" "+value}
72	// If Op is "", then just map[string]interface{}{"operation": value}.
73	// Afterwards, the Opts are traversed; []map[string]interface{}{}
74	// For every entry in Opts, we create one map, and append it to an array of maps.
75	// At the end, json.Marshal the whole thing.
76	result := map[string]interface{}{}
77	for _, filter := range fs {
78		if filter.Path == "" {
79			continue
80		}
81
82		cursor := result
83		nodes := strings.Split(filter.Path, ".")
84		for len(nodes) > 1 {
85			branch := nodes[0]
86			if _, ok := cursor[branch]; !ok {
87				cursor[branch] = map[string]interface{}{}
88			}
89			cursor = cursor[branch].(map[string]interface{})
90			nodes = nodes[1:len(nodes)]
91		}
92
93		leaf := nodes[0]
94		if filter.Val != nil {
95			operation := filter.Val
96			if filter.Op != "" {
97				var format string
98				switch filter.Val.(type) {
99				case int:
100					format = "%d"
101				default:
102					format = "%s"
103				}
104				operation = filter.Op + " " + fmt.Sprintf(format, filter.Val)
105			}
106
107			cursor[leaf] = map[string]interface{}{
108				"operation": operation,
109			}
110		}
111
112		if filter.Opts == nil {
113			continue
114		}
115
116		options := []map[string]interface{}{}
117		for name, value := range filter.Opts {
118			options = append(options, map[string]interface{}{
119				"name":  name,
120				"value": value,
121			})
122		}
123
124		cursor[leaf] = map[string]interface{}{
125			"operation": filter.Op,
126			"options":   options,
127		}
128	}
129
130	jsonStr, _ := json.Marshal(result)
131	return string(jsonStr)
132}
133
134// Builds the filter string in JSON format
135func (f Filter) Build() string {
136	return Build(f)
137}
138
139// Add options to the filter. Can be chained for multiple options.
140func (f Filter) Opt(name string, value interface{}) Filter {
141	if f.Opts == nil {
142		f.Opts = map[string]interface{}{}
143	}
144
145	f.Opts[name] = value
146	return f
147}
148
149// Set this filter to test if property is equal to the value
150func (f Filter) Eq(val interface{}) Filter {
151	f.Op = ""
152	f.Val = val
153	return f
154}
155
156// Set this filter to test if property is not equal to the value
157func (f Filter) NotEq(val interface{}) Filter {
158	f.Op = "!="
159	f.Val = val
160	return f
161}
162
163// Set this filter to test if property is like the value
164func (f Filter) Like(val interface{}) Filter {
165	f.Op = "~"
166	f.Val = val
167	return f
168}
169
170// Set this filter to test if property is unlike value
171func (f Filter) NotLike(val interface{}) Filter {
172	f.Op = "!~"
173	f.Val = val
174	return f
175}
176
177// Set this filter to test if property is less than value
178func (f Filter) LessThan(val interface{}) Filter {
179	f.Op = "<"
180	f.Val = val
181	return f
182}
183
184// Set this filter to test if property is less than or equal to the value
185func (f Filter) LessThanOrEqual(val interface{}) Filter {
186	f.Op = "<="
187	f.Val = val
188	return f
189}
190
191// Set this filter to test if property is greater than value
192func (f Filter) GreaterThan(val interface{}) Filter {
193	f.Op = ">"
194	f.Val = val
195	return f
196}
197
198// Set this filter to test if property is greater than or equal to value
199func (f Filter) GreaterThanOrEqual(val interface{}) Filter {
200	f.Op = ">="
201	f.Val = val
202	return f
203}
204
205// Set this filter to test if property is null
206func (f Filter) IsNull() Filter {
207	f.Op = ""
208	f.Val = "is null"
209	return f
210}
211
212// Set this filter to test if property is not null
213func (f Filter) NotNull() Filter {
214	f.Op = ""
215	f.Val = "not null"
216	return f
217}
218
219// Set this filter to test if property contains the value
220func (f Filter) Contains(val interface{}) Filter {
221	f.Op = "*="
222	f.Val = val
223	return f
224}
225
226// Set this filter to test if property does not contain the value
227func (f Filter) NotContains(val interface{}) Filter {
228	f.Op = "!*="
229	f.Val = val
230	return f
231}
232
233// Set this filter to test if property starts with the value
234func (f Filter) StartsWith(val interface{}) Filter {
235	f.Op = "^="
236	f.Val = val
237	return f
238}
239
240// Set this filter to test if property does not start with the value
241func (f Filter) NotStartsWith(val interface{}) Filter {
242	f.Op = "!^="
243	f.Val = val
244	return f
245}
246
247// Set this filter to test if property ends with the value
248func (f Filter) EndsWith(val interface{}) Filter {
249	f.Op = "$="
250	f.Val = val
251	return f
252}
253
254// Set this filter to test if property does not end with the value
255func (f Filter) NotEndsWith(val interface{}) Filter {
256	f.Op = "!$="
257	f.Val = val
258	return f
259}
260
261// Set this filter to test if property is one of the values in args.
262func (f Filter) In(args ...interface{}) Filter {
263	f.Op = "in"
264	values := []interface{}{}
265	for _, arg := range args {
266		values = append(values, arg)
267	}
268
269	return f.Opt("data", values)
270}
271
272// Set this filter to test if property has a date older than the value in days.
273func (f Filter) DaysPast(val interface{}) Filter {
274	f.Op = ">= currentDate -"
275	f.Val = val
276	return f
277}
278
279// Set this filter to test if property has the exact date as the value.
280func (f Filter) Date(date string) Filter {
281	f.Op = "isDate"
282	f.Val = nil
283	return f.Opt("date", []string{date})
284}
285
286// Set this filter to test if property has a date before the value.
287func (f Filter) DateBefore(date string) Filter {
288	f.Op = "lessThanDate"
289	f.Val = nil
290	return f.Opt("date", []string{date})
291}
292
293// Set this filter to test if property has a date after the value.
294func (f Filter) DateAfter(date string) Filter {
295	f.Op = "greaterThanDate"
296	f.Val = nil
297	return f.Opt("date", []string{date})
298}
299
300// Set this filter to test if property has a date between the values.
301func (f Filter) DateBetween(start string, end string) Filter {
302	f.Op = "betweenDate"
303	f.Val = nil
304	return f.Opt("startDate", []string{start}).Opt("endDate", []string{end})
305}
306