AIP-160
Filtering
Often, when listing resources (using a list method as defined in AIP-132 or something reasonably similar), it is desirable to filter over the collection and only return results that the user is interested in.
It is tempting to define a structure to handle the precise filtering needs for each API. However, filtering requirements evolve frequently, and therefore it is prudent to use a string field with a structured syntax accessible to a non-technical audience. This allows updates to be able to be made transparently, without waiting for UI or client updates.
Note: Because list filters are intended for a potentially non-technical audience, they sometimes borrow from patterns of colloquial speech rather than common patterns found in code.
Guidance
APIs may provide filtering to users on List
methods (or similar methods
to query a collection, such as Search
). If they choose to do so, they
should follow the common specification for filters discussed here. The
syntax is formally defined in the EBNF grammar.
When employing filtering, a request message should have exactly one
filtering field, string filter
. Filtering of related objects is handled
through traversal or functions.
Note: List Filters have fuzzy matching characteristics with support for result ranking and scoring. For developers interested in deterministic evaluation of list filters, see CEL.
Literals
A bare literal value (examples: "42", "Hugo") is a value to be matched against. Literals appearing alone (with no specified field) should usually be matched anywhere it may appear in an object's field values.
However, a service may choose to only consider certain fields; if so, it must document which fields it considers. A service may include new fields over time, but should do so judiciously and consider impact on existing users.
Note: Literals separated by whitespace are considered to have a fuzzy
variant of AND
. Therefore, Victor Hugo
is roughly equivalent to
Victor AND Hugo
.
Logical Operators
Filtering implementations should provide the binary operators:
Operator | Example | Meaning |
---|---|---|
AND |
a AND b |
True if a and b are true. |
OR |
a OR b OR c |
True if any of a , b , c are true. |
Note: To match common patterns of speech, the OR
operator has higher
precedence than AND
, unlike what is found in most programming languages. The
expression a AND b OR c
evaluates: a AND (b OR c)
. API documentation and
examples should encourage the use of explicit parentheses to avoid
confusion, but should not require explicit parentheses.
Negation Operators
Filtering implementations should provide the unary operators NOT
and -
.
These are used interchangeably, and a service that supports negation must
support both formats.
Operator | Example | Meaning |
---|---|---|
NOT |
NOT a |
True if a is not true. |
- |
-a |
True if a is not true. |
Comparison Operators
Filtering implementations should provide the binary comparison operators
=
, !=
, <
, >
, <=
, and >=
for string, numeric, timestamp, and
duration fields (but should not provide them for booleans or enums).
Operator | Example | Meaning |
---|---|---|
= |
a = true |
True if a is true. |
!= |
a != 42 |
True unless a equals 42. |
< |
a < 42 |
True if a is a numeric value below 42. |
> |
a > "foo" |
True if a is lexically ordered after "foo". |
<= |
a <= "foo" |
True if a is "foo" or lexically before it. |
>= |
a >= 42 |
True if a is a numeric value of 42 or higher. |
Note: Unlike in most programming languages, field names must appear on the left-hand side of a comparison operator; the right-hand side only accepts literals and logical operators.
Because filters are accepted as query strings, type conversion takes place to translate the string to the appropriate strongly-typed value:
- Enums expect the enum's string representation (case-sensitive).
- Booleans expect
true
andfalse
literal values. - Numbers expect the standard integer or float representations. For floats,
exponents are supported (e.g.
2.997e9
). - Durations expect a numeric representation followed by an
s
suffix (for seconds). Examples:20s
,1.2s
. - Timestamps expect an RFC-3339 formatted string (e.g.
2012-04-21T11:30:00-04:00
). UTC offsets are supported.
Warning: The identifiers true
, false
, and null
only carry intrinsic
meaning when used in the context of a typed field reference.
Additionally, when comparing strings for equality, services should support
wildcards using the *
character; for example, a = "*.foo"
is true if a
ends with ".foo".
Traversal operator
Filtering implementations should provide the .
operator, which indicates
traversal through a message, map, or struct.
Example | Meaning |
---|---|
a.b = true |
True if a has a boolean b field that is true. |
a.b > 42 |
True if a has a numeric b field that is above 42. |
a.b.c = "foo" |
True if a.b has a string c field that is "foo". |
Traversal must be written using the field names from the resource. If a service wishes to support "implicit fields" of some kind, they must do so through well-documented functions. A service may specify a subset of fields that are supported for traversal.
If a user attempts to traverse to a field that is not defined on the message,
the service should return an error with INVALID_ARGUMENT
. A service
may permit traversal to undefined keys on maps and structs, and should
document how it behaves in this situation.
Important: The .
operator must not be used to traverse through a
repeated field or list, except for specific use with the :
operator.
Has Operator
Filtering implementations must provide the :
operator, which means "has".
It is usable with collections (repeated fields or maps) as well as messages,
and behaves slightly differently in each case.
Repeated fields query to see if the repeated structure contains a matching element:
Example | Meaning |
---|---|
r:42 |
True if r contains 42. |
r.foo:42 |
True if r contains an element e such that e.foo = 42 . |
Important: Filters can not query a specific element on a repeated field
for a value. For example, e.0.foo = 42
and e[0].foo = 42
are not valid
filters.
Maps, structs, messages can query either for the presence of a field in the map or a specific value:
Example | Meaning |
---|---|
m:foo |
True if m contains the key "foo". |
m.foo:* |
True if m contains the key "foo". |
m.foo:42 |
True if m.foo is 42. |
There are two slight distinctions when parsing messages:
- When traversing messages, a field is only considered to be present if it has a non-default value.
- When traversing messages, field names are snake case, although implementations may choose to support automatic conversion between camel case and snake case.
Functions
The filtering language supports a function call syntax in order to support
API-specific extensions. An API may define a function using the
call(arg...)
syntax, and must document any specific functions it
supports.
Limitations
A service may specify further structure or limitations for filter queries, above what is defined here. For example, a service may support the logical operators but only permit a certain number of them (to avoid "queries of death" or other performance concerns).
Further structure or limitations must be clearly documented, must not
violate requirements set forth in this document, and a non-compliant filter
query must error with INVALID_ARGUMENT
.