UNPKG

serverless-offline-streams

Version:

This plugin provides support for event driven systems using Serverless Offline

113 lines (94 loc) 4.52 kB
import {FilterPatterns, NumericOperator, NumericRange, NumericSingle} from "./filterGrammar"; import {DynamoStreamsEventFilters} from "../dynamoStreamsEventTypes"; const AWSDataKeys = new Set(["S", "N", "B", "BOOL", "L", "M", "NS", "SS", "BS", "NULL"]) const AWSRuleKeys = new Set(["anything-but"]) const isDataContainer = (v: any): boolean => !!(v && typeof v === 'object' && Object.keys(v).length === 1) const dataContainerKey = (v: object) => Object.keys(v)[0] const dataContainerValue = (v: object) => v[dataContainerKey(v)] const isPrimitiveScalar = (v: any): boolean => new Set(['boolean', 'string', 'number']).has(typeof v) const isArrayScalar = (v: any): boolean => Array.isArray(v) const isAwsScalar = (v: any): boolean => isDataContainer(v) && AWSDataKeys.has((Object.keys(v))[0]) const isEmptyScalar = (v: any): boolean => v === undefined || v === null const isScalar = (v: any): boolean => isPrimitiveScalar(v) || isArrayScalar(v) || isAwsScalar(v) || isEmptyScalar(v) const isAwsRule = (v: any, ruleKey?: string): boolean => { if (!isDataContainer(v)) return false return ruleKey ? ruleKey === dataContainerKey(v) : AWSRuleKeys.has(dataContainerKey(v)) } const toScalar = (data: any): Array<any> => { if (Array.isArray(data)) { return data as Array<any> } if (isAwsScalar(data)) { const dataTypeKey = Object.keys(data)[0]; if (dataTypeKey === 'NULL') { return null } return toScalar(data[dataTypeKey]) } return data } const isAnythingButMatch = (p: any, data: any) => isAwsRule(p, 'anything-but') && evalReduceAnd(dataContainerValue(p), (v) => v !== data) const isExistsMatch = (p: any, data: any) => isAwsRule(p, 'exists') && (!!(p.exists && data) || !!(!p.exists && !data)) const isNumericMatch = (p: any, data: any) => { const applyMatchRules = (operator: NumericOperator, operand: number) => { switch (operator) { case '=': return data === operand case '<': return data < operand case '<=': return data <= operand case '>': return data > operand case '>=': return data >= operand default: throw Error(`Unknown numeric rule operand: '${operand}'`) } } if (isAwsRule(p, 'numeric')) { const numericRules = dataContainerValue(p); if (numericRules.length === 2) { const [operator, operand] = numericRules as NumericSingle return applyMatchRules(operator, operand) } else { const [operator1, operand1, operator2, operand2] = numericRules as NumericRange return applyMatchRules(operator1, operand1) && applyMatchRules(operator2, operand2) } } return false } const isEqualityMatch = (p: any, data: any) => p === null && data === null || (isPrimitiveScalar(p) && isPrimitiveScalar(data) && p === data) || (isArrayScalar(p) && isPrimitiveScalar(data) && evalReduceOr(p, (v) => v === data)) const isBeginsWithMatch = (p: any, data: any) => isAwsRule(p, 'prefix') && data.startsWith(dataContainerValue(p)) const isMatch = (p: any, data: any) => evalReduceOr([ isEqualityMatch, isAnythingButMatch, isNumericMatch, isExistsMatch, isBeginsWithMatch ], f => f.apply(null, [p, data])) const evalNested = (patternData: any, eventData: any) => { if (isScalar(patternData) && isScalar(eventData)) { const patternDataScalar = toScalar(patternData); const eventDataScalar = toScalar(eventData) return evalReduceOr(patternDataScalar, (p) => isMatch(p, eventDataScalar)) } else { return evalReduceAnd(Object.keys(patternData), (k) => evalNested(patternData?.[k], eventData?.[k])) } } function evalReduceAnd<T>(data: T[], evalFunc: (data: T, i: number) => boolean): boolean { return evalReduce(data, evalFunc, (acc, v) => acc && v) } function evalReduceOr<T>(data: T[], evalFunc: (data: T, i: number) => boolean): boolean { return evalReduce(data, evalFunc, (acc, v) => acc || v) } function evalReduce<T>(data: T[], evalFunc: (data: T, i: number) => boolean, reduceFunc: (acc: boolean, v: boolean) => boolean): boolean { return data.map((d, i) => evalFunc(d, i)).reduce(reduceFunc) } export const allowEvent = (filterPatterns: FilterPatterns[], event: DynamoStreamsEventFilters): boolean => evalReduceOr(filterPatterns, (p) => evalNested(p, event))