Skip to content

Commit

Permalink
fix(transition): Normalize error() to always return Rejection
Browse files Browse the repository at this point in the history
In a7464bb we normalized the `Transition.error()` to always return a `Rejection` object.  However, we missed two cases: transition invalid and/or invalid parameter values.  This fixes those two cases.

We also updated the docs and return types (switch from `any` to `Rejection`) to better reflect this change.
  • Loading branch information
christopherthielen committed Apr 15, 2018
1 parent 453b028 commit 9bcc5db
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 12 deletions.
2 changes: 1 addition & 1 deletion src/hooks/invalidTransition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { Transition } from '../transition/transition';
*/
function invalidTransitionHook(trans: Transition) {
if (!trans.valid()) {
throw new Error(trans.error());
throw new Error(trans.error().toString());
}
}

Expand Down
69 changes: 68 additions & 1 deletion src/transition/rejectFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,88 @@ import { stringify } from '../common/strings';
import { is } from '../common/hof';

export enum RejectType {
/**
* A new transition superseded this one.
*
* While this transition was running, a new transition started.
* This transition is cancelled because it was superseded by new transition.
*/
SUPERSEDED = 2,

/**
* The transition was aborted
*
* The transition was aborted by a hook which returned `false`
*/
ABORTED = 3,

/**
* The transition was invalid
*
* The transition was never started because it was invalid
*/
INVALID = 4,

/**
* The transition was ignored
*
* The transition was ignored because it would have no effect.
*
* Either:
*
* - The transition is targeting the current state and parameter values
* - The transition is targeting the same state and parameter values as the currently running transition.
*/
IGNORED = 5,

/**
* The transition errored.
*
* This generally means a hook threw an error or returned a rejected promise
*/
ERROR = 6,
}

/** @hidden */
let id = 0;

export class Rejection {
/** @hidden */
$id = id++;
type: number;
/**
* The type of the rejection.
*
* This value is an number representing the type of transition rejection.
* If using Typescript, this is a Typescript enum.
*
* - [[RejectType.SUPERSEDED]] (`2`)
* - [[RejectType.ABORTED]] (`3`)
* - [[RejectType.INVALID]] (`4`)
* - [[RejectType.IGNORED]] (`5`)
* - [[RejectType.ERROR]] (`6`)
*
*/
type: RejectType;

/**
* A message describing the rejection
*/
message: string;

/**
* A detail object
*
* This value varies based on the mechanism for rejecting the transition.
* For example, if an error was thrown from a hook, the `detail` will be the `Error` object.
* If a hook returned a rejected promise, the `detail` will be the rejected value.
*/
detail: any;

/**
* Indicates if the transition was redirected.
*
* When a transition is redirected, the rejection [[type]] will be [[RejectType.SUPERSEDED]] and this flag will be true.
*/
redirected: boolean;

/** Returns true if the obj is a rejected promise created from the `asPromise` factory */
Expand Down
24 changes: 14 additions & 10 deletions src/transition/transition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { UIRouter } from '../router';
import { UIInjector } from '../interface';
import { RawParams } from '../params/interface';
import { ResolvableLiteral } from '../resolve/interface';
import { Rejection } from './rejectFactory';

/** @hidden */
const stateSelf: (_state: StateObject) => StateDeclaration = prop('self');
Expand Down Expand Up @@ -87,7 +88,7 @@ export class Transition implements IHookRegistry {
/** @hidden */
_aborted: boolean;
/** @hidden */
private _error: any;
private _error: Rejection;

/** @hidden Holds the hook registration functions such as those passed to Transition.onStart() */
_registeredHooks: RegisteredHooks = {};
Expand Down Expand Up @@ -703,7 +704,7 @@ export class Transition implements IHookRegistry {
runAllHooks(getHooksFor(TransitionHookPhase.SUCCESS));
};

const transitionError = (reason: any) => {
const transitionError = (reason: Rejection) => {
trace.traceError(reason, this);
this.success = false;
this._deferred.reject(reason);
Expand Down Expand Up @@ -770,20 +771,23 @@ export class Transition implements IHookRegistry {
* If the transition is invalid (and could not be run), returns the reason the transition is invalid.
* If the transition was valid and ran, but was not successful, returns the reason the transition failed.
*
* @returns an error message explaining why the transition is invalid, or the reason the transition failed.
* @returns a transition rejection explaining why the transition is invalid, or the reason the transition failed.
*/
error() {
error(): Rejection {
const state: StateObject = this.$to();

if (state.self.abstract) return `Cannot transition to abstract state '${state.name}'`;
if (state.self.abstract) {
return Rejection.invalid(`Cannot transition to abstract state '${state.name}'`);
}

const paramDefs = state.parameters(),
values = this.params();
const paramDefs = state.parameters();
const values = this.params();
const invalidParams = paramDefs.filter(param => !param.validates(values[param.id]));

if (invalidParams.length) {
return `Param values not valid for state '${state.name}'. Invalid params: [ ${invalidParams
.map(param => param.id)
.join(', ')} ]`;
const invalidValues = invalidParams.map(param => `[${param.id}:${stringify(values[param.id])}]`).join(', ');
const detail = `The following parameter values are not valid for state '${state.name}': ${invalidValues}`;
return Rejection.invalid(detail);
}

if (this.success === false) return this._error;
Expand Down

0 comments on commit 9bcc5db

Please sign in to comment.