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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions