Skip to content

Commit

Permalink
feat: add $try expression for try-catch logic
Browse files Browse the repository at this point in the history
  • Loading branch information
simonfan committed Apr 23, 2021
1 parent 444d162 commit 848251f
Show file tree
Hide file tree
Showing 4 changed files with 235 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/__snapshots__/index.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ Array [
"$stringTrim",
"$switch",
"$switchKey",
"$try",
"$type",
"$value",
"$xor",
Expand Down
133 changes: 133 additions & 0 deletions src/expressions/functional.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,61 @@ import { ARRAY_EXPRESSIONS } from './array'
import { MATH_EXPRESSIONS } from './math'
import { STRING_EXPRESSIONS } from './string'

import {
SyncModeUnsupportedError,
AsyncModeUnsupportedError,
EvaluationError,
} from '../errors'

import { _prepareEvaluateTestCases } from '../../spec/specUtil'

const _mockFailSync = [
(err) => {
throw err || new Error('MOCK_ERROR')
},
['any'],
{ defaultParam: -1 },
]

const _mockFailAsync = [
(err) =>
new Promise((resolve, reject) => {
setTimeout(() => {
reject(err || new Error('MOCK_ERROR'))
}, 100)
}),
['any'],
{ defaultParam: -1 },
]

const EXPS = {
...VALUE_EXPRESSIONS,
...COMPARISON_EXPRESSIONS,
...FUNCTIONAL_EXPRESSIONS,
...ARRAY_EXPRESSIONS,
...MATH_EXPRESSIONS,
...STRING_EXPRESSIONS,

$mockFailSync: { sync: _mockFailSync },
$mockFailAsync: { async: _mockFailAsync },
$mockFailIsomorphic: {
sync: _mockFailSync,
async: _mockFailAsync,
},
$mockFailImplicitSync: _mockFailSync,
}

const _evTestCases = _prepareEvaluateTestCases(EXPS)

class CustomError extends Error {
constructor(code: string) {
super(`CustomError message: the error code was "${code}"`)
this.code = code
}

code: string
}

describe('$pipe', () => {
const SUM_2 = ['$arrayMap', ['$mathSum', 2]]
const MULT_2 = ['$arrayMap', ['$mathMult', 2]]
Expand All @@ -31,3 +73,94 @@ describe('$pipe', () => {
[VALUE, ['$pipe', [SUM_2, GREATER_THAN_50]], []],
])
})

describe('$try', () => {
describe('sync/async isomorphism', () => {
const VALUE = 'any-value'

_evTestCases([
[
VALUE,
['$try', ['$mockFailIsomorphic']],
{ error: true, message: 'MOCK_ERROR' },
],

// Custom error code
[
VALUE,
['$try', ['$mockFailIsomorphic', 'CUSTOM_ERROR_CODE']],
{ error: true, code: 'CUSTOM_ERROR_CODE' },
],

// Custom error object
[
VALUE,
[
'$try',
['$mockFailIsomorphic', { error: true, code: 'CUSTOM_ERROR_CODE' }],
],
{ error: true, code: 'CUSTOM_ERROR_CODE' },
],

// CustomError instance
[
VALUE,
['$try', ['$mockFailIsomorphic', new CustomError('CUSTOM_ERROR_CODE')]],
{
error: true,
code: 'CUSTOM_ERROR_CODE',
message:
'CustomError message: the error code was "CUSTOM_ERROR_CODE"',
},
],

// Invalid error
[VALUE, ['$try', ['$mockFailIsomorphic', 9]], EvaluationError],

// Catch value
[VALUE, ['$try', ['$mockFailIsomorphic'], 'FAIL_VALUE'], 'FAIL_VALUE'],

// Catch expression
[
VALUE,
[
'$try',
['$mockFailIsomorphic'],
['$stringConcat', ['::FAILED::', ['$value', '$$ERROR.message']]],
],
'any-value::FAILED::MOCK_ERROR',
],
])
})

describe('sync only', () => {
const VALUE = 'any-value'

_evTestCases.testSyncCases([
[
VALUE,
['$try', ['$mockFailSync']],
{ error: true, message: 'MOCK_ERROR' },
],
[VALUE, ['$try', ['$mockFailAsync']], SyncModeUnsupportedError],
])
})

describe('async only', () => {
const VALUE = 'any-value'

_evTestCases.testAsyncCases([
[VALUE, ['$try', ['$mockFailSync']], AsyncModeUnsupportedError],
[
VALUE,
['$try', ['$mockFailImplicitSync']],
{ error: true, message: 'MOCK_ERROR' },
],
[
VALUE,
['$try', ['$mockFailAsync']],
{ error: true, message: 'MOCK_ERROR' },
],
])
})
})
102 changes: 100 additions & 2 deletions src/expressions/functional.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import { EvaluationContext, Expression, InterpreterSpec } from '../types'
import {
EvaluationContext,
Expression,
InterpreterSpec,
InterpreterSpecSingle,
} from '../types'

import { evaluate } from '../evaluate'
import { EvaluationError } from '../errors'

import { isPlainObject } from 'lodash'

import { evaluate, evaluateAsync } from '../evaluate'

import { anyType } from '@orioro/typing'

/**
* @function $pipe
Expand All @@ -21,6 +32,93 @@ export const $pipe: InterpreterSpec = [
['array'],
]

const _serializableError = (err) => {
if (err instanceof Error) {
return {
error: true,
...err,
message: err.message,
}
} else if (typeof err === 'string') {
return {
error: true,
code: err,
}
} else if (isPlainObject(err)) {
return {
error: true,
...err,
}
} else {
throw new EvaluationError('$try', `Invalid error object: ${err}`)
}
}

const _tryHandleError = (catchExpressionOrValue, context, err) => {
if (err instanceof EvaluationError) {
throw err
}

const $$ERROR = _serializableError(err)

return catchExpressionOrValue === undefined
? $$ERROR
: evaluate(
{
...context,
scope: {
...context.scope,
$$ERROR,
},
},
catchExpressionOrValue
)
}

const _trySync: InterpreterSpecSingle = [
(
expressionOrValue: Expression | any,
catchExpressionOrValue: undefined | Expression | any,
context: EvaluationContext
): any => {
try {
return evaluate(context, expressionOrValue)
} catch (err) {
return _tryHandleError(catchExpressionOrValue, context, err)
}
},
[anyType({ skipEvaluation: true }), anyType({ skipEvaluation: true })],
{
defaultParam: -1,
},
]

const _tryAsync: InterpreterSpecSingle = [
(
expressionOrValue: Expression | any,
catchExpressionOrValue: undefined | Expression | any,
context: EvaluationContext
): Promise<any> =>
evaluateAsync(context, expressionOrValue).catch((err) =>
_tryHandleError(catchExpressionOrValue, context, err)
),
[anyType({ skipEvaluation: true }), anyType({ skipEvaluation: true })],
{
defaultParam: -1,
},
]

/**
* @function $try
* @param {Expression | *} expressionOrValue
* @param {Expression | *} [catchExpressionOrValue=$$ERROR]
*/
export const $try: InterpreterSpec = {
sync: _trySync,
async: _tryAsync,
}

export const FUNCTIONAL_EXPRESSIONS = {
$pipe,
$try,
}
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ export type EvaluationScope = {
$$ACC?: any
$$SORT_A?: any
$$SORT_B?: any
$$ERROR?: PlainObject
[key: string]: any
}

Expand Down

0 comments on commit 848251f

Please sign in to comment.