forked from launchdarkly/node-server-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
/
operators.js
106 lines (94 loc) · 3.32 KB
/
operators.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
const semver = require('semver');
// Our reference SDK, Go, parses date/time strings with the time.RFC3339Nano format. This regex should match
// strings that are valid in that format, and no others.
// Acceptable: 2019-10-31T23:59:59Z, 2019-10-31T23:59:59.100Z, 2019-10-31T23:59:59-07, 2019-10-31T23:59:59-07:00, etc.
// Unacceptable: no "T", no time zone designation
const dateRegex = new RegExp('^\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d(\\.\\d\\d*)?(Z|[-+]\\d\\d(:\\d\\d)?)');
function stringOperator(f) {
return (userValue, clauseValue) =>
typeof userValue === 'string' && typeof clauseValue === 'string' && f(userValue, clauseValue);
}
function numericOperator(f) {
return (userValue, clauseValue) =>
typeof userValue === 'number' && typeof clauseValue === 'number' && f(userValue, clauseValue);
}
function dateOperator(f) {
return (userValue, clauseValue) => {
const userValueNum = parseDate(userValue);
const clauseValueNum = parseDate(clauseValue);
return userValueNum !== null && clauseValueNum !== null && f(userValueNum, clauseValueNum);
};
}
function parseDate(input) {
switch (typeof input) {
case 'number':
return input;
case 'string':
return dateRegex.test(input) ? Date.parse(input) : null;
default:
return null;
}
}
function semVerOperator(fn) {
return (a, b) => {
const av = parseSemVer(a),
bv = parseSemVer(b);
return av && bv ? fn(av, bv) : false;
};
}
function parseSemVer(input) {
if (typeof input !== 'string') {
return null;
}
if (input.startsWith('v')) {
// the semver library tolerates a leading "v", but the standard does not.
return null;
}
let ret = semver.parse(input);
if (!ret) {
const versionNumericComponents = new RegExp('^\\d+(\\.\\d+)?(\\.\\d+)?').exec(input);
if (versionNumericComponents) {
let transformed = versionNumericComponents[0];
for (let i = 1; i < versionNumericComponents.length; i++) {
if (versionNumericComponents[i] === undefined) {
transformed = transformed + '.0';
}
}
transformed = transformed + input.substring(versionNumericComponents[0].length);
ret = semver.parse(transformed);
}
}
return ret;
}
function safeRegexMatch(pattern, value) {
try {
return new RegExp(pattern).test(value);
} catch (e) {
// do not propagate this exception, just treat a bad regex as a non-match for consistency with other SDKs
return false;
}
}
const operators = {
in: (a, b) => a === b,
endsWith: stringOperator((a, b) => a.endsWith(b)),
startsWith: stringOperator((a, b) => a.startsWith(b)),
matches: stringOperator((a, b) => safeRegexMatch(b, a)),
contains: stringOperator((a, b) => a.indexOf(b) > -1),
lessThan: numericOperator((a, b) => a < b),
lessThanOrEqual: numericOperator((a, b) => a <= b),
greaterThan: numericOperator((a, b) => a > b),
greaterThanOrEqual: numericOperator((a, b) => a >= b),
before: dateOperator((a, b) => a < b),
after: dateOperator((a, b) => a > b),
semVerEqual: semVerOperator((a, b) => a.compare(b) === 0),
semVerLessThan: semVerOperator((a, b) => a.compare(b) < 0),
semVerGreaterThan: semVerOperator((a, b) => a.compare(b) > 0),
};
const operatorNone = () => false;
function fn(op) {
return operators[op] || operatorNone;
}
module.exports = {
operators: operators,
fn: fn,
};