Skip to content

Commit

Permalink
feat: refactors extra subpackage in casl/ability and adds AccessibleF…
Browse files Browse the repository at this point in the history
…ields helper class
  • Loading branch information
stalniy committed Feb 23, 2024
1 parent dd97c3e commit 5806cd3
Show file tree
Hide file tree
Showing 12 changed files with 243 additions and 202 deletions.
2 changes: 1 addition & 1 deletion packages/casl-ability/extra.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from './extra/extra';
export * from './dist/types/extra';
1 change: 0 additions & 1 deletion packages/casl-ability/extra/extra.d.ts

This file was deleted.

4 changes: 2 additions & 2 deletions packages/casl-ability/extra/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@casl/ability/extra",
"typings": "./extra.d.ts",
"typings": "../dist/types/extra/index.d.ts",
"main": "../dist/umd/extra.js",
"module": "../dist/es5m/extra.js",
"es2015": "../dist/es6/extra.js"
"es2015": "../dist/es6c/extra.js"
}
7 changes: 0 additions & 7 deletions packages/casl-ability/index.metadata.json

This file was deleted.

14 changes: 6 additions & 8 deletions packages/casl-ability/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
"require": "./dist/es6c/index.js"
},
"./extra": {
"types": "./dist/types/extra.d.ts",
"import": "./dist/es6m/extra.mjs",
"require": "./dist/es6c/extra.js"
"types": "./dist/types/extra/index.d.ts",
"import": "./dist/es6m/extra/index.mjs",
"require": "./dist/es6c/extra/index.js"
}
},
"sideEffects": false,
Expand All @@ -32,16 +32,14 @@
},
"scripts": {
"build.core": "dx rollup -n casl -g @ucast/mongo2js:ucast.mongo2js",
"build.extra": "dx rollup -i src/extra.ts -n casl.extra -g @ucast/mongo2js:ucast.mongo2js",
"build.types": "dx tsc && cp index.metadata.json dist/types",
"build.extra": "dx rollup -i src/extra/index.ts -n casl.extra -g @ucast/mongo2js:ucast.mongo2js",
"build.types": "dx tsc",
"prebuild": "rm -rf dist/*",
"build": "npm run build.types && npm run build.core && npm run build.extra",
"lint": "dx eslint src/ spec/",
"test": "dx jest",
"prerelease": "npm run lint && npm test && NODE_ENV=production npm run build",
"release": "dx semantic-release",
"preb": "echo 'pre'",
"b": "echo 123"
"release": "dx semantic-release"
},
"keywords": [
"permissions",
Expand Down
182 changes: 0 additions & 182 deletions packages/casl-ability/src/extra.ts

This file was deleted.

5 changes: 5 additions & 0 deletions packages/casl-ability/src/extra/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './packRules';
export * from './permittedFieldsOf';
export * from './rulesToFields';
export * from './rulesToQuery';
export * from './packRules';
61 changes: 61 additions & 0 deletions packages/casl-ability/src/extra/packRules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@

import { RawRule } from '../RawRule';
import { SubjectType } from '../types';
import { wrapArray } from '../utils';

const joinIfArray = (value: string | string[]) => Array.isArray(value) ? value.join(',') : value;

export type PackRule<T extends RawRule<any, any>> =
[string, string] |
[string, string, T['conditions']] |
[string, string, T['conditions'] | 0, 1] |
[string, string, T['conditions'] | 0, 1 | 0, string] |
[string, string, T['conditions'] | 0, 1 | 0, string | 0, string];

export type PackSubjectType<T extends SubjectType> = (type: T) => string;

export function packRules<T extends RawRule<any, any>>(
rules: T[],
packSubject?: PackSubjectType<T['subject']>
): PackRule<T>[] {
return rules.map((rule) => { // eslint-disable-line
const packedRule: PackRule<T> = [
joinIfArray((rule as any).action || (rule as any).actions),
typeof packSubject === 'function'
? wrapArray(rule.subject).map(packSubject).join(',')
: joinIfArray(rule.subject),
rule.conditions || 0,
rule.inverted ? 1 : 0,
rule.fields ? joinIfArray(rule.fields) : 0,
rule.reason || ''
];

while (packedRule.length > 0 && !packedRule[packedRule.length - 1]) packedRule.pop();

return packedRule;
});
}

export type UnpackSubjectType<T extends SubjectType> = (type: string) => T;

export function unpackRules<T extends RawRule<any, any>>(
rules: PackRule<T>[],
unpackSubject?: UnpackSubjectType<T['subject']>
): T[] {
return rules.map(([action, subject, conditions, inverted, fields, reason]) => {
const subjects = subject.split(',');
const rule = {
inverted: !!inverted,
action: action.split(','),
subject: typeof unpackSubject === 'function'
? subjects.map(unpackSubject)
: subjects
} as T;

if (conditions) rule.conditions = conditions;
if (fields) rule.fields = fields.split(',');
if (reason) rule.reason = reason;

return rule;
});
}
69 changes: 69 additions & 0 deletions packages/casl-ability/src/extra/permittedFieldsOf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { AnyAbility } from '../PureAbility';
import { Rule } from '../Rule';
import { RuleOf } from '../RuleIndex';
import { Subject, SubjectType } from '../types';

export type GetRuleFields<R extends Rule<any, any>> = (rule: R) => string[];

export interface PermittedFieldsOptions<T extends AnyAbility> {
fieldsFrom: GetRuleFields<RuleOf<T>>
}

export function permittedFieldsOf<T extends AnyAbility>(
ability: T,
action: Parameters<T['can']>[0],
subject: Parameters<T['can']>[1],
options: PermittedFieldsOptions<T>
): string[] {
const subjectType = ability.detectSubjectType(subject);
const rules = ability.possibleRulesFor(action, subjectType);
const uniqueFields = new Set<string>();
const deleteItem = uniqueFields.delete.bind(uniqueFields);
const addItem = uniqueFields.add.bind(uniqueFields);
let i = rules.length;

while (i--) {
const rule = rules[i];
if (rule.matchesConditions(subject)) {
const toggle = rule.inverted ? deleteItem : addItem;
options.fieldsFrom(rule).forEach(toggle);
}
}

return Array.from(uniqueFields);
}

export type GetSubjectTypeAllFieldsExtractor = (subjectType: SubjectType) => string[];

/**
* Helper class to make custom `accessibleFieldsBy` helper function
*/
export class AccessibleFields<T extends Subject> {
constructor(
private readonly _ability: AnyAbility,
private readonly _action: string,
private readonly _getAllFields: GetSubjectTypeAllFieldsExtractor
) {}

/**
* Returns accessible fields for Model type
*/
ofType(subjectType: Extract<T, SubjectType>): string[] {
return permittedFieldsOf(this._ability, this._action, subjectType, {
fieldsFrom: this._getRuleFields(subjectType)
});
}

/**
* Returns accessible fields for particular document
*/
of(subject: Exclude<T, SubjectType>): string[] {
return permittedFieldsOf(this._ability, this._action, subject, {
fieldsFrom: this._getRuleFields(this._ability.detectSubjectType(subject))
});
}

private _getRuleFields(type: SubjectType): GetRuleFields<RuleOf<AnyAbility>> {
return (rule) => (rule.fields || this._getAllFields(type));
}
}
Loading

0 comments on commit 5806cd3

Please sign in to comment.