Skip to content

Commit 4737fe2

Browse files
authored
feat: adds possibility to auto detect subject type based on passed rules (#882)
1 parent 71bb1fb commit 4737fe2

File tree

3 files changed

+32
-16
lines changed

3 files changed

+32
-16
lines changed

packages/casl-ability/spec/ability.spec.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,6 @@ describe('Ability', () => {
114114
ability = defineAbility((can) => {
115115
can('read', Post)
116116
can('update', Post, { authorId: 1 })
117-
}, {
118-
detectSubjectType: object => object.constructor
119117
})
120118

121119
expect(ability).to.allow('read', Post)

packages/casl-ability/src/RuleIndex.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
AbilityTuple,
99
ExtractSubjectType
1010
} from './types';
11-
import { wrapArray, detectSubjectType, mergePrioritized, getOrDefault, identity, isSubjectType } from './utils';
11+
import { wrapArray, detectSubjectType, mergePrioritized, getOrDefault, identity, isSubjectType, DETECT_SUBJECT_TYPE_STRATEGY } from './utils';
1212
import { LinkedItem, linkedItem, unlinkItem, cloneLinkedItem } from './structures/LinkedItem';
1313

1414
export interface RuleIndexOptions<A extends Abilities, C> extends Partial<RuleOptions<C>> {
@@ -91,12 +91,13 @@ type AbilitySubjectTypeParameters<T extends Abilities, IncludeField extends bool
9191
export class RuleIndex<A extends Abilities, Conditions> {
9292
private _hasPerFieldRules: boolean = false;
9393
private _events?: Events<this>;
94-
private _indexedRules: IndexTree<A, Conditions>;
94+
private _indexedRules: IndexTree<A, Conditions> = new Map();
9595
private _rules: RawRuleFrom<A, Conditions>[];
9696
private readonly _ruleOptions: RuleOptions<Conditions>;
97-
private readonly _detectSubjectType: this['detectSubjectType'];
97+
private _detectSubjectType: this['detectSubjectType'];
9898
private readonly _anyAction: string;
9999
private readonly _anySubjectType: string;
100+
private readonly _hasCustomSubjectTypeDetection: boolean;
100101
readonly [ɵabilities]!: A;
101102
readonly [ɵconditions]!: Conditions;
102103

@@ -111,9 +112,10 @@ export class RuleIndex<A extends Abilities, Conditions> {
111112
};
112113
this._anyAction = options.anyAction || 'manage';
113114
this._anySubjectType = options.anySubjectType || 'all';
114-
this._detectSubjectType = options.detectSubjectType || (detectSubjectType as this['detectSubjectType']);
115115
this._rules = rules;
116-
this._indexedRules = this._buildIndexFor(rules);
116+
this._hasCustomSubjectTypeDetection = !!options.detectSubjectType;
117+
this._detectSubjectType = options.detectSubjectType || (detectSubjectType as this['detectSubjectType']);
118+
this._indexAndAnalyzeRules(rules);
117119
}
118120

119121
get rules() {
@@ -135,14 +137,15 @@ export class RuleIndex<A extends Abilities, Conditions> {
135137

136138
this._emit('update', event);
137139
this._rules = rules;
138-
this._indexedRules = this._buildIndexFor(rules);
140+
this._indexAndAnalyzeRules(rules);
139141
this._emit('updated', event);
140142

141143
return this;
142144
}
143145

144-
private _buildIndexFor(rawRules: RawRuleFrom<A, Conditions>[]) {
146+
private _indexAndAnalyzeRules(rawRules: RawRuleFrom<A, Conditions>[]) {
145147
const indexedRules: IndexTree<A, Conditions> = new Map();
148+
let typeOfSubjectType: string | undefined;
146149

147150
for (let i = rawRules.length - 1; i >= 0; i--) {
148151
const priority = rawRules.length - i - 1;
@@ -153,14 +156,24 @@ export class RuleIndex<A extends Abilities, Conditions> {
153156

154157
for (let k = 0; k < subjects.length; k++) {
155158
const subjectRules = getOrDefault(indexedRules, subjects[k], defaultSubjectEntry);
159+
if (typeOfSubjectType === undefined) {
160+
typeOfSubjectType = typeof subjects[k];
161+
}
162+
if (typeof subjects[k] !== typeOfSubjectType && typeOfSubjectType !== 'mixed') {
163+
typeOfSubjectType = 'mixed';
164+
}
156165

157166
for (let j = 0; j < actions.length; j++) {
158167
getOrDefault(subjectRules, actions[j], defaultActionEntry).rules.push(rule);
159168
}
160169
}
161170
}
162171

163-
return indexedRules;
172+
this._indexedRules = indexedRules;
173+
if (typeOfSubjectType !== 'mixed' && !this._hasCustomSubjectTypeDetection) {
174+
const detectSubjectType = DETECT_SUBJECT_TYPE_STRATEGY[typeOfSubjectType as 'function' | 'string'] || DETECT_SUBJECT_TYPE_STRATEGY.string;
175+
this._detectSubjectType = detectSubjectType as this['detectSubjectType'];
176+
}
164177
}
165178

166179
possibleRulesFor(...args: AbilitySubjectTypeParameters<A, false>): Rule<A, Conditions>[];

packages/casl-ability/src/utils.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,23 @@ export const isSubjectType = (value: unknown): value is SubjectType => {
4343
};
4444

4545
const getSubjectClassName = (value: SubjectClass) => value.modelName || value.name;
46-
export const getSubjectTypeName = (value: SubjectType) => {
46+
export function getSubjectTypeName(value: SubjectType) {
4747
return typeof value === 'string' ? value : getSubjectClassName(value);
48-
};
48+
}
4949

50-
export function detectSubjectType(subject: Exclude<Subject, SubjectType>): string {
51-
if (Object.hasOwn(subject, TYPE_FIELD)) {
52-
return subject[TYPE_FIELD];
50+
export function detectSubjectType(object: Exclude<Subject, SubjectType>): string {
51+
if (Object.hasOwn(object, TYPE_FIELD)) {
52+
return object[TYPE_FIELD];
5353
}
5454

55-
return getSubjectClassName(subject.constructor as SubjectClass);
55+
return getSubjectClassName(object.constructor as SubjectClass);
5656
}
5757

58+
export const DETECT_SUBJECT_TYPE_STRATEGY = {
59+
function: (object: Exclude<Subject, SubjectType>) => object.constructor as SubjectClass,
60+
string: detectSubjectType
61+
};
62+
5863
type AliasMerge = (actions: string[], action: string | string[]) => string[];
5964
function expandActions(aliasMap: AliasesMap, rawActions: string | string[], merge: AliasMerge) {
6065
let actions = wrapArray(rawActions);

0 commit comments

Comments
 (0)