Repository navigation
Expand file tree
/
Copy pathopen-predicate-schema.json
More file actions
395 lines (371 loc) · 18.1 KB
/
Copy pathopen-predicate-schema.json
File metadata and controls
395 lines (371 loc) · 18.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://openpredicate.tech/schema/v0.4.0/open-predicate-schema.json",
"title": "OpenPredicate — Filter",
"description": "A JSON-encoded, SQL-flavoured predicate language for describing the request body of search operations (POST /…/search and QUERY /…). Everything needed to write a correct filter is described here, operator by operator, so one grammar can be shared by every search endpoint in an API: reference this schema from an OpenAPI document, or inline it as the input schema of a search tool. Normative semantics are defined in SPEC.md.",
"$comment": "Version 0.4.0. See https://github.com/OpenPredicate/open-predicate",
"$ref": "#/$defs/Filter",
"x-profiles": {
"core": ["$and", "$or", "$nor", "$not", "$eq", "$ne", "$in", "$nin", "$gt", "$gte", "$lt", "$lte", "$exists", "$isNull", "$unknownAs"],
"strings": ["$like", "$nlike", "$ilike", "$nilike", "$startsWith", "$endsWith", "$contains"],
"regex": ["$regex", "$flags"],
"ranges": ["$between", "$nbetween"],
"types": ["$type"],
"collections": ["$some", "$every", "$hasAll", "$size"],
"refs": ["$field", "$literal"],
"text": ["$search"]
},
"$defs": {
"Filter": {
"title": "Filter",
"description": "A predicate over a record. Sibling members are combined with implicit AND: every member must evaluate TRUE for the filter to match. Keys beginning with '$' are reserved for logical operators; a field whose real name begins with '$' is escaped by doubling it ('$$price' means the field '$price').",
"type": "object",
"minProperties": 1,
"properties": {
"$and": {
"title": "$and",
"description": "All of the listed filters must evaluate TRUE.",
"type": "array",
"minItems": 1,
"items": { "$ref": "#/$defs/Filter" }
},
"$or": {
"title": "$or",
"description": "At least one of the listed filters must evaluate TRUE.",
"type": "array",
"minItems": 1,
"items": { "$ref": "#/$defs/Filter" }
},
"$nor": {
"title": "$nor",
"description": "NOT (a OR b OR …), evaluated under three-valued logic. It is TRUE only when every listed filter is FALSE: if one of them is UNKNOWN the result is UNKNOWN, not TRUE, so a $nor over a nullable field excludes the records whose field is null or absent. See SPEC.md §4.1.",
"type": "array",
"minItems": 1,
"items": { "$ref": "#/$defs/Filter" }
},
"$not": {
"title": "$not",
"description": "Negates a filter. Under three-valued logic NOT UNKNOWN is UNKNOWN, not TRUE — so this does not match records whose field is null or absent. Add $unknownAs to the inner constraint to include them. See SPEC.md §4.1.",
"$ref": "#/$defs/Filter"
}
},
"patternProperties": {
"^(?:[^$]|\\$\\$)": { "$ref": "#/$defs/Constraint" }
},
"propertyNames": {
"$comment": "Deliberately overlaps with patternProperties/additionalProperties above: those two enforce the default grammar, while this is the single named override point. Narrow #/$defs/FieldPath in a bundled copy of this schema to restrict an endpoint to a fixed field set — the narrowing then applies at every nesting level, because Filter recurses through this same $ref. See README §Restricting the queryable field set.",
"anyOf": [
{ "enum": ["$and", "$or", "$nor", "$not"] },
{ "$ref": "#/$defs/FieldPath" }
]
},
"additionalProperties": false,
"examples": [
{ "status": "open" },
{ "department": { "$eq": "sales" }, "age": { "$gte": 18 } },
{
"$and": [
{ "department": { "$eq": "sales" } },
{ "$or": [{ "role": "admin" }, { "role": "manager" }] }
]
}
]
},
"FieldPath": {
"title": "Field path",
"description": "A dotted path to a member of the record: 'a.b.c'. An array element is addressed by index ('items[0]'); to say something about the elements of an array without naming an index, use $some or $every, which quantify explicitly. A literal '.' inside a key is escaped as '\\.'. The default rule is permissive — any non-empty string not beginning with a single '$' — because the set of queryable paths is a property of the resource, not of the language. Full grammar in SPEC.md §3.",
"type": "string",
"minLength": 1,
"pattern": "^(?:[^$]|\\$\\$)",
"not": {
"$comment": "The '[*]' wildcard was removed in v0.4.0; $some and $every replace it. Rejecting it here turns a stale filter into a validation error instead of a path that would be read as a literal key name.",
"pattern": "\\[\\*\\]"
},
"examples": ["name", "address.city", "items[0].sku", "$$internal"]
},
"Constraint": {
"title": "Constraint",
"description": "The condition applied to one field. Either a scalar shorthand — {\"status\": \"open\"} is exactly {\"status\": {\"$eq\": \"open\"}} — or an object of operators. Arrays and objects are deliberately excluded from the shorthand so that {\"tags\": [\"a\"]} can never be read ambiguously as either $eq or $in.",
"anyOf": [
{ "$ref": "#/$defs/ScalarOrNull" },
{ "$ref": "#/$defs/ConstraintObject" }
]
},
"ConstraintObject": {
"title": "Constraint object",
"description": "One or more operators applied to the same field. Sibling operators are combined with implicit AND, so {\"age\": {\"$gt\": 18, \"$ne\": 30}} is a single well-formed constraint.",
"type": "object",
"minProperties": 1,
"dependentSchemas": {
"$unknownAs": {
"$comment": "$unknownAs is a modifier, not a predicate: it needs at least one operator to modify. Requiring a second member forbids {\"$unknownAs\": true} standing alone.",
"minProperties": 2
}
},
"properties": {
"$eq": {
"title": "$eq",
"description": "Field equals the operand. Any JSON value, including null, arrays and objects. A value of a different JSON type is unequal — FALSE, not UNKNOWN.",
"$ref": "#/$defs/Operand"
},
"$ne": {
"title": "$ne",
"description": "Field does not equal the operand. The negation of $eq under three-valued logic, so this is UNKNOWN — and therefore does not match — when the field is null or absent. Add $unknownAs: true to include those records. See SPEC.md §4.1.",
"$ref": "#/$defs/Operand"
},
"$gt": {
"title": "$gt",
"description": "Field is strictly greater than the operand.",
"$ref": "#/$defs/OrderedOperand"
},
"$gte": {
"title": "$gte",
"description": "Field is greater than or equal to the operand.",
"$ref": "#/$defs/OrderedOperand"
},
"$lt": {
"title": "$lt",
"description": "Field is strictly less than the operand.",
"$ref": "#/$defs/OrderedOperand"
},
"$lte": {
"title": "$lte",
"description": "Field is less than or equal to the operand.",
"$ref": "#/$defs/OrderedOperand"
},
"$between": {
"title": "$between",
"description": "Field falls within [lower, upper], inclusive of both bounds.",
"$ref": "#/$defs/Bounds"
},
"$nbetween": {
"title": "$nbetween",
"description": "Field falls outside [lower, upper]. The negation of $between under three-valued logic, so it is UNKNOWN when the field is null or absent.",
"$ref": "#/$defs/Bounds"
},
"$in": {
"title": "$in",
"description": "Field equals at least one member of the list. Compares the whole value — it does NOT test membership inside an array-valued field: {\"tags\": {\"$in\": [\"a\"]}} asks whether tags equals \"a\". For array elements quantify explicitly: {\"tags\": {\"$some\": {\"$in\": [\"a\"]}}}.",
"$ref": "#/$defs/OperandSet"
},
"$nin": {
"title": "$nin",
"description": "Field equals no member of the list. Like $in, compares the whole value rather than array elements. The negation of $in under three-valued logic, so it is UNKNOWN when the field is null or absent.",
"$ref": "#/$defs/OperandSet"
},
"$like": {
"title": "$like",
"description": "SQL LIKE pattern: '%' matches any run of characters, '_' matches exactly one, and '\\' escapes either. Case-sensitive.",
"type": "string",
"examples": ["%urgent%", "INV-____", "100\\%"]
},
"$nlike": {
"title": "$nlike",
"description": "Negated $like, under three-valued logic: UNKNOWN when the field is null, absent or not a string.",
"type": "string"
},
"$ilike": {
"title": "$ilike",
"description": "Case-insensitive $like. Collation is server-defined; see SPEC.md §5.5.",
"type": "string"
},
"$nilike": {
"title": "$nilike",
"description": "Negated $ilike, under three-valued logic: UNKNOWN when the field is null, absent or not a string.",
"type": "string"
},
"$startsWith": {
"title": "$startsWith",
"description": "Field begins with this literal substring. Wildcards are not interpreted.",
"type": "string"
},
"$endsWith": {
"title": "$endsWith",
"description": "Field ends with this literal substring. Wildcards are not interpreted.",
"type": "string"
},
"$contains": {
"title": "$contains",
"description": "Field contains this literal substring. String-only — for arrays, quantify over the elements with $some or $every.",
"type": "string"
},
"$regex": {
"title": "$regex",
"description": "Field matches this regular expression (ECMA-262 syntax). Servers SHOULD bound execution time; see SPEC.md §7.",
"type": "string",
"format": "regex"
},
"$flags": {
"title": "$flags",
"description": "Modifiers for $regex: 'i' case-insensitive, 'm' multiline, 's' dot-matches-newline. Only valid alongside $regex.",
"type": "string",
"pattern": "^[ims]{0,3}$"
},
"$exists": {
"title": "$exists",
"description": "true if the field is present on the record, even when its value is null; false if absent. Total — never UNKNOWN. Distinct from $isNull — see SPEC.md §4.2.",
"type": "boolean"
},
"$isNull": {
"title": "$isNull",
"description": "true if the field's value is null (equivalent to {\"$eq\": null}); false if it is non-null (equivalent to {\"$ne\": null}). UNKNOWN only when the path resolves to nothing.",
"type": "boolean"
},
"$type": {
"title": "$type",
"description": "Field's JSON type. 'integer' matches a number with no fractional part.",
"type": "string",
"enum": ["string", "number", "integer", "boolean", "object", "array", "null"]
},
"$some": {
"title": "$some",
"description": "At least one element of the array-valued field satisfies this condition. Supply a Filter for arrays of objects (paths are relative to the element) or a constraint object for arrays of scalars. An element for which the condition is UNKNOWN does not satisfy it, so the quantifier yields TRUE or FALSE over any array — FALSE for an empty one. It is UNKNOWN only when the field is not an array, including when it is null or absent. See SPEC.md §5.8.",
"anyOf": [
{ "$ref": "#/$defs/Filter" },
{ "$ref": "#/$defs/ConstraintObject" }
],
"examples": [{ "qty": { "$gt": 2 } }, { "$in": ["urgent", "p1"] }]
},
"$every": {
"title": "$every",
"description": "Every element of the array-valued field satisfies this condition; an element for which it is UNKNOWN does not. TRUE for an empty array, vacuously. UNKNOWN only when the field is not an array, including when it is null or absent — a missing array is not vacuously TRUE. Same operand shape as $some. See SPEC.md §5.8.",
"anyOf": [
{ "$ref": "#/$defs/Filter" },
{ "$ref": "#/$defs/ConstraintObject" }
],
"examples": [{ "status": "settled" }, { "$gte": 0 }]
},
"$hasAll": {
"title": "$hasAll",
"description": "The array-valued field contains every member of this list. Quantifies over the operand rather than over the elements, which is why it is not expressible as a $some or $every. Set semantics: multiplicity is ignored. UNKNOWN when the field is not an array.",
"$ref": "#/$defs/OperandSet"
},
"$size": {
"title": "$size",
"description": "Length of the array-valued field: either an exact count or a nested numeric constraint.",
"$ref": "#/$defs/SizeConstraint"
},
"$search": {
"title": "$search",
"description": "Free-text match against the field. Tokenisation, stemming and relevance are server-defined.",
"type": "string",
"minLength": 1
},
"$not": {
"title": "$not (field-level)",
"description": "Negates the constraint on this field. {\"age\": {\"$not\": {\"$gt\": 5}}} is TRUE when age is not greater than 5 and UNKNOWN when age is null or absent.",
"$ref": "#/$defs/ConstraintObject"
},
"$unknownAs": {
"title": "$unknownAs",
"description": "Resolves UNKNOWN to TRUE or FALSE for this constraint, applied last — after every sibling operator, including a field-level $not. {\"status\": {\"$ne\": \"archived\", \"$unknownAs\": true}} means 'not archived, and count the records where status is null or absent'. A modifier, not a predicate: it needs at least one operator beside it. No-op on $exists, which is already total. See SPEC.md §4.6.",
"type": "boolean"
}
},
"dependentRequired": {
"$flags": ["$regex"]
},
"additionalProperties": false,
"examples": [
{ "$eq": "open" },
{ "$gt": 18, "$ne": 30 },
{ "$in": ["red", "green"] },
{ "$regex": "^inv-", "$flags": "i" },
{ "$ne": "archived", "$unknownAs": true },
{ "$some": { "$in": ["urgent", "p1"] } }
]
},
"ScalarOrNull": {
"title": "Scalar value",
"description": "A JSON scalar or null.",
"type": ["string", "number", "boolean", "null"]
},
"Operand": {
"title": "Operand",
"description": "A value to compare against: any JSON value, a reference to another field of the same record, or a $literal-wrapped value.",
"anyOf": [
{ "$ref": "#/$defs/ValueRef" },
{ "$ref": "#/$defs/LiteralWrapper" },
{ "$ref": "#/$defs/PlainValue" }
]
},
"OrderedOperand": {
"title": "Ordered operand",
"description": "A value on which an ordering is defined: a number, or a string compared lexicographically — which for RFC 3339 date, date-time and time strings coincides with chronological order.",
"anyOf": [
{ "type": "number" },
{ "type": "string" },
{ "type": "string", "format": "date" },
{ "type": "string", "format": "date-time" },
{ "type": "string", "format": "time" },
{ "$ref": "#/$defs/ValueRef" }
],
"examples": [50, "2023-01-01", "2023-01-01T00:00:00Z", { "$field": "cost" }]
},
"ValueRef": {
"title": "Field reference",
"description": "Compares against another field of the same record rather than a constant: {\"price\": {\"$gt\": {\"$field\": \"cost\"}}} is SQL's WHERE price > cost.",
"type": "object",
"required": ["$field"],
"properties": {
"$field": { "$ref": "#/$defs/FieldPath" }
},
"additionalProperties": false
},
"LiteralWrapper": {
"title": "Literal escape",
"description": "Forces the wrapped value to be treated as data. Required only when an object operand would otherwise look like a field reference, e.g. {\"$literal\": {\"$field\": \"not-a-reference\"}}.",
"type": "object",
"required": ["$literal"],
"properties": {
"$literal": true
},
"additionalProperties": false
},
"PlainValue": {
"title": "Plain value",
"description": "Any JSON value that is not an object carrying '$'-prefixed keys. The '$' prefix is reserved for $field and $literal, so plain object operands must not use it.",
"if": { "type": "object" },
"then": { "type": "object", "propertyNames": { "pattern": "^[^$]" } }
},
"Bounds": {
"title": "Bounds",
"description": "An inclusive [lower, upper] pair. Both bounds SHOULD be of the same type.",
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": { "$ref": "#/$defs/OrderedOperand" },
"examples": [[10, 20], ["2023-01-01", "2023-12-31"]]
},
"OperandSet": {
"title": "Operand set",
"description": "A non-empty list of distinct values. Servers MAY cap its length; see SPEC.md §7.",
"type": "array",
"minItems": 1,
"uniqueItems": true,
"items": { "$ref": "#/$defs/Operand" },
"examples": [["red", "green", "blue"], [1, 2, 3]]
},
"SizeConstraint": {
"title": "Size constraint",
"description": "An exact array length, or a comparison on that length.",
"anyOf": [
{ "type": "integer", "minimum": 0 },
{
"type": "object",
"minProperties": 1,
"properties": {
"$eq": { "type": "integer", "minimum": 0 },
"$ne": { "type": "integer", "minimum": 0 },
"$gt": { "type": "integer", "minimum": 0 },
"$gte": { "type": "integer", "minimum": 0 },
"$lt": { "type": "integer", "minimum": 0 },
"$lte": { "type": "integer", "minimum": 0 }
},
"additionalProperties": false
}
],
"examples": [3, { "$gte": 1 }]
}
}
}