Skip to content

Commit 97beee4

Browse files
authored
refactor: renames PureAbility to Ability (#1205)
BREAKING CHANGE: renames PureAbility to Ability and dops default options. In order, to get the previous behavior of `Ability` class, clients are expected to use `createMongoAbility` and `MongoAbility` type
1 parent 88d1540 commit 97beee4

16 files changed

Lines changed: 124 additions & 134 deletions

packages/casl-ability/spec/ability.spec.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { defineAbility, Ability, createAliasResolver, createMongoAbility, RuleOf, PureAbility } from '../src'
1+
import { defineAbility, Ability, createAliasResolver, createMongoAbility, RuleOf } from '../src'
22
import { Post, ruleToObject } from './fixtures'
33

44
describe('Ability', () => {
@@ -62,7 +62,7 @@ describe('Ability', () => {
6262
})
6363

6464
it('allows to check abilities only by action', () => {
65-
const ability = new PureAbility([{ action: 'read' }])
65+
const ability = new Ability([{ action: 'read' }])
6666

6767
expect(ability.can('read')).toBe(true)
6868
})
@@ -508,11 +508,16 @@ describe('Ability', () => {
508508

509509
it('throws exception if 3rd argument is passed but "fieldMatchher" options was not provided', () => {
510510
const rules = [{ action: 'read', subject: 'Post', fields: ['title'] }]
511-
expect(() => new PureAbility(rules)).toThrow(/"fieldMatcher" option/)
511+
expect(() => new Ability(rules)).toThrow(/"fieldMatcher" option/)
512+
})
513+
514+
it('throws exception if rule has conditions but "conditionsMatcher" options was not provided', () => {
515+
const rules = [{ action: 'read', subject: 'Post', conditions: { authorId: 1 } }]
516+
expect(() => new Ability(rules)).toThrow(/"conditionsMatcher" option/)
512517
})
513518

514519
it('throws if there is a rule with "fields" property to be an empty array', () => {
515-
expect(() => defineAbility(can => can('read', 'Post', []))).toThrow(/`rawRule.fields` cannot be an empty array/)
520+
expect(() => defineAbility(can => can('read', 'Post', []))).toThrow(/`rawRule.fields` array cannot be empty/)
516521
})
517522

518523
describe('when field patterns defined', () => {

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AbilityBuilder, defineAbility, PureAbility, createMongoAbility } from '../src'
1+
import { AbilityBuilder, defineAbility, Ability, createMongoAbility } from '../src'
22
import { Post, ruleToObject } from './spec_helper'
33

44
describe('AbilityBuilder', () => {
@@ -132,7 +132,7 @@ describe('AbilityBuilder', () => {
132132
cannot('read', 'Book', { private: true })
133133
})
134134

135-
expect(ability).to.be.instanceof(PureAbility)
135+
expect(ability).to.be.instanceof(Ability)
136136
expect(ability.rules.map(ruleToObject)).to.deep.equal([
137137
{ action: 'read', subject: 'Book' },
138138
{ inverted: true, action: 'read', subject: 'Book', conditions: { private: true } }
@@ -145,7 +145,7 @@ describe('AbilityBuilder', () => {
145145
cannot('read', 'Book', { private: true })
146146
})
147147

148-
expect(ability).to.be.instanceof(PureAbility)
148+
expect(ability).to.be.instanceof(Ability)
149149
expect(ability.rules.map(ruleToObject)).to.deep.equal([
150150
{ action: 'read', subject: 'Book' },
151151
{ inverted: true, action: 'read', subject: 'Book', conditions: { private: true } }

packages/casl-ability/spec/error.spec.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ForbiddenError, getDefaultErrorMessage, PureAbility, SubjectType } from '../src'
1+
import { ForbiddenError, getDefaultErrorMessage, Ability, SubjectType } from '../src'
22

33
describe('`ForbiddenError` class', () => {
44
describe('`throwUnlessCan` method', () => {
@@ -13,13 +13,13 @@ describe('`ForbiddenError` class', () => {
1313
})
1414

1515
it('raises error with context information', () => {
16-
let thrownError: ForbiddenError<PureAbility> | undefined
16+
let thrownError: ForbiddenError<Ability> | undefined
1717
const { error } = setup()
1818

1919
try {
2020
error.throwUnlessCan('archive', 'Post')
2121
} catch (abilityError) {
22-
thrownError = abilityError as ForbiddenError<PureAbility>
22+
thrownError = abilityError as ForbiddenError<Ability>
2323
}
2424

2525
expect(thrownError!.action).toBe('archive')
@@ -51,7 +51,7 @@ describe('`ForbiddenError` class', () => {
5151
it('correctly extracts subject type name from class subject types', () => {
5252
class Post {}
5353

54-
const ability = new PureAbility([
54+
const ability = new Ability([
5555
{ action: 'read', subject: Post }
5656
], {
5757
detectSubjectType: o => o.constructor as SubjectType
@@ -61,8 +61,8 @@ describe('`ForbiddenError` class', () => {
6161
ForbiddenError.from(ability).throwUnlessCan('update', Post)
6262
expect('this code').toBe('never reached')
6363
} catch (error) {
64-
expect((error as ForbiddenError<PureAbility>).subjectType).toBe('Post')
65-
expect((error as ForbiddenError<PureAbility>).message).toBe('Cannot execute "update" on "Post"')
64+
expect((error as ForbiddenError<Ability>).subjectType).toBe('Post')
65+
expect((error as ForbiddenError<Ability>).message).toBe('Cannot execute "update" on "Post"')
6666
}
6767
})
6868
})
@@ -81,7 +81,7 @@ describe('`ForbiddenError` class', () => {
8181
})
8282

8383
function setup() {
84-
const ability = new PureAbility([
84+
const ability = new Ability([
8585
{ action: 'read', subject: 'Post' }
8686
])
8787
const error = ForbiddenError.from(ability)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { defineAbility, PureAbility } from '../src'
1+
import { defineAbility, Ability } from '../src'
22
import { permittedFieldsOf } from '../src/extra'
33
import { Post } from './spec_helper'
44

@@ -8,7 +8,7 @@ describe('permittedFieldsOf', () => {
88
}
99

1010
it('returns an empty array for `Ability` with empty rules', () => {
11-
const ability = new PureAbility()
11+
const ability = new Ability()
1212
expect(permittedFieldsOf(ability, 'read', 'Post', defaultOptions)).to.be.empty
1313
})
1414

packages/casl-ability/spec/types/Ability.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
SubjectType,
66
createAliasResolver,
77
MongoAbility,
8-
PureAbility,
8+
Ability,
99
createMongoAbility,
1010
MongoQuery
1111
} from '../../src'
@@ -32,7 +32,7 @@ describe('Ability types', () => {
3232
})
3333

3434
it('can be casted to `AbilityClass`', () => {
35-
const AppAbility = PureAbility as AbilityClass<AppAbility>
35+
const AppAbility = Ability as AbilityClass<AppAbility>
3636
expectTypeOf(AppAbility).toEqualTypeOf<AbilityClass<AppAbility>>()
3737
})
3838

packages/casl-ability/spec/types/AbilityBuilder.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { expectTypeOf } from 'expect-type'
22
import {
33
AbilityBuilder,
4-
AbilityClass, AbilityTuple, createMongoAbility, MongoAbility, PureAbility
4+
AbilityClass, AbilityTuple, createMongoAbility, MongoAbility, Ability
55
} from '../../src'
66

77
describe('AbilityBuilder types', () => {
8-
it('infers types from `PureAbility` default generics', () => {
9-
const builder = new AbilityBuilder<PureAbility<AbilityTuple>>(PureAbility)
8+
it('infers types from `Ability` default generics', () => {
9+
const builder = new AbilityBuilder<Ability<AbilityTuple>>(Ability)
1010

1111
builder.can('read', 'Subject')
1212
builder.can('read', class {})
@@ -77,7 +77,7 @@ describe('AbilityBuilder types', () => {
7777
})
7878

7979
it('infers single action argument type from ClaimAbility', () => {
80-
const ClaimAbility = PureAbility as AbilityClass<PureAbility<string>>
80+
const ClaimAbility = Ability as AbilityClass<Ability<string>>
8181
// eslint-disable-next-line
8282
const builder = new AbilityBuilder(ClaimAbility)
8383
type Can = typeof builder.can

packages/casl-ability/src/Ability.ts

100644100755
Lines changed: 48 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,56 @@
1-
import { PureAbility, AbilityOptions, AbilityOptionsOf } from './PureAbility';
2-
import { RawRuleFrom } from './RawRule';
3-
import { AbilityTuple } from './types';
4-
import { MongoQuery, mongoQueryMatcher } from './matchers/conditions';
5-
import { fieldPatternMatcher } from './matchers/field';
6-
import { Public, RawRuleOf } from './RuleIndex';
1+
import { RuleIndex, RuleIndexOptions, RuleIndexOptionsOf, Public, RawRuleOf } from './RuleIndex';
2+
import { Abilities, AbilityTuple, CanParameters, Subject } from './types';
3+
import { Rule } from './Rule';
4+
5+
export interface AbilityOptions<A extends Abilities, Conditions>
6+
extends RuleIndexOptions<A, Conditions> {}
7+
export interface AnyAbility extends Public<Ability<any, any>> {}
8+
export interface AbilityOptionsOf<T extends AnyAbility> extends RuleIndexOptionsOf<T> {}
9+
10+
export type AbilityClass<T extends AnyAbility> = new (
11+
rules?: RawRuleOf<T>[],
12+
options?: AbilityOptionsOf<T>
13+
) => T;
14+
15+
export type CreateAbility<T extends AnyAbility> = (
16+
rules?: RawRuleOf<T>[],
17+
options?: AbilityOptionsOf<T>
18+
) => T;
719

8-
/**
9-
* @deprecated use createMongoAbility function instead and MongoAbility<Abilities> interface.
10-
* In the next major version PureAbility will be renamed to Ability and this class will be removed
11-
*/
1220
export class Ability<
13-
A extends AbilityTuple = AbilityTuple,
14-
C extends MongoQuery = MongoQuery
15-
> extends PureAbility<A, C> {
16-
constructor(rules: RawRuleFrom<A, C>[] = [], options: AbilityOptions<A, C> = {}) {
17-
super(rules, {
18-
conditionsMatcher: mongoQueryMatcher,
19-
fieldMatcher: fieldPatternMatcher,
20-
...options,
21-
});
21+
A extends Abilities = AbilityTuple,
22+
Conditions = unknown
23+
> extends RuleIndex<A, Conditions> {
24+
can(...args: CanParameters<A>): boolean;
25+
can(action: string, subject?: Subject, field?: string): boolean {
26+
const rule = (this as PrimitiveAbility).relevantRuleFor(action, subject, field);
27+
return !!rule && !rule.inverted;
2228
}
23-
}
2429

25-
export interface AnyMongoAbility extends Public<PureAbility<any, MongoQuery>> {}
26-
export interface MongoAbility<
27-
A extends AbilityTuple = AbilityTuple,
28-
C extends MongoQuery = MongoQuery
29-
> extends PureAbility<A, C> {}
30+
relevantRuleFor(...args: CanParameters<A>): Rule<A, Conditions> | null;
31+
relevantRuleFor(action: string, subject?: Subject, field?: string): Rule<A, Conditions> | null {
32+
const subjectType = this.detectSubjectType(subject);
33+
const rules = (this as any).rulesFor(action, subjectType, field);
34+
35+
for (let i = 0, length = rules.length; i < length; i++) {
36+
if (rules[i].matchesConditions(subject)) {
37+
return rules[i];
38+
}
39+
}
40+
41+
return null;
42+
}
43+
44+
cannot(...args: CanParameters<A>): boolean;
45+
cannot(action: string, subject?: Subject, field?: string): boolean {
46+
return !(this as PrimitiveAbility).can(action, subject, field);
47+
}
48+
}
3049

3150
/**
32-
* Creates Ability with MongoDB conditions matcher
51+
* helper interface that helps to emit js methods that have static parameters
3352
*/
34-
export function createMongoAbility<
35-
T extends AnyMongoAbility = MongoAbility
36-
>(rules?: RawRuleOf<T>[], options?: AbilityOptionsOf<T>): T;
37-
export function createMongoAbility<
38-
A extends AbilityTuple = AbilityTuple,
39-
C extends MongoQuery = MongoQuery
40-
>(rules?: RawRuleFrom<A, C>[], options?: AbilityOptions<A, C>): MongoAbility<A, C>;
41-
export function createMongoAbility(rules: any[] = [], options = {}): AnyMongoAbility {
42-
return new PureAbility(rules, {
43-
conditionsMatcher: mongoQueryMatcher,
44-
fieldMatcher: fieldPatternMatcher,
45-
...options,
46-
});
53+
interface PrimitiveAbility<A extends Abilities = AbilityTuple, Conditions = unknown> {
54+
can(action: string, subject?: Subject, field?: string): boolean;
55+
relevantRuleFor(action: string, subject?: Subject, field?: string): Rule<A, Conditions> | null
4756
}

packages/casl-ability/src/AbilityBuilder.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { AnyMongoAbility, createMongoAbility, MongoAbility } from './Ability';
2-
import { ProduceGeneric } from './hkt';
3-
import { AbilityOptionsOf, AnyAbility } from './PureAbility';
4-
import { Generics, RawRuleOf } from './RuleIndex';
5-
import {
1+
import { type AnyMongoAbility, createMongoAbility, type MongoAbility } from './createMongoAbility';
2+
import type { ProduceGeneric } from './hkt';
3+
import type { AbilityOptionsOf, AnyAbility } from './Ability';
4+
import type { Generics, RawRuleOf } from './RuleIndex';
5+
import type {
66
AbilityTuple, AnyClass, AnyObject, ExtractSubjectType as E, Normalize, SubjectType,
77
TaggedInterface
88
} from './types';

packages/casl-ability/src/ForbiddenError.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { AnyAbility } from './PureAbility';
2-
import { Normalize, Subject } from './types';
3-
import { Generics } from './RuleIndex';
1+
import type { AnyAbility } from './Ability';
2+
import type { Normalize, Subject } from './types';
3+
import type { Generics } from './RuleIndex';
44
import { getSubjectTypeName } from './utils';
55

66
export type GetErrorMessage = (error: ForbiddenError<AnyAbility>) => string;

packages/casl-ability/src/PureAbility.ts

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

0 commit comments

Comments
 (0)