-
Notifications
You must be signed in to change notification settings - Fork 0
/
evaluator.ts
98 lines (86 loc) · 2.72 KB
/
evaluator.ts
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
import { Flag, Shard, Range, Variation } from './interfaces';
import { Rule, matchesRule } from './rules';
import { MD5Sharder, Sharder } from './sharders';
import { SubjectAttributes } from './types';
export interface FlagEvaluation {
flagKey: string;
subjectKey: string;
subjectAttributes: SubjectAttributes;
allocationKey: string | null;
variation: Variation | null;
extraLogging: Record<string, string>;
doLog: boolean;
}
export class Evaluator {
sharder: Sharder;
constructor(sharder?: Sharder) {
this.sharder = sharder ?? new MD5Sharder();
}
evaluateFlag(
flag: Flag,
subjectKey: string,
subjectAttributes: SubjectAttributes,
obfuscated: boolean,
): FlagEvaluation {
if (!flag.enabled) {
return noneResult(flag.key, subjectKey, subjectAttributes);
}
const now = new Date();
for (const allocation of flag.allocations) {
if (allocation.startAt && now < new Date(allocation.startAt)) continue;
if (allocation.endAt && now > new Date(allocation.endAt)) continue;
if (
matchesRules(allocation?.rules ?? [], { id: subjectKey, ...subjectAttributes }, obfuscated)
) {
for (const split of allocation.splits) {
if (
split.shards.every((shard) => this.matchesShard(shard, subjectKey, flag.totalShards))
) {
return {
flagKey: flag.key,
subjectKey,
subjectAttributes,
allocationKey: allocation.key,
variation: flag.variations[split.variationKey],
extraLogging: split.extraLogging ?? {},
doLog: allocation.doLog,
};
}
}
}
}
return noneResult(flag.key, subjectKey, subjectAttributes);
}
matchesShard(shard: Shard, subjectKey: string, totalShards: number): boolean {
const assignedShard = this.sharder.getShard(hashKey(shard.salt, subjectKey), totalShards);
return shard.ranges.some((range) => isInShardRange(assignedShard, range));
}
}
export function isInShardRange(shard: number, range: Range): boolean {
return range.start <= shard && shard < range.end;
}
export function hashKey(salt: string, subjectKey: string): string {
return `${salt}-${subjectKey}`;
}
export function noneResult(
flagKey: string,
subjectKey: string,
subjectAttributes: SubjectAttributes,
): FlagEvaluation {
return {
flagKey,
subjectKey,
subjectAttributes,
allocationKey: null,
variation: null,
extraLogging: {},
doLog: false,
};
}
export function matchesRules(
rules: Rule[],
subjectAttributes: SubjectAttributes,
obfuscated: boolean,
): boolean {
return !rules.length || rules.some((rule) => matchesRule(rule, subjectAttributes, obfuscated));
}