Skip to content

Commit

Permalink
refactor(ability): makes ForbiddenError to be newable type
Browse files Browse the repository at this point in the history
Relates to #248
  • Loading branch information
stalniy committed Feb 10, 2020
1 parent ea164b5 commit 31fd387
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 55 deletions.
1 change: 1 addition & 0 deletions packages/casl-ability/spec/ability.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ describe('Ability', () => {
spy.on(console, 'warn')
ability.update([{ inverted: true, action: 'read', subject: 'Post' }])

// eslint-disable-next-line
expect(console.warn).to.have.been.called()

spy.restore(console, 'warn')
Expand Down
5 changes: 2 additions & 3 deletions packages/casl-ability/spec/query.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import { AbilityBuilder } from '../src'
import { rulesToQuery } from '../src/extra'

function toQuery(ability, action, subject) {
return rulesToQuery(ability, action, subject, (rule) => {
return rule.inverted ? { $not: rule.conditions } : rule.conditions
})
const convert = rule => rule.inverted ? { $not: rule.conditions } : rule.conditions
return rulesToQuery(ability, action, subject, convert)
}

describe('rulesToQuery', () => {
Expand Down
93 changes: 41 additions & 52 deletions packages/casl-ability/src/ForbiddenError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,94 +13,83 @@ type ForbiddenErrorMeta = {
subjectName: string
};

interface ForbiddenErrorType {
(this: ForbiddenError, message: string, options?: ForbiddenErrorMeta): void
interface ForbiddenErrorContructor {
new (ability: Ability, options?: ForbiddenErrorMeta): ForbiddenError
from: (ability: Ability) => ForbiddenError
setDefaultMessage(messageOrFn: string | GetErrorMessage): void
}

type ExtendedError = Error & ForbiddenErrorMeta;

interface ForbiddenError extends ExtendedError {
_customMessage: string | null
_ability?: Ability
_ability: Ability

setMessage(message: string | null): this
throwUnlessCan(action: string, subject: AbilitySubject, field?: string): void
_setMetadata(meta: ForbiddenErrorMeta): void
}

const ForbiddenError: ForbiddenErrorType = Object.assign(
function ForbiddenError(this: ForbiddenError, message: string, options?: ForbiddenErrorMeta) {
Error.call(this, message);

if (options) {
this._setMetadata(options);
}
function setMeta(error: ForbiddenError, meta?: ForbiddenErrorMeta) {
if (meta) {
error.subject = meta.subject;
error.subjectName = meta.subjectName;
error.action = meta.action;
error.field = meta.field;
}
}

this.message = message || defaultErrorMessage(this);
this._customMessage = null;
const ForbiddenError = function ForbiddenError(
this: ForbiddenError,
ability: Ability,
options?: ForbiddenErrorMeta
) {
Error.call(this, '');
this._ability = ability;
setMeta(this, options);

if (typeof Error.captureStackTrace === 'function') {
this.name = this.constructor.name;
Error.captureStackTrace(this, this.constructor);
}
} as Function as ForbiddenErrorContructor;

if (typeof Error.captureStackTrace === 'function') {
this.name = this.constructor.name;
Error.captureStackTrace(this, this.constructor);
}
},
{
setDefaultMessage(messageOrFn: string | GetErrorMessage): void {
if (messageOrFn === null) {
defaultErrorMessage = getDefaultMessage;
} else {
defaultErrorMessage = typeof messageOrFn === 'string' ? () => messageOrFn : messageOrFn;
}
},

from(ability: Ability): ForbiddenError {
const error = new (this as any)('');
Object.defineProperty(error, '_ability', { value: ability });
return error;
}
ForbiddenError.setDefaultMessage = function setMessage(messageOrFn: string | GetErrorMessage) {
if (messageOrFn === null) {
defaultErrorMessage = getDefaultMessage;
} else {
defaultErrorMessage = typeof messageOrFn === 'string' ? () => messageOrFn : messageOrFn;
}
);
};

ForbiddenError.from = function fromAbility(ability: Ability): ForbiddenError {
return new this(ability);
};

ForbiddenError.prototype = Object.create(Error.prototype);
ForbiddenError.prototype.constructor = ForbiddenError;
ForbiddenError.prototype = Object.assign(Object.create(Error.prototype), {
constructor: ForbiddenError,

Object.assign(ForbiddenError.prototype, {
setMessage(this: ForbiddenError, message: string | null): ForbiddenError {
this._customMessage = message;
setMessage(this: ForbiddenError, message: string) {
this.message = message;
return this;
},

throwUnlessCan(this: ForbiddenError, action: string, subject: AbilitySubject, field?: string) {
if (!this._ability) {
throw new ReferenceError('Cannot throw FordiddenError without respective ability instance');
}

const rule = this._ability.relevantRuleFor(action, subject, field);

if (rule && !rule.inverted) {
return;
}

this._setMetadata({
setMeta(this, {
action,
subject,
field,
subjectName: this._ability.subjectName(subject)
});

const reason = rule ? rule.reason : '';
this.message = this._customMessage || reason || defaultErrorMessage(this);
this.message = this.message || reason || defaultErrorMessage(this);
throw this; // eslint-disable-line
},

_setMetadata(this: ForbiddenError, meta: ForbiddenErrorMeta): void {
this.subject = meta.subject;
this.subjectName = meta.subjectName;
this.action = meta.action;
this.field = meta.field;
},
});

export default ForbiddenError;

0 comments on commit 31fd387

Please sign in to comment.