Custom Filter Types
Say you want to add a criteria property that, when set, invokes complex filtering logic for a read operation in an entity repo. A custom filter is a way to accomplish this.
TL;DR
There are three steps to implementing a custom filter:
- Create your class implementing
IFilter
. - Create its
IFilterDefBuilder
Class - Register it with the repo plugin.
- Use it in your criteria object as a property type or a property decorator.
- (optional) Create a custom deserializer for it.
The Scenario
You're building a carnival api, and you want to add a criteria property to filter on the UserProfile
entities that are allowed to ride a given roller coaster. Let's name this new property: canRide
.
Now if there were a boolean property named canRide
on your UserProfile
entity, then we'd be done; the repo would take it from there.
But not in this scenario: there is no property canRide
on a user profile. In fact, canRide
really means...
* ...is at least 42" tall
* ...AND is not a pregnant female
* ...AND does not have a heart condition
* ...AND is not overdue on membership fees
So if our api receives a criteria object that looks like this:
{
"attraction": "greased-lightning",
"canRide": true
}
...then the logic it needs to invoke is going to look something like this (using MongoDb):
{
"height": { "$gt": 42 },
"$not": {
"sex": "female",
"pregnant": true
},
"medical": {
"$not": {
"$contains": "heart"
}
}
}
So Create a Custom Filter for Your Custom Logic
A filter is a class type that you apply to a property on a criteria object. Let's make a FastRollerCoasterFilter
...as the property type:
class UserProfileCriteria {
public canRide?: FastRollerCoasterFilter;
public attraction: string;
}
...or declared in a filterWith
decorator
class UserProfileCriteria {
public attraction: string;
@filterWith(FastRollerCoasterFilter)
public canRide?: boolean|;
}
A Custom filterDef will allow you to invoke unique query behaviors from a criteria property, without having to write a custom endpoint or repo.
How to Implement:
Step 1: Create Your Own Filter Class
This is where you write general logic for the api platform.
class FastRollerCoasterFilter implements IFilter {
constructor() {
}
public property?: string;
public $not?: this;
}
Step 2: Create its FilterDefBuilder Class
This is where you add support for your particular database platform. You'll need to implement IFilterDefBuilder
.
The MongoDb Repo Plugin provdes a base class for you to extend: MongoFilterDefBuilder
...
class FastRollerCoasterFilterBuilder extends MongoFilterDefBuilder<FastRollerCoasterFilter> {
}
Step 3: Register It with the Repo
Declare your filter type from within your api's bootstrap()
method...
this.registerFilterType(FastRollerCoasterFilter, FastRollerCoasterFilterBuilder);
Step 4: Use Custom Filter as a property type on a criteria class for your entity
There are two ways to use your custom filter type:
...as the property type:
class UserProfileCriteria {
public canRide?: FastRollerCoasterFilter;
public attraction: string;
}
...or declared in a filterWith
decorator
class UserProfileCriteria {
public attraction: string;
@filterWith(FastRollerCoasterFilter)
public canRide?: boolean|;
}
Step 5: Custom Deserializers
Perhaps your custom filter type has a unique abbreviated string representation in a request that needs custom handling in order to translate it into a concrete instance off your filter.
StringFilter
does this, allowing an inbound request like
{ "favoriteColor": "$sw_gr" }
...to be deserialized into the following instance of StringFilter
:
{
"property": "favoriteColor",
"$startsWith": "gr"
}
To apply a custom deserializer for your filter, you will need to register your deserializer with the api itself, so that it can use it when it encounters your filter during the inbound sequence of a request.
Create your Deserializer
So first, let's create a static method on the filter class for deserializing
class FastRollerCoasterFilterSerializer {
public deserialize(value: string) {
let instance = new FastRollerCoasterFilter();
this.doSomethingUnique(value, instance);
return instance;
}
}
(NOTE: YES, you can instantiate a serializer class, too, instead of using a static method. We're just trying to keep it simple and stay focused on the registration in the following step. That's the required part)
Register Your Deserializer With the Api
In the bootstrap()
method of your api, tell the api itself how to deserialize an instance in a request:
Bootstrap(host: apiHost): void {
...
api.registerFilterSerializerFor(FastRollerCoasterFilter,
x => new FastRollerCoasterFilterSerializer().deserialize(x)
);
...
}