Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion lib/state-machines/state-types/Base-state.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const debugPackage = require('debug')('statebox')
const _ = require('lodash')
const Status = require('../../Status')
const States = require('./errors')
const ExecutionContext = require('./execution-context')

const PathHandlers = require('./path-handlers')

Expand Down Expand Up @@ -35,7 +36,7 @@ class BaseState {

run (executionDescription) {
try {
const input = this.inputSelector(executionDescription.ctx)
const input = this.inputSelector(executionDescription.ctx, ExecutionContext(executionDescription))
return this.process(executionDescription, input)
} catch (e) {
return this.processTaskFailure(e, executionDescription.executionName)
Expand Down
35 changes: 0 additions & 35 deletions lib/state-machines/state-types/Task.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
const BaseStateType = require('./Base-state')
const boom = require('@hapi/boom')
const jp = require('jsonpath')
const _ = require('lodash')
const debug = require('debug')('statebox')
const States = require('./errors')

Expand Down Expand Up @@ -38,41 +36,8 @@ class Context {
return this.task.processTaskHeartbeat(output, this.executionName)
.then(result => { this.heartbeat(result); return result })
}

resolveInputPaths (input, template) {
const clonedInput = cloneOrDefault(input)
const clonedTemplate = cloneOrDefault(template)
resolvePaths(clonedInput, clonedTemplate)
return clonedTemplate
}
}

function cloneOrDefault (obj) {
return (_.isObject(obj)) ? _.cloneDeep(obj) : { }
} // cloneOrDefault

function resolvePaths (input, root) {
if (!_.isObject(root)) return

// TODO: Support string-paths inside arrays
if (Array.isArray(root)) {
root.forEach(element => resolvePaths(input, element))
return
}

for (const [key, value] of Object.entries(root)) {
if (isJSONPath(value)) {
root[key] = jp.value(input, value)
} else {
resolvePaths(input, value)
}
} // for ...
} // resolvePaths

function isJSONPath (p) {
return _.isString(p) && p.length !== 0 && p[0] === '$'
} // isJSONPath

/// //////////////////////////////////
class Task extends BaseStateType {
constructor (stateName, stateMachine, stateDefinition, options) {
Expand Down
14 changes: 14 additions & 0 deletions lib/state-machines/state-types/execution-context/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const { DateTime } = require('luxon')

class ExecutionContext {
constructor (execDesc) {
this.execDesc = execDesc
}

get StartTimestamp () { return this.execDesc.startDate }
get DayOfWeek () { return DateTime.local().weekdayLong }
get Time () { return DateTime.local().toLocaleString(DateTime.TIME_24_SIMPLE) }
get Date () { return DateTime.local().toLocaleString(DateTime.DATE_SHORT) }
}

module.exports = executionDescription => new ExecutionContext(executionDescription)
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ function inputPathHandler (inputPath, parameters) {
const path = findSelector(inputPath)
const parameterTemplate = payloadTemplateHandler(parameters)

return ctx => parameterTemplate(path(ctx))
return (input, executionContext) => parameterTemplate(path(input), executionContext)
} // inputPathHandler

module.exports = inputPathHandler
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ const Tokenizr = require('tokenizr')
const PARAMLIST = 'param-list'

const lexer = new Tokenizr()
lexer.rule(/^\$(\$.*$)/, (ctx, match) => {
ctx.accept('contextpath', match[1])
})
lexer.rule(/^\$.*$/, (ctx, match) => {
ctx.accept('path', match[0])
})
Expand All @@ -14,6 +17,9 @@ lexer.rule(/\)/, (ctx) => {
ctx.accept('end-function', null)
ctx.pop()
})
lexer.rule(PARAMLIST, /\$(\$[^, )]*)/, (ctx, match) => {
ctx.accept('contextpath', match[1])
})
lexer.rule(PARAMLIST, /\$[^, )]*/, (ctx, match) => {
ctx.accept('path', match[0])
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ function makeDynamicHandler (params, references) {
const skeleton = skeletonizeParams(params, references)
const replacers = makeReplacers(references)

return input => {
return (input, executionContext) => {
const parameters = _.cloneDeep(skeleton)
for (const [path, expr] of replacers) {
const extractedValue = evaluateExpression(expr, input)
const extractedValue = evaluateExpression(expr, input, executionContext)

dottie.set(parameters, path, extractedValue)
}
Expand All @@ -60,6 +60,7 @@ function makeDynamicHandler (params, references) {
} // makeDynamicHandler

const Evaluate = {
contextpath: evaluateContextObjectPath,
path: evaluatePath,
function: evaluateIntrinsic,
string: token => token.value,
Expand All @@ -68,27 +69,31 @@ const Evaluate = {
null: () => null
} // Evaluate

function evaluateExpression (expression, input) {
function evaluateExpression (expression, input, executionContext) {
const token = argTokeniser(expression)
return evaluateArgument(token, input)
return evaluateArgument(token, input, executionContext)
} // evaluateExpression

function evaluateArgument (token, input) {
return Evaluate[token.type](token, input)
function evaluateArgument (token, input, executionContext) {
return Evaluate[token.type](token, input, executionContext)
} // evaluateArgument

function evaluateContextObjectPath (token, _, executionContext) {
return evaluatePath(token, executionContext)
} // evaluateContextObjectPath

function evaluatePath ({ value }, input) {
return extractValue(_.cloneDeep(jp.query(input, value)))
} // evaluatePath

function evaluateIntrinsic (func, input) {
function evaluateIntrinsic (func, input, executionContext) {
const fn = instrinsics[func.value]
if (!fn) {
ErrorStates.IntrinsicFailure.raise(`Unknown intrinsic States.${func.value}`)
}

try {
const values = func.parameters.map(token => evaluateArgument(token, input))
const values = func.parameters.map(token => evaluateArgument(token, input, executionContext))

if (fn.validate) {
fn.validate(func.parameters, values)
Expand Down
60 changes: 60 additions & 0 deletions test/context-object.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/* eslint-env mocha */

const chai = require('chai')
const expect = chai.expect

const contextObjectStateMachines = require('./fixtures/state-machines/context-object')

const Statebox = require('./../lib')

let statebox

describe('Context Object', () => {
before('setup statebox', async () => {
statebox = new Statebox()
await statebox.ready
await statebox.createStateMachines(contextObjectStateMachines, {})
})

const today = new Date().toLocaleDateString('en-EN', { weekday: 'long' })

const contextObjectStates = {
NonExistantProperty: { oops: null },
DayOfWeek: { day: today },
FormattedDayOfWeek: { day: `Today is ${today}` },
StartTime: eD => { return { startedAt: eD.startDate } },
Time: eD => {
expect(eD.ctx.time).to.match(/^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/)
return eD.ctx
},
Date: eD => {
expect(eD.ctx.date).to.match(/^\d\d\/\d\d\/\d\d\d\d$/)
return eD.ctx
}
}

for (const [name, result] of Object.entries(contextObjectStates)) {
test(name, result)
}
})

function test (statemachine, result) {
it(statemachine, async () => {
const executionDescription = await runStateMachine(statemachine)

expect(executionDescription.status).to.eql('SUCCEEDED')
expect(executionDescription.stateMachineName).to.eql(statemachine)
expect(executionDescription.currentResource).to.eql(undefined)

const expected = (typeof result !== 'function') ? result : result(executionDescription)
expect(executionDescription.ctx).to.eql(expected)
}) // it ...
}

function runStateMachine (statemachine) {
return statebox.startExecution(
{}, // input
statemachine,
{ sendResponse: 'COMPLETE' } // options
)
}
12 changes: 12 additions & 0 deletions test/fixtures/state-machines/context-object/built-ins/date.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"StartAt": "First",
"States": {
"First": {
"Type": "Pass",
"Parameters": {
"date.$": "$$.Date"
},
"End": true
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"StartAt": "First",
"States": {
"First": {
"Type": "Pass",
"Parameters": {
"day.$": "$$.DayOfWeek"
},
"End": true
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"StartAt": "First",
"States": {
"First": {
"Type": "Pass",
"Parameters": {
"day.$": "States.Format('Today is {}', $$.DayOfWeek)"
},
"End": true
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"StartAt": "First",
"States": {
"First": {
"Type": "Pass",
"Parameters": {
"oops.$": "$$.MissingProperty"
},
"End": true
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"StartAt": "First",
"States": {
"First": {
"Type": "Pass",
"Parameters": {
"startedAt.$": "$$.StartTimestamp"
},
"End": true
}
}
}
12 changes: 12 additions & 0 deletions test/fixtures/state-machines/context-object/built-ins/time.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"StartAt": "First",
"States": {
"First": {
"Type": "Pass",
"Parameters": {
"time.$": "$$.Time"
},
"End": true
}
}
}
9 changes: 9 additions & 0 deletions test/fixtures/state-machines/context-object/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = {
NonExistantProperty: require('./built-ins/non-existant.json'),
DayOfWeek: require('./built-ins/day-of-week.json'),
FormattedDayOfWeek: require('./built-ins/formatted-day-of-week.json'),
StartTime: require('./built-ins/start-time.json'),
Time: require('./built-ins/time.json'),
Date: require('./built-ins/date.json'),
Timestamp: require('./built-ins/time.json')
}
10 changes: 5 additions & 5 deletions test/pass-states.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,12 +166,12 @@ function test (label, statemachine, input, result) {
}) // it ...
}

async function runStateMachine (statemachine, input) {
const executionDescription = await statebox.startExecution(
function runStateMachine (statemachine, input) {
return statebox.startExecution(
Object.assign({}, input),
statemachine,
{} // options
{
sendResponse: 'COMPLETE'
} // options
)

return statebox.waitUntilStoppedRunning(executionDescription.executionName)
}