Skip to content

Commit 6cfaff9

Browse files
committed
$if instruction
1 parent 183155e commit 6cfaff9

9 files changed

Lines changed: 235 additions & 49 deletions

lib/instruction_keywords.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ var getJst = require('./get_jst');
66
module.exports = {
77
callExec: callExec,
88
getRef: getRef,
9-
getData: getData
9+
getData: getData,
10+
ifThen: ifThen
1011
};
1112

1213

@@ -37,3 +38,13 @@ function getData(params) {
3738
if ($data[0] == '#') $data = decodeURIComponent($data.slice(1));
3839
return pointer.get(this.data, $data);
3940
}
41+
42+
43+
function ifThen(params) {
44+
var script = params.$if
45+
? params.$then
46+
: typeof params.$else == 'undefined' ? null : params.$else;
47+
return script && typeof script == 'object'
48+
? new this.js.Script(script)
49+
: script;
50+
}

lib/jsonscript.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ function JSONScript(opts) {
1919
this._executors = util.copy(this._opts.executors);
2020
this._util = util;
2121
this.ajv = Ajv({ passContext: true, v5: true });
22+
this.Script = Script;
2223
addAjvKeywords.call(this);
2324
addCoreInstructions.call(this);
2425
}
@@ -113,3 +114,8 @@ function _generate(schemaName) {
113114
this.ajv.removeSchema(schema.id);
114115
return schema;
115116
}
117+
118+
119+
function Script(script) {
120+
this.script = script;
121+
}

lib/jst/definitions.def

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,6 @@
4545
#}}
4646

4747

48-
{{## def.validateSubSchemaAsync:
49-
{{# def.validateSubSchema }}
50-
if ({{=$valid}}) return {{=$data}};
51-
throw new ValidationError(vErrors);
52-
#}}
53-
54-
5548
{{## def.setCompositeRule:
5649
{{
5750
var $wasComposite = it.compositeRule;

lib/jst/instruction_keyword.jst

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,23 @@
33

44
var {{=$newData}} = this.js._evalKeywords.{{=$keyword}}.call(this, {{=$data}});
55

6-
{{# def.replaceWithNewData }}
7-
var {{=$valid}} = true;
6+
var {{=$valid}};
7+
if ({{=$newData}} instanceof this.js.Script) {
8+
{{
9+
var $it = it.util.copy(it);
10+
$it.schema = { $ref: '#' };
11+
$it.level++;
12+
}}
13+
14+
{{=$newData}} = { script: {{=$newData}}.script };
15+
{{# def.replaceWithNewData }}
16+
17+
{{# def.validateSubSchema }}
18+
19+
{{=$newData}} = {{=$data}}.script;
20+
{{# def.replaceWithNewData }}
21+
} else {
22+
{{# def.replaceWithNewData }}
23+
{{=$valid}} = true;
24+
}
825

lib/jst/validate_async.jst

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,14 @@
1212

1313
var {{=$valid}};
1414
if ({{=$data}} && typeof {{=$data}}.then == 'function') {
15-
var {{=$newData}} = {{=$data}}.then(function ({{=$data}}) {
15+
var {{=$newData}} = {{=$data}}.then((function ({{=$data}}) {
1616
{{# def.validateSubSchema }}
1717
if ({{=$valid}}) return {{=$data}};
1818
throw new ValidationError(vErrors);
19-
});
19+
}).bind(this));
2020

2121
{{# def.replaceWithNewData }}
2222
{{=$valid}} = true;
2323
} else {
24-
{{# def.validateSubSchema}}
24+
{{# def.validateSubSchema }}
2525
}
26-

spec/$exec.spec.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ describe('$exec instruction - call to external executor', function() {
2424
};
2525

2626
return js.evaluate(script).then(function (res) {
27-
assert.equal(res, 'you requested /resource');
27+
assert.equal(res, 'you requested /resource from router1');
2828
});
2929
});
3030

@@ -68,7 +68,7 @@ describe('$exec instruction - call to external executor', function() {
6868
};
6969

7070
return js.evaluate(script, data).then(function (res) {
71-
assert.equal(res, 'you posted {"test":"test"} to /resource');
71+
assert.equal(res, 'you posted {"test":"test"} to /resource at router1');
7272
});
7373
});
7474

@@ -86,7 +86,7 @@ describe('$exec instruction - call to external executor', function() {
8686
};
8787

8888
return js.evaluate(script, data).then(function (res) {
89-
assert.equal(res, 'you posted {"test":"test"} to /resource');
89+
assert.equal(res, 'you posted {"test":"test"} to /resource at router1');
9090
});
9191
});
9292
});

spec/$if.spec.js

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
'use strict';
2+
3+
var JSONScript = require('../lib/jsonscript');
4+
var assert = require('assert');
5+
var testutil = require('./testutil');
6+
var shouldBeError = testutil.shouldBeError;
7+
var routers = require('./routers');
8+
9+
10+
describe('$if instruction - conditional evaluation', function() {
11+
var js;
12+
13+
before(function() {
14+
js = JSONScript();
15+
js.addExecutor('router1', routers.router1);
16+
js.addExecutor('router2', routers.router2);
17+
js.addExecutor('async', function (value) { return Promise.resolve(value); });
18+
});
19+
20+
var data = {
21+
cond1: true,
22+
cond2: false,
23+
notBoolean: 1,
24+
then: 'foo',
25+
else: 'bar',
26+
thenRouter: 'router1',
27+
elseRouter: 'router2'
28+
};
29+
30+
it('should return the result of evaluation of script in $then or in $else', function() {
31+
return Promise.all([
32+
test({ $if: true, $then: 'foo', $else: 'bar' }, 'foo'),
33+
test({ $if: false, $then: 'foo', $else: 'bar' }, 'bar'),
34+
test({ $if: false, $then: 'foo' }, null),
35+
test({ $if: { $data: '/cond1' }, $then: 'foo', $else: 'bar' }, 'foo'),
36+
test({ $if: { $data: '/cond2' }, $then: 'foo', $else: 'bar' }, 'bar'),
37+
test({ $if: { $data: '/cond2' }, $then: 'foo' }, null),
38+
test({
39+
$if: true,
40+
$then: { $data: '/then' },
41+
$else: { $data: '/else' }
42+
}, 'foo'),
43+
test({
44+
$if: false,
45+
$then: { $data: '/then' },
46+
$else: { $data: '/else' }
47+
}, 'bar'),
48+
test({
49+
$if: false,
50+
$then: { $data: '/then' }
51+
}, null),
52+
test({
53+
$if: { $data: '/cond1' },
54+
$then: { $data: '/then' },
55+
$else: { $data: '/else' }
56+
}, 'foo'),
57+
test({
58+
$if: { $data: '/cond2' },
59+
$then: { $data: '/then' },
60+
$else: { $data: '/else' }
61+
}, 'bar'),
62+
test({
63+
$if: { $data: '/cond2' },
64+
$then: { $data: '/then' }
65+
}, null)
66+
]);
67+
});
68+
69+
it('should allow for $if/$then/$else to be asynchronous', function() {
70+
return Promise.all([
71+
test({
72+
$if: { $exec: 'async', $args: true },
73+
$then: 'foo',
74+
$else: 'bar'
75+
}, 'foo'),
76+
test({
77+
$if: { $exec: 'async', $args: false },
78+
$then: 'foo',
79+
$else: 'bar'
80+
}, 'bar'),
81+
test({
82+
$if: { $exec: 'async', $args: false },
83+
$then: 'foo'
84+
}, null),
85+
test({
86+
$if: true,
87+
$then: { $exec: 'async', $args: 'foo' },
88+
$else: { $exec: 'async', $args: 'bar' }
89+
}, 'foo'),
90+
test({
91+
$if: false,
92+
$then: { $exec: 'async', $args: 'foo' },
93+
$else: { $exec: 'async', $args: 'bar' }
94+
}, 'bar'),
95+
test({
96+
$if: false,
97+
$then: { $exec: 'async', $args: 'foo' }
98+
}, null),
99+
test({
100+
$if: { $exec: 'async', $args: true },
101+
$then: { $exec: 'async', $args: 'foo' },
102+
$else: { $exec: 'async', $args: 'bar' }
103+
}, 'foo'),
104+
test({
105+
$if: { $exec: 'async', $args: false },
106+
$then: { $exec: 'async', $args: 'foo' },
107+
$else: { $exec: 'async', $args: 'bar' }
108+
}, 'bar'),
109+
test({
110+
$if: { $exec: 'async', $args: false },
111+
$then: { $exec: 'async', $args: 'foo' }
112+
}, null)
113+
]);
114+
});
115+
116+
it('should allow for $if/$then/$else to be any script', function() {
117+
return Promise.all([
118+
test({
119+
$if: { $data: '/cond1' },
120+
$then: { $exec: 'router1', $args: { path: '/resource' } },
121+
$else: { $exec: 'router2', $args: { path: '/resource' } }
122+
}, 'you requested /resource from router1'),
123+
test({
124+
$exec: {
125+
$if: { $data: '/cond1' },
126+
$then: { $data: '/thenRouter' },
127+
$else: { $data: '/elseRouter' }
128+
},
129+
$args: { path: '/resource' }
130+
}, 'you requested /resource from router1')
131+
]);
132+
});
133+
134+
it('should throw error if $if is not boolean', function() {
135+
return Promise.all([
136+
shouldBeError(js.evaluate({ $if: 1, $then: 'foo' }), 'validation failed', ['should be boolean']),
137+
shouldBeError(js.evaluate({ $if: { $data: '/notBoolean' }, $then: 'foo' }, data),
138+
'validation failed', ['should be boolean']),
139+
shouldBeError(js.evaluate({ $if: { $exec: 'async', $args: 1 }, $then: 'foo' }, data),
140+
'validation failed', ['should be boolean']),
141+
shouldBeError(js.evaluate({ $if: { $data: { $exec: 'async', $args: '/notBoolean' } }, $then: 'foo' }, data),
142+
'validation failed', ['should be boolean'])
143+
]);
144+
});
145+
146+
147+
function test(script, expectedResult) {
148+
return js.evaluate(script, data).then(function (res) {
149+
assert.strictEqual(res, expectedResult);
150+
if (expectedResult) assert.deepEqual(res, expectedResult);
151+
});
152+
};
153+
});

spec/evaluate.spec.js

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ describe('script evaluation', function() {
1313

1414
before(function() {
1515
js = JSONScript();
16-
js.addExecutor('router', routers.router1);
16+
js.addExecutor('router1', routers.router1);
1717
});
1818

1919
beforeEach(function(){
@@ -24,55 +24,55 @@ describe('script evaluation', function() {
2424
it('should evaluate parallel execution', function() {
2525
var script = {
2626
a: {
27-
$exec: 'router',
27+
$exec: 'router1',
2828
$args: { path: '/resource' }
2929
},
3030
b: {
31-
$exec: 'router',
31+
$exec: 'router1',
3232
$method: 'post',
3333
$args: { path: '/resource', body: { test: 'test' } }
3434
}
3535
};
3636

3737
return js.evaluate(script).then(function (res) {
3838
assert.deepEqual(res, {
39-
a: 'you requested /resource',
40-
b: 'you posted {"test":"test"} to /resource'
39+
a: 'you requested /resource from router1',
40+
b: 'you posted {"test":"test"} to /resource at router1'
4141
});
4242

4343
assert.deepEqual(callsResolutions, [
4444
{ call: 'get: /resource' },
4545
{ call: 'post: /resource' },
46-
{ res: 'you requested /resource' },
47-
{ res: 'you posted {"test":"test"} to /resource' }
46+
{ res: 'you requested /resource from router1' },
47+
{ res: 'you posted {"test":"test"} to /resource at router1' }
4848
]);
4949
});
5050
});
5151

5252
it('should evaluate sequential execution', function() {
5353
var script = [
5454
{
55-
$exec: 'router',
55+
$exec: 'router1',
5656
$args: { path: '/resource' }
5757
},
5858
{
59-
$exec: 'router',
59+
$exec: 'router1',
6060
$method: 'post',
6161
$args: { path: '/resource', body: { test: 'test' } }
6262
}
6363
];
6464

6565
return js.evaluate(script).then(function (res) {
6666
assert.deepEqual(res, [
67-
'you requested /resource',
68-
'you posted {"test":"test"} to /resource'
67+
'you requested /resource from router1',
68+
'you posted {"test":"test"} to /resource at router1'
6969
]);
7070

7171
assert.deepEqual(callsResolutions, [
7272
{ call: 'get: /resource' },
73-
{ res: 'you requested /resource' },
73+
{ res: 'you requested /resource from router1' },
7474
{ call: 'post: /resource' },
75-
{ res: 'you posted {"test":"test"} to /resource' }
75+
{ res: 'you posted {"test":"test"} to /resource at router1' }
7676
]);
7777
});
7878
});

0 commit comments

Comments
 (0)