dtl-js
Version:
Data Transformation Language - JSON templates and data transformation
311 lines (280 loc) • 12.6 kB
JavaScript
/* =================================================
* Copyright (c) 2015-2020 Jay Kuri
*
* This file is part of DTL.
*
* DTL is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* DTL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with DTL; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* =================================================
*/
var chai = require('chai');
var assert = chai.assert;
var util = require('util');
var DTL = require('../lib/DTL.js');
var uuid = require('uuid');
var container = {
ctx: { "foo": 72, 'request': { 'origin': { 'detail': { port: 25 }}}},
meta: { "bob":10, "john":22, "will":"no", "deep": { "things": 22 },
"@unusual:": 'is ok',
"obj_list" : {
'fff' : {
'name': 'fff',
},
'abc' : {
'name': 'abc'
},
'zz' : {
'name': 'zz',
},
'ccf' : {
'name': 'ccf',
'bar': null
}
},
"sortable" : [
{ 'name' : 'fff'},
{ 'name' : 'ccc'},
{ 'name' : 'ddd'},
{ 'name' : 'aaa'},
{ 'name' : 'cmy'},
null
],
"sortable_numeric" : [
{ 'val' : 10},
{ 'val' : 120},
{ 'val' : 300},
{ 'val' : 700},
{ 'val' : 111},
{ 'val' : 11},
{ 'val' : 22},
],
"filter_me" : {
name : 'a name',
foo : 'a foo',
url : 'http://foo.bar.com/?x=1&m=500'
},
"numbers": ['1','2','3','4','5'],
"nullers": ['1', undefined, '2','3','4','5'],
"url": "http://foo.bar.com/fuzzy/about.php",
"long_url" : 'http://foo.bar.com:900/path/to/do?a=1',
"encode_me" : "hello world",
"long_path" : 'the/long/path/to/do/the/thing',
"thing": "port",
"this.thing": 173,
"list": ["bob", "john", "will", "shallow"],
"Step": 3,
"content-type": 'text/plain',
"pair": ["the_key", "the_value"],
"pairs": [["key2", "value2"],["key3", "value3"]],
"key": { is: 'meta.deep.things' },
"phone" : "303-554-9000",
"not_true" : false,
"contacts": [
{
"email": "bob@gmail.com",
"name": "Bob Wilson"
},
{
"email": "bob@yahoo.com",
"name": "Bob Johnson"
},
{
"email": "kate@gmail.com",
"name": "Kate Smith"
},
{
"email": "dude@example.com",
"name": "Jeffry Lebowski"
},
{
"email": "donny@example.com",
"name": "Theodore Donald Kerabatsos"
}
]
}
};
describe('DTL Advanced Features', function(done) {
describe('Modified options', function() {
it('changing transform_extractor works', function() {
let options = {};
options.transform_extractor = function(transform) {
if ( typeof transform == "string" && transform.substring(0, 2) == '%%' &&
transform.substring(transform.length-2, transform.length) == '%%') {
return transform.substring(2, transform.length-2);
} else {
return undefined;
}
}
let transform = {
"out": {
"parsed": "%% uuid() %%",
"notparsed": "(: uuid() :)"
}
}
let result = DTL.apply(container, transform, "out", options);
assert.equal(uuid.version(result.parsed), 4, 'transform is identified and processed correctly');
assert.equal(result.notparsed, transform.out.notparsed, 'happy tags no longer function when overridden');
});
it('custom transform_extractor works on nested transform objects', function() {
let options = {};
options.transform_extractor = function(transform) {
if ( typeof transform == "string" && transform.substring(0, 2) == '%%' &&
transform.substring(transform.length-2, transform.length) == '%%') {
return transform.substring(2, transform.length-2);
} else {
return undefined;
}
}
let transform = {
"out": "%% $. -> sub_tx %%",
"sub_tx": {
"parsed": "%% grep($meta.contacts '%% $item.name =~ /Bob/ %%') %%",
"notparsed": "(: uuid() :)"
}
}
let result = DTL.apply(container, transform, "out", options);
assert.equal(result.parsed.length, '2', 'nested transforms are identified and processed correctly');
assert.equal(result.parsed[0].email, "bob@gmail.com", 'nested transform produces expected results');
assert.equal(result.notparsed, transform.sub_tx.notparsed, 'happy tags still do not function in deep transforms');
});
it('contextual transform_extractor works', function() {
let options = {};
// don't copy this, this almost certainly won't work the way you want as $ is not present
// in every DTL expression
options.transform_extractor = function(transform) {
if ( typeof transform == "string" && /\$/.test(transform) ) {
return transform;
} else {
return undefined;
}
}
let transform = {
"out": {
"parsed": "keys($ctx)",
"notparsed": {
"word": "uuid()",
"number": "(: 5 :)"
}
}
}
let result = DTL.apply(container, transform, "out", options);
assert.equal(result.parsed.length, 2, 'nested transforms are identified and processed correctly');
assert.deepEqual(result.notparsed, transform.out.notparsed, 'elements that do not match are left as-is');
});
it('returning a non-string in transform_extractor fails', function() {
let options = {};
options.transform_extractor = function(transform) {
if ( typeof transform == "string" ) {
return { "result": "broken" };
} else {
return undefined;
}
}
let transform = {
"out": {
"parsed": "%% uuid() %%",
"notparsed": "(: uuid() :)"
}
}
let result = DTL.apply(container, transform, "out", options);
assert.equal(result.parsed, transform.out.parsed, 4, 'transform is identified and processed correctly');
assert.equal(result.notparsed, transform.out.notparsed, 'happy tags no longer function when overridden');
});
it("keyfilter option works", function() {
var result;
var expected_results = [ 72, 10, { things: 22 } ];
var options = {};
options.keyfilter = function(keys) {
if (keys[0] != 'ctx') {
keys.unshift('meta')
}
return keys;
};
result = DTL.apply(container, { out: "(: [ $ctx.foo $bob $deep ] :)" }, 'out', options);
assert.deepEqual(result, expected_results, "Produces expected results: " + util.inspect(result) + " = " + util.inspect(expected_results) );
});
});
describe('Tetchy mode', function() {
it('tetchy mode works via options', function() {
let options = {
tetchy: true
};
let transform = {
"out": {
"found": "(: $meta.obj_list.fff.name :)",
"notfound": "(: $meta.obj_list.ffe.foo :)",
}
}
const throwingFunction = function() {
let result = DTL.apply(container, transform, "out", options);
};
assert.throws(throwingFunction, Error);
});
it('tetchy mode works via config', function() {
let config = {
tetchy: true
};
let dtl = new DTL(config);
let transform = {
"out": {
"found": "(: $meta.obj_list.fff.name :)",
"notfound": "(: $meta.obj_list.abd.bar :)",
}
}
const throwingFunction = function() {
let result = dtl.apply(container, transform, "out");
};
assert.throws(throwingFunction, Error);
});
it('tetchy mode works on parenthesized data access', function() {
let config = {
tetchy: true
};
let dtl = new DTL(config);
let transform = {
"out": {
// this will succeed
"found": "(: ($. -> test).obj_list.fff.name :)",
// this should explode
"notfound": "(: ($. -> test).obj_list.abd.bar :)",
},
"test": "(: $meta :)"
}
const throwingFunction = function() {
let result = dtl.apply(container, transform, "out");
};
assert.throws(throwingFunction, Error);
});
it('tetchy mode throws on coercion', function() {
let config = {
tetchy: true
};
let dtl = new DTL(config);
let transform = {
"out": {
// this will succeed
"nocoerce": "(: map(0..2 '(: $item :)') :)",
// this should explode
"coerce": "(: map(0 '(: $item :)') :)",
},
"test": "(: $meta :)"
}
const throwingFunction = function() {
let result = dtl.apply(container, transform, "out");
};
assert.throws(throwingFunction, Error);
//throwingFunction();
});
});
});