Skip to content

Commit 9d2ad70

Browse files
authored
feat: refactors extra subpackage in casl/ability and adds AccessibleFields helper class (#883)
1 parent dd97c3e commit 9d2ad70

File tree

12 files changed

+243
-202
lines changed

12 files changed

+243
-202
lines changed

packages/casl-ability/extra.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export * from './extra/extra';
1+
export * from './dist/types/extra';

packages/casl-ability/extra/extra.d.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@casl/ability/extra",
3-
"typings": "./extra.d.ts",
3+
"typings": "../dist/types/extra/index.d.ts",
44
"main": "../dist/umd/extra.js",
55
"module": "../dist/es5m/extra.js",
6-
"es2015": "../dist/es6/extra.js"
6+
"es2015": "../dist/es6c/extra.js"
77
}

packages/casl-ability/index.metadata.json

Lines changed: 0 additions & 7 deletions
This file was deleted.

packages/casl-ability/package.json

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
"require": "./dist/es6c/index.js"
1616
},
1717
"./extra": {
18-
"types": "./dist/types/extra.d.ts",
19-
"import": "./dist/es6m/extra.mjs",
20-
"require": "./dist/es6c/extra.js"
18+
"types": "./dist/types/extra/index.d.ts",
19+
"import": "./dist/es6m/extra/index.mjs",
20+
"require": "./dist/es6c/extra/index.js"
2121
}
2222
},
2323
"sideEffects": false,
@@ -32,16 +32,14 @@
3232
},
3333
"scripts": {
3434
"build.core": "dx rollup -n casl -g @ucast/mongo2js:ucast.mongo2js",
35-
"build.extra": "dx rollup -i src/extra.ts -n casl.extra -g @ucast/mongo2js:ucast.mongo2js",
36-
"build.types": "dx tsc && cp index.metadata.json dist/types",
35+
"build.extra": "dx rollup -i src/extra/index.ts -n casl.extra -g @ucast/mongo2js:ucast.mongo2js",
36+
"build.types": "dx tsc",
3737
"prebuild": "rm -rf dist/*",
3838
"build": "npm run build.types && npm run build.core && npm run build.extra",
3939
"lint": "dx eslint src/ spec/",
4040
"test": "dx jest",
4141
"prerelease": "npm run lint && npm test && NODE_ENV=production npm run build",
42-
"release": "dx semantic-release",
43-
"preb": "echo 'pre'",
44-
"b": "echo 123"
42+
"release": "dx semantic-release"
4543
},
4644
"keywords": [
4745
"permissions",

packages/casl-ability/src/extra.ts

Lines changed: 0 additions & 182 deletions
This file was deleted.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export * from './packRules';
2+
export * from './permittedFieldsOf';
3+
export * from './rulesToFields';
4+
export * from './rulesToQuery';
5+
export * from './packRules';
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
2+
import { RawRule } from '../RawRule';
3+
import { SubjectType } from '../types';
4+
import { wrapArray } from '../utils';
5+
6+
const joinIfArray = (value: string | string[]) => Array.isArray(value) ? value.join(',') : value;
7+
8+
export type PackRule<T extends RawRule<any, any>> =
9+
[string, string] |
10+
[string, string, T['conditions']] |
11+
[string, string, T['conditions'] | 0, 1] |
12+
[string, string, T['conditions'] | 0, 1 | 0, string] |
13+
[string, string, T['conditions'] | 0, 1 | 0, string | 0, string];
14+
15+
export type PackSubjectType<T extends SubjectType> = (type: T) => string;
16+
17+
export function packRules<T extends RawRule<any, any>>(
18+
rules: T[],
19+
packSubject?: PackSubjectType<T['subject']>
20+
): PackRule<T>[] {
21+
return rules.map((rule) => { // eslint-disable-line
22+
const packedRule: PackRule<T> = [
23+
joinIfArray((rule as any).action || (rule as any).actions),
24+
typeof packSubject === 'function'
25+
? wrapArray(rule.subject).map(packSubject).join(',')
26+
: joinIfArray(rule.subject),
27+
rule.conditions || 0,
28+
rule.inverted ? 1 : 0,
29+
rule.fields ? joinIfArray(rule.fields) : 0,
30+
rule.reason || ''
31+
];
32+
33+
while (packedRule.length > 0 && !packedRule[packedRule.length - 1]) packedRule.pop();
34+
35+
return packedRule;
36+
});
37+
}
38+
39+
export type UnpackSubjectType<T extends SubjectType> = (type: string) => T;
40+
41+
export function unpackRules<T extends RawRule<any, any>>(
42+
rules: PackRule<T>[],
43+
unpackSubject?: UnpackSubjectType<T['subject']>
44+
): T[] {
45+
return rules.map(([action, subject, conditions, inverted, fields, reason]) => {
46+
const subjects = subject.split(',');
47+
const rule = {
48+
inverted: !!inverted,
49+
action: action.split(','),
50+
subject: typeof unpackSubject === 'function'
51+
? subjects.map(unpackSubject)
52+
: subjects
53+
} as T;
54+
55+
if (conditions) rule.conditions = conditions;
56+
if (fields) rule.fields = fields.split(',');
57+
if (reason) rule.reason = reason;
58+
59+
return rule;
60+
});
61+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { AnyAbility } from '../PureAbility';
2+
import { Rule } from '../Rule';
3+
import { RuleOf } from '../RuleIndex';
4+
import { Subject, SubjectType } from '../types';
5+
6+
export type GetRuleFields<R extends Rule<any, any>> = (rule: R) => string[];
7+
8+
export interface PermittedFieldsOptions<T extends AnyAbility> {
9+
fieldsFrom: GetRuleFields<RuleOf<T>>
10+
}
11+
12+
export function permittedFieldsOf<T extends AnyAbility>(
13+
ability: T,
14+
action: Parameters<T['can']>[0],
15+
subject: Parameters<T['can']>[1],
16+
options: PermittedFieldsOptions<T>
17+
): string[] {
18+
const subjectType = ability.detectSubjectType(subject);
19+
const rules = ability.possibleRulesFor(action, subjectType);
20+
const uniqueFields = new Set<string>();
21+
const deleteItem = uniqueFields.delete.bind(uniqueFields);
22+
const addItem = uniqueFields.add.bind(uniqueFields);
23+
let i = rules.length;
24+
25+
while (i--) {
26+
const rule = rules[i];
27+
if (rule.matchesConditions(subject)) {
28+
const toggle = rule.inverted ? deleteItem : addItem;
29+
options.fieldsFrom(rule).forEach(toggle);
30+
}
31+
}
32+
33+
return Array.from(uniqueFields);
34+
}
35+
36+
export type GetSubjectTypeAllFieldsExtractor = (subjectType: SubjectType) => string[];
37+
38+
/**
39+
* Helper class to make custom `accessibleFieldsBy` helper function
40+
*/
41+
export class AccessibleFields<T extends Subject> {
42+
constructor(
43+
private readonly _ability: AnyAbility,
44+
private readonly _action: string,
45+
private readonly _getAllFields: GetSubjectTypeAllFieldsExtractor
46+
) {}
47+
48+
/**
49+
* Returns accessible fields for Model type
50+
*/
51+
ofType(subjectType: Extract<T, SubjectType>): string[] {
52+
return permittedFieldsOf(this._ability, this._action, subjectType, {
53+
fieldsFrom: this._getRuleFields(subjectType)
54+
});
55+
}
56+
57+
/**
58+
* Returns accessible fields for particular document
59+
*/
60+
of(subject: Exclude<T, SubjectType>): string[] {
61+
return permittedFieldsOf(this._ability, this._action, subject, {
62+
fieldsFrom: this._getRuleFields(this._ability.detectSubjectType(subject))
63+
});
64+
}
65+
66+
private _getRuleFields(type: SubjectType): GetRuleFields<RuleOf<AnyAbility>> {
67+
return (rule) => (rule.fields || this._getAllFields(type));
68+
}
69+
}

0 commit comments

Comments
 (0)