filter
— Stop Processing
Filter Flows are used to filter messages. They are used to determine if a message should be processed further or if it should be dropped.
Interface
The interface FilterFlow can be defined as:
type FilterFunc = (msg: IMsg, context: IMessageContext) => boolean
type FilterFlow = FilterFunc | { kind: 'filter'; filter: FilterFunc }
Refer to the Message Interface (IMsg) for more information on the Msg class and extrapolating data from the message to use in comparisons.
Refer to the Context Object for more information on the context object.
Use Cases
The Ingestion and Route each allow for an array of flows. The filter flow, is one of the allowed flows in either one of these arrays. If a message is filtered out in an ingestion flow, then none of the latter flows in the ingestion, nor any of the routes will be called. If a message is filtered out in a route, then none of the latter flows in that route will be called, however, the next route will still be called. Routes are called asynchronously, Ingestion flows are called synchronously.
const channel: ChannelConfig = {
// ...
ingestion: [
// filters here
],
routes: [
{
kind: 'route',
name: 'Route 1',
flows: [
// filters here
],
},
{
kind: 'route',
name: 'Route 2',
flows: [
// filters here
],
}
]
};
Example
If the filter function returns true, then the message will be processed further. If the filter functions return false, then the message will be dropped. An easy catchy phrase to remember is “If it’s true, then let it through. If it’s false, then it will halt.”
Here is a simple example of a filter that will only allow ADT event messages to be processed further:
import gofer, { ChannelConfig, FilterFunc } from '@gofer-engine/engine';
const filter: FilterFunc = (msg, { kind }) => {
if (kind !== 'HL7v2') return false;
return msg.get('MSH-9.1') === 'ADT'
}
// OOP style
gofer.listen('tcp', 'localhost', 5510)
.name('ADT Channel with OOP')
.filter(filter)
.ack()
// Config style
const channelConfig: ChannelConfig = {
name: 'ADT Channel with Configs',
source: {
kind: 'tcp',
tcp: {
host: 'localhost',
port: 5511,
},
},
ingestion: [{
kind: 'filter',
filter,
}],
};
gofer.configs([channelConfig]);
Advanced Example
Since Gofer Engine uses native TypeScript/JavaScript, you can use any of the recipes you are familiar with to pass variables and create advanced filtering logic. You could even keep your filters in a common directory and import them where needed into your channels.
Let's refactor the previous example a little bit and make the filter function reusable for other channels allowing a set of HL7 v2 message categories.
import { FilterFunc } from '@gofer-engine/engine';
export const filterByCategory = (categories: string[]): FilterFunc => {
return (msg, { kind }) => {
if (kind !== 'HL7v2') return false;
return categories.includes(msg.get('MSH-9.1'));
};
};
import gofer from '@gofer-engine/engine';
import { filterByCategory } from './filterByCategory';
gofer
.listen('tcp', 'localhost', 5512)
.filter(filterByCategory(['ADT', 'ORM', 'ORU']))
.ack();
Advanced Type Control
Config Style allows for advanced type control. You can pass through a generic to the ChannelConfig
(the first generic option) to either:
'F'
= Only allows raw filter Functions. E.G. ingestion:[() => true]
'O'
= Only allow filter functions in Objects. E.G. ingestion:[{ filter: () => true }]
'B'
= Allow Both raw filter functions or wrapped in objects. E.G. ingestion:[() => true, { filter: () => true }]
The default is 'B'
. E.G. const channel: ChannelConfig<'B'> = ...
import { ChannelConfig } from '@gofer-engine/engine';
const functionalFilterChannel: ChannelConfig<'F'> = {
name: 'Functional Filter Channel',
source: {
kind: 'http',
http: { host: 'localhost', port: 8081, method: 'POST', msgType: 'HL7v2' },
},
ingestion: [
(msg) => msg.get('PV1.3.1') !== 'ER',
],
};
const objectFilterChannel: ChannelConfig<'O'> = {
name: 'Functional Filter Channel',
source: {
kind: 'https',
https: {
host: 'localhost',
port: 8082,
method: 'POST',
cert: process.env['SSL_CERT'],
key: process.env['SSL_KEY'],
msgType: 'HL7v2',
},
},
ingestion: [
{
kind: 'filter',
filter: (msg, { setMsgVar, messageId }) => {
setMsgVar(messageId, 'name', `${msg.get('name.first')} ${msg.get('name.last')}`)
return true;
},
}
],
};
// `ChannelConfig<'B'>` is same as default `ChannelConfig`
const mixedFilterChannel: ChannelConfig<'B'> = {
name: 'Mixed Filter Channel',
source: {
kind: 'sftp',
sftp: {
msgType: 'DELIMITED',
connection: {
host: 'sftp.example.com',
username: 'user',
password: 'pass',
}
},
},
ingestion: [
// require the header first column to be 'name'
{
kind: 'filter',
filter: (msg) => msg.get('A0') === 'name',
},
// set the message variable 'name' to the array of names
// this filter will always return true, just a shortcut
// for setting a message variable
(msg, { setMsgVar, messageId }) => {
setMsgVar(messageId, 'names', msg.get('A').shift());
return true;
},
],
};
export const channels = [
functionalFilterChannel,
objectFilterChannel,
mixedFilterChannel
];
OOP Style
The OOP style channel builder filter
method aligns with the following types:
IngestionClass.filter = (filter: FilterFlow<'F'>) => IngestionClass
RouteClass.filter = (filter: FilterFlow<'F>) => RouteClass
This means that the filter
method accepts a single argument which is a function and does not accept an object containing the kind
property