Skip to content

Commit d870f3f

Browse files
committed
Refactor to only have one TransitionCondition class for auth
1 parent f79208a commit d870f3f

File tree

1 file changed

+120
-111
lines changed

1 file changed

+120
-111
lines changed

src/components/workflow/workflow.granter.ts

Lines changed: 120 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,15 @@ export function WorkflowEventGranter<W extends Workflow>(workflow: W) {
4646
}
4747

4848
isTransitions(...transitions: Array<Many<Names>>) {
49-
return TransitionCondition.fromName(transitions.flat());
49+
return TransitionCondition.fromName(workflow, transitions.flat());
5050
}
5151

5252
transitions(...transitions: Array<Many<Names>>) {
5353
return this.when(this.isTransitions(...transitions));
5454
}
5555

5656
isState(...states: Array<Many<State>>) {
57-
return TransitionCondition.fromEndState(states.flat());
57+
return TransitionCondition.fromEndState(workflow, states.flat());
5858
}
5959

6060
state(...states: Array<Many<State>>) {
@@ -66,128 +66,137 @@ export function WorkflowEventGranter<W extends Workflow>(workflow: W) {
6666
}
6767
}
6868

69-
interface TransitionCheck {
70-
key: ID[];
71-
name?: Names;
72-
endState?: State;
73-
}
69+
return WorkflowEventGranterClass;
70+
}
7471

75-
class TransitionCondition implements Condition<EventClass> {
76-
private readonly allowedTransitionKeys;
72+
interface TransitionCheck<W extends Workflow> {
73+
key: ID[];
74+
name?: W['transition']['name'];
75+
endState?: W['state'];
76+
}
7777

78-
protected constructor(private readonly checks: readonly TransitionCheck[]) {
79-
this.allowedTransitionKeys = new Set(checks.flatMap((c) => c.key));
80-
}
78+
export class TransitionCondition<W extends Workflow>
79+
implements Condition<W['eventResource']>
80+
{
81+
readonly allowedTransitionKeys;
8182

82-
static fromName(transitionNames: readonly Names[]) {
83-
const allowed = new Set(transitionNames);
84-
return new TransitionCondition(
85-
[...allowed].map((name) => ({
86-
name,
87-
key: [workflow.transitions.find((t) => t.name === name)!.key],
88-
})),
89-
);
90-
}
83+
protected constructor(
84+
private readonly checks: ReadonlyArray<TransitionCheck<W>>,
85+
) {
86+
this.allowedTransitionKeys = new Set(checks.flatMap((c) => c.key));
87+
}
9188

92-
static fromEndState(states: readonly State[]) {
93-
const allowed = new Set(states);
94-
return new TransitionCondition(
95-
[...allowed].map((endState) => ({
96-
endStatus: endState,
97-
key: workflow.transitions
98-
// TODO handle dynamic to?
99-
.filter((t) => typeof t.to === 'string' && allowed.has(t.to))
100-
.map((t) => t.key),
101-
})),
102-
);
103-
}
89+
static fromName<W extends Workflow>(
90+
workflow: W,
91+
transitionNames: ReadonlyArray<W['transition']['name']>,
92+
) {
93+
const allowed = new Set(transitionNames);
94+
return new TransitionCondition(
95+
[...allowed].map((name) => ({
96+
name,
97+
key: [workflow.transitions.find((t) => t.name === name)!.key],
98+
})),
99+
);
100+
}
104101

105-
isAllowed({ object }: IsAllowedParams<EventClass>) {
106-
if (!object) {
107-
// We are expecting to be called without an object sometimes.
108-
// These should be treated as false without error.
109-
return false;
110-
}
111-
const transitionKey = Reflect.get(object, TransitionKey);
112-
if (!transitionKey) {
113-
return false;
114-
}
115-
return this.allowedTransitionKeys.has(transitionKey);
116-
}
102+
static fromEndState<W extends Workflow>(
103+
workflow: W,
104+
states: ReadonlyArray<W['state']>,
105+
) {
106+
const allowed = new Set(states);
107+
return new TransitionCondition(
108+
[...allowed].map((endState) => ({
109+
endStatus: endState,
110+
key: workflow.transitions
111+
// TODO handle dynamic to?
112+
.filter((t) => typeof t.to === 'string' && allowed.has(t.to))
113+
.map((t) => t.key),
114+
})),
115+
);
116+
}
117117

118-
asCypherCondition(query: Query) {
119-
// TODO bypasses to statuses won't work with this. How should these be filtered?
120-
const required = query.params.addParam(
121-
this.allowedTransitionKeys,
122-
'allowedTransitions',
123-
);
124-
return `node.transition IN ${String(required)}`;
118+
isAllowed({ object }: IsAllowedParams<W['eventResource']>) {
119+
if (!object) {
120+
// We are expecting to be called without an object sometimes.
121+
// These should be treated as false without error.
122+
return false;
125123
}
126-
127-
asEdgeQLCondition() {
128-
// TODO bypasses to statuses won't work with this. How should these be filtered?
129-
const transitionAllowed = eqlInLiteralSet(
130-
'.transitionKey',
131-
this.allowedTransitionKeys,
132-
'uuid',
133-
);
134-
// If no transition then false
135-
return `((${transitionAllowed}) ?? false)`;
124+
const transitionKey = Reflect.get(object, TransitionKey) as ID | null;
125+
if (!transitionKey) {
126+
return false;
136127
}
128+
return this.allowedTransitionKeys.has(transitionKey);
129+
}
137130

138-
union(this: void, conditions: readonly this[]) {
139-
const checks = [
140-
...new Map(
141-
conditions
142-
.flatMap((condition) => condition.checks)
143-
.map((check) => {
144-
const key = check.name
145-
? `name:${check.name}`
146-
: `state:${check.endState!}`;
147-
return [key, check];
148-
}),
149-
).values(),
150-
];
151-
return new TransitionCondition(checks);
152-
}
131+
asCypherCondition(query: Query) {
132+
// TODO bypasses to statuses won't work with this. How should these be filtered?
133+
const required = query.params.addParam(
134+
this.allowedTransitionKeys,
135+
'allowedTransitions',
136+
);
137+
return `node.transition IN ${String(required)}`;
138+
}
153139

154-
intersect(this: void, conditions: readonly this[]) {
155-
const checks = [...conditions[0].checks].filter((check1) =>
156-
conditions.every((cond) =>
157-
cond.checks.some(
158-
(check2) =>
159-
check1.name === check2.name ||
160-
check1.endState === check2.endState,
161-
),
162-
),
163-
);
164-
return new TransitionCondition(checks);
165-
}
140+
asEdgeQLCondition() {
141+
// TODO bypasses to statuses won't work with this. How should these be filtered?
142+
const transitionAllowed = eqlInLiteralSet(
143+
'.transitionKey',
144+
this.allowedTransitionKeys,
145+
'uuid',
146+
);
147+
// If no transition then false
148+
return `((${transitionAllowed}) ?? false)`;
149+
}
166150

167-
[inspect.custom](_depth: number, _options: InspectOptionsStylized) {
168-
const render = (label: string, items: readonly string[]) => {
169-
const itemsStr = items.map((l) => ` ${l}`).join('\n');
170-
return `${label} {\n${itemsStr}\n}`;
171-
};
172-
if (this.allowedTransitionKeys.size === 0) {
173-
return 'No Transitions';
174-
}
175-
const checkNames = this.checks.flatMap((c) => c.name ?? []);
176-
const checkEndStates = this.checks.flatMap((c) => c.endState ?? []);
177-
const transitions =
178-
checkNames.length > 0 ? render('Transitions', checkNames) : undefined;
179-
const endStates =
180-
checkEndStates.length > 0
181-
? render('End States', checkEndStates)
182-
: undefined;
183-
if (transitions && endStates) {
184-
return `(${transitions} OR ${endStates})`;
185-
}
186-
return transitions ?? endStates!;
187-
}
151+
union(this: void, conditions: readonly this[]) {
152+
const checks = [
153+
...new Map(
154+
conditions
155+
.flatMap((condition) => condition.checks)
156+
.map((check) => {
157+
const key = check.name
158+
? `name:${check.name}`
159+
: `state:${check.endState!}`;
160+
return [key, check];
161+
}),
162+
).values(),
163+
];
164+
return new TransitionCondition(checks);
188165
}
189166

190-
return WorkflowEventGranterClass;
167+
intersect(this: void, conditions: readonly this[]) {
168+
const checks = [...conditions[0].checks].filter((check1) =>
169+
conditions.every((cond) =>
170+
cond.checks.some(
171+
(check2) =>
172+
check1.name === check2.name || check1.endState === check2.endState,
173+
),
174+
),
175+
);
176+
return new TransitionCondition(checks);
177+
}
178+
179+
[inspect.custom](_depth: number, _options: InspectOptionsStylized) {
180+
const render = (label: string, items: readonly string[]) => {
181+
const itemsStr = items.map((l) => ` ${l}`).join('\n');
182+
return `${label} {\n${itemsStr}\n}`;
183+
};
184+
if (this.allowedTransitionKeys.size === 0) {
185+
return 'No Transitions';
186+
}
187+
const checkNames = this.checks.flatMap((c) => c.name ?? []);
188+
const checkEndStates = this.checks.flatMap((c) => c.endState ?? []);
189+
const transitions =
190+
checkNames.length > 0 ? render('Transitions', checkNames) : undefined;
191+
const endStates =
192+
checkEndStates.length > 0
193+
? render('End States', checkEndStates)
194+
: undefined;
195+
if (transitions && endStates) {
196+
return `(${transitions} OR ${endStates})`;
197+
}
198+
return transitions ?? endStates!;
199+
}
191200
}
192201

193202
const TransitionKey = Symbol('TransitionKey');

0 commit comments

Comments
 (0)