Skip to content

Filtering #1

@nickevansuk

Description

@nickevansuk

Requirements

To allow for collections of resources to be filtered within the API, a standard approach to filtering/querying collections should be used.

The approach must be compatible with the PropertyValueSpecification for query string properties.

The approach should cover:

1) The following for numeric and date types:

  • Range filters for dates and values (e.g. remainingAttendeeCapacity>2 or startDate>2018-01-01T12:00:00)
  • Component filters for dates, to only search on time component or date component (e.g. startDate>12:00)

2) The following for enum types and controlled vocabularies:

  • Standard filtering using schema.org enums (e.g. genderRestriction=Male)
  • Filtering using SKOS concepts (e.g. activity=Yoga)
  • Restriction to set of enums (e.g. genderRestriction=Male or genderRestriction=Mixed)

3) The following regarding query structure:

  • AND and OR nesting definition, to remove ambiguity
  • Nested properties to be included in the filter, e.g. offers.price

4) Boolean values as primitive types, including null values (for undefined properties):

  • isAccessibleForFree=true,null

5) Geo search

  • Including a filter for radial and boundingBox search

For example, for both of the endpoints below:

  • /sessions?
  • /facility-uses?

References

Discounted options

A good discussion of available options is available here: https://www.moesif.com/blog/technical/api-design/REST-API-Design-Filtering-Sorting-and-Pagination/

  • hydra:search is "deliberately fuzzy", so too generic to be applicable
  • The common option of just using standard enum filters ?type=japanese,chinese&rating=4,5&days=sunday is not expressive enough
  • The option of using OData style ?$filter=price lt 10.00 was overly expressive, and implied a greater complexity of query capability than is likely available in most cases. It also requires the use of a complex $filter syntax even for simple cases, where most APIs implement something similar to ?status=open
  • The option of using {property_name}_from and {property_name}_to query range was also too simplistic, and not easily extensible.
  • For filter functions on specific properties, creating objects such aslocation.geo.radialFilter.latitude={latitude}&location.geo.radial.longitude={longitude}&location.geo.radial.radius={radius} intrudes on the properties namespace and extends the length of the GET request unnecessarily. Using the operator pattern consistently here resolves this e.g. location.geo=radial:{location.geo:radial.latitude},{location.geo:radial.longitude},{location.geo:radial.radius}

Proposal with Examples

Of all the options investigated, the OpenStack approach appears to be the most user-friendly, as it strikes the best balance between extensibility and familiarity.

The following prefixed operators are allowed: in, nin, neq, gt, gte, lt, and lte e.g. ?size=gt:8. A comma separated list as an operand is synonymous with "in".

  • Standard filtering using schema.org enums (e.g. genderRestriction=Male)
    • ?genderRestriction=Female (only the string following the # is required from e.g. ID https://www.openactive.io/ns#Female)
  • Filtering using SKOS concepts (e.g. activity=Yoga)
    • ?activity=d5f34cb1-35c0-46e5-ad6d-181f77274640 (only the string following the # is required from e.g. ID https://www.openactive.io/activity-list/#d5f34cb1-35c0-46e5-ad6d-181f77274640)
  • Restriction to set of enums (e.g. genderRestriction=Male or genderRestriction=Mixed)
    • ?genderRestriction=in:Female,Male (only the string following the # is required from e.g. ID https://www.openactive.io/ns#Female)
  • Range filters for dates and values (e.g. remainingAttendeeCapacity>2 or startDate>2018-01-01T12:00:00)
    • ?remainingAttendeeCapacity=gt:2
    • ?startDate=gt:2018-01-01T12:00:00Z
    • ?startDate=gt:2018-01-01T12:00:00Z&startDate=lt:2018-03-01T12:00:00Z
  • Component filters for dates, to only search on time component or date component (e.g. startDate>12:00)
    • ?startDate=gt:10:00Z&startDate=lt:14:00Z
    • ?startDate=gte:2018-01-01&startDate=lte:2018-01-01 is the same as ?startDate=2018-01-01 - all will return results for startDate any time on 2018-01-01
  • Boolean values as primitive types
    • ?isAccessibleForFree=true
    • ?isAccessibleForFree=in:true,null
  • AND and OR nesting definition, to remove ambiguity
    • AND represented by multiple params: ?startDate=gt:10:00Z&startDate=lt:14:00Z
    • OR represented by specific operators: ?genderRestriction=in:Female,Male
    • AND's connect each parameter, with ORs used within specific operator, hence ?startDate=gt:10:00Z&startDate=lt:14:00Z&genderRestriction=in:Female,Male is parsed as startDate>10:00 AND startDate<14:00 AND (genderRestriction = Female OR genderRestriction)
  • Nested properties to be included in the filter, e.g. offers.price
    • Use dot notation to access properties?slot.startDate=gt:10:00Z&slot.startDate=lt:14:00Z
  • Because operators are only available on numeric, date and enum types, and not strings, there is no issue of literal values matching operators.
  • Boolean values as primitive types, including null values (for undefined properties):
    • isAccessibleForFree=true,null
    • Available values: true, false, null
  • null is a reserved value for all types, and can be used to filter on where a specific property is undefined
  • Including a filter of latitude, longitude, and radius
    • geo objects are afforded specific search objects:
      • location.geo=radial:{location.geo:radial.latitude},{location.geo:radial.longitude},{location.geo:radial.radius}
      • location.geo=radial:{location.geo:radial.latitude},{location.geo:radial.longitude} (automatic radius)
      • location.geo=boundingBox:{location.geo:boundingBox.topLeft.latitude},{location.geo:boundingBox.topLeft.longitude},{location.geo:boundingBox.bottomRight.latitude},{location.geo:boundingBox.bottomRight.longitude}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions