Socket
Socket
Sign inDemoInstall

hapi-csv

Package Overview
Dependencies
Maintainers
2
Versions
41
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

hapi-csv - npm Package Compare versions

Comparing version 1.0.0 to 2.1.0

273

lib/index.js
'use strict';
// Load modules
const Joi = require('joi');
const Hoek = require('Hoek');
let internals = {};
internals.maximumElementsArray = 5;
internals.routeMap = new Map();
const internals = {
routeMap: new Map(),
maximumElementsInArray: 0
};
exports.register = (server, baseOptions, next) => {
exports.register = (server, options, next) => {
server.ext('onPreStart', (request, reply) => {
internals.maximumElementsInArray = options.maximumElementsInArray || 5;
server.connections[0].table().forEach((route) => {
server.ext('onPreStart', (srv, nxt) => {
if (route.settings.response.schema) {
internals.routeMap.set(route.path, route.settings.response.schema);
}
});
return reply();
});
srv.connections.forEach((connection) => {
server.ext('onPreResponse', (request, reply) => {
connection.table().forEach((route) => {
let modifiedReply = null;
const acceptHeader = request.headers.accept;
if(internals.routeMap.has(request.route.path) && acceptHeader.indexOf("text/csv") > -1) {
const schema = internals.routeMap.get(request.route.path);
modifiedReply = Hoek.clone(reply);
modifiedReply.response.source = internals.schemaToCsv(schema, request.response.source, ",", 5);
return reply(modifiedReply);
}
return reply.continue();
});
if (route.settings.response.schema) {
internals.routeMap.set(route.path, route.settings.response.schema);
}
});
});
return next();
return nxt();
});
server.ext('onPreResponse', (request, reply) => {
const acceptHeader = request.headers.accept;
const isTextCsvAcceptHeader = acceptHeader.indexOf('text/csv') > -1;
const isApplicationCsvAcceptHeader = acceptHeader.indexOf('application/csv') > -1;
if (internals.routeMap.has(request.route.path) && (isApplicationCsvAcceptHeader || isTextCsvAcceptHeader)) {
const schema = internals.routeMap.get(request.route.path);
const responseHeader = isTextCsvAcceptHeader ? 'text/csv' : 'application/csv';
return reply(internals.schemaToCsv(schema, request.response.source, options.separator || ',')).header('content-type', responseHeader);
}
return reply.continue();
});
return next();
};
exports.register.attributes = {
pkg: require('../package.json'),
once: true
pkg: require('../package.json'),
once: true
};
exports.schemaToCsv = function (schema, dataset, separator, maxElementsArray) {
internals.schemaToCsv = (schema, dataset, separator) => {
if (!(dataset instanceof Array) && !(dataset instanceof Object)) {
return dataset;
}
// We return if the dataset is not an array or an object, just a primitive type
if (!(Array.isArray(dataset)) && !(dataset === Object(dataset))) {
return dataset;
}
const headers = internals.schemaToHeaders(schema);
internals.maximumElementsArray = maxElementsArray ? maxElementsArray : 5;
const compiledSchema = Joi.compile(schema).describe();
const headerQueryArray = internals.parseSchema(compiledSchema);
const headerQueryMap = internals.arrayToMap(headerQueryArray);
return internals.headerQueryMapToCsv(headers, dataset, separator);
return internals.headerQueryMapToCsv(headerQueryMap, dataset, separator);
};
internals.schemaToHeaders = function (schema) {
// Utility function to convert an array to a Map
internals.arrayToMap = (array) => {
return internals.arrayToMap(internals.parseSchema(Joi.compile(schema).describe()));
};
const resultMap = new Map();
internals.arrayToMap = function (array) {
Hoek.flatten(array).forEach((item) => {
const resultMap = new Map();
const key = Object.keys(item)[0];
Hoek.flatten(array).map((item) => {
if (key !== undefined) {
const value = item[key];
if (Object.keys(item)[0] !== undefined) {
const key = Object.keys(item)[0];
const value = item[key];
resultMap.set(key, value);
}
return resultMap;
});
resultMap.set(key, value);
}
});
return resultMap;
return resultMap;
};
internals.parseSchema = function (joiSchema, keyName, arrayFlag) {
// Recursive function which parses a Joi Schema to an array of header, query entries
// arrayFlag is defined when the previous call to the function handled a joiSchema of type array
internals.parseSchema = (joiSchema, keyName, arrayFlag) => {
if (joiSchema.type === 'object') {
let children = [];
if (joiSchema.type === 'object') {
if (joiSchema.children) {
const childrenKeys = Object.keys(joiSchema.children);
children = children.concat(childrenKeys.map((key) => internals.parseSchema(joiSchema.children[key], key)));
if (joiSchema.children) {
const childrenKeys = Object.keys(joiSchema.children);
let children = childrenKeys.map((key) => internals.parseSchema(joiSchema.children[key], key));
if (!keyName) {
return children;
}
if (!keyName) {
return children;
}
children = Hoek.flatten(children);
children = Hoek.flatten(children);
return children.map((child) => {
return children.map((child) => {
const key = Object.keys(child)[0];
child[key].unshift(keyName);
const key = Object.keys(child)[0];
child[key].unshift(keyName);
if (!arrayFlag) {
const name = keyName + '.' + key;
child[name] = child[key];
delete child[key];
}
// If the previous call didn't handle an array Joi schema, we alter the keys
if (!arrayFlag) {
const name = keyName + '.' + key;
child[name] = child[key];
delete child[key];
}
return child;
});
}
return false;
}
return child;
});
}
if (joiSchema.type === 'array') {
return false;
}
const item = joiSchema.items[0];
if (joiSchema.type === 'array') {
// FUTURE: Make it work for arrays with multiple item definitions
const item = joiSchema.items[0];
const parsedData = internals.parseSchema(item, keyName, true);
const parsedItem = internals.parseSchema(item, keyName, true);
const prefixedDataArray = [];
const prefixedItemArray = [];
if (keyName && parsedData) {
for (let i = 0; i < internals.maximumElementsArray; ++i) {
if (keyName && parsedItem) {
for (let i = 0; i < internals.maximumElementsInArray; ++i) {
prefixedDataArray.push(parsedData.map((res) => {
const keyEntry = Object.keys(res)[0];
const name = parsedData.length === 1 ? `${keyName}_${i}` : `${keyName}_${i}.${keyEntry}`;
const spliced = res[keyEntry].slice();
spliced.splice(1, 0, i);
return {
[name]: spliced
};
}));
}
return prefixedDataArray;
}
return parsedData;
}
prefixedItemArray.push(parsedItem.map((headerQuery) => {
return [{ [keyName]: [keyName] }];
const key = Object.keys(headerQuery)[0];
//commentaar
const name = parsedItem.length === 1 ? `${keyName}_${i}` : `${keyName}_${i}.${key}`;
const sliced = headerQuery[key].slice();
sliced.splice(1, 0, i); //We splice the index after the array key
return {
[name]: sliced
};
}));
}
return prefixedItemArray;
}
return parsedItem;
}
// First square brackets are used to convert to String, second brackets are used to convert to an array
return [{ [keyName]: [keyName] }];
};
internals.headerQueryMapToCsv = function (headers, dataset, separator) {
internals.headerQueryMapToCsv = (headerQueryMap, dataset, separator) => {
let csv = '';
let row = '';
let flag = false;
let csv = '';
let headerRow = '';
let noValueFoundFlag = false;
for (const key of headers.keys()) {
row += key + separator;
}
for (const header of headerQueryMap.keys()) {
headerRow += header + separator;
}
csv += row + '\n';
row = '';
csv += headerRow + '\n';
for (let i = 0; i < dataset.length; ++i) {
row = '';
let temp = dataset[i];
for (const properties of headers.values()) {
for (const header of properties) {
if (temp[header] === null || temp[header] === undefined) {
flag = true;
break;
}
else {
temp = temp[header];
}
}
if (flag) {
row += separator;
flag = false;
}
else {
if (!(dataset instanceof Array)) {
dataset = [dataset];
}
row += '\"' + temp + '\"' + separator;
}
temp = dataset[i];
}
for (let i = 0; i < dataset.length; ++i) {
let dataRow = '';
csv += row + '\n';
}
return csv.trim();
};
for (const query of headerQueryMap.values()) {
let temp = dataset[i];
for (const queryPart of query) {
if (temp[queryPart] === null || temp[queryPart] === undefined) {
noValueFoundFlag = true;
break; //We break out of the for loop because there is no need to dig deeper into the object, the object is already undefined or null
}
else {
temp = temp[queryPart];
}
}
if (noValueFoundFlag) {
noValueFoundFlag = false;
}
else {
dataRow += '"' + temp + '"';
}
dataRow += separator;
}
csv += dataRow + '\n';
}
return csv.trim();
};

@@ -0,0 +0,0 @@ The MIT License (MIT)

{
"name": "hapi-csv",
"version": "1.0.0",
"version": "2.1.0",
"description": "Hapi plugin for converting a Joi response schema and dataset to csv",
"main": "lib/index.js",
"scripts": {
"test": "lab -a code -v -t 85 -r"
"test": "lab -a code -vL --lint-fix -t 100",
"test-cov-html": "lab -a code -vL -t 100 -r html -o coverage.html"
},

@@ -24,3 +25,3 @@ "repository": {

"hoek": "4.x.x",
"joi": "8.x.x"
"joi": "9.x.x"
},

@@ -27,0 +28,0 @@ "devDependencies": {

@@ -0,7 +1,9 @@

# Hapi-csv
## What
Converts the response to csv based on the defined response schema when the Accept-header includes text/csv
Converts the response to csv based on the Joi response schema when the Accept header includes `text/csv` or `application/csv`
## How
npm install hapi-csv
`npm install hapi-csv`

@@ -14,5 +16,8 @@ Register the hapi-csv plugin on the server

options: {
maximumElementsInArray: 5,
separator: ','
}
}, function (err) {
if (err) console.log(err);
if (err) throw err;
...

@@ -22,3 +27,3 @@ });

When you have a route on which a response schema is defined, like in the example below, the plugin will convert the response to csv when the Accept-header includes text/csv
When you have a route on which a response schema is defined, like in the example below, the plugin will convert the response to csv when the Accept header includes `text/csv` or `application/csv`

@@ -25,0 +30,0 @@ ```javascript

@@ -6,3 +6,4 @@ 'use strict';

const Joi = require('joi');
const hapiCsv = require("../lib/index");
const Hapi = require('hapi');
const HapiCsv = require('../lib/index');

@@ -16,140 +17,234 @@ const lab = exports.lab = Lab.script();

describe('Basics', () => {
describe('Basics', () => {
it('Parse Joi schema', (done) => {
it('Register plugin with simple response schema', (done) => {
const testSchema = Joi.array().required().items({
testObject: Joi.object().keys({
testPropOne: Joi.number().required(),
testPropTwo: Joi.number(),
testPropThree: Joi.string()
}),
testNumber: Joi.number().required(),
testString: Joi.string().allow(null),
testEmail: Joi.string().email({errorLevel: 68}).lowercase().max(1000).required(),
testDate: Joi.date().iso().allow(null),
testArray: Joi.array().items(Joi.object().keys({
testPropOne: Joi.number().required(),
testPropTwo: Joi.string()
})),
testPrimitiveArray: Joi.array().items(Joi.number()),
testObjectWithoutKeys: Joi.object()
});
const server = new Hapi.Server();
server.connection();
const dataset = [{
"testObject": {
"testPropOne": 1,
"testPropTwo": 2,
"testPropThree": 3
},
"testNumber": 5,
"testString": "test",
"testEmail": "test@testprovider.com",
"testDate": "2016-07-04T13:56:31.000Z",
"testPrimitiveArray" : [5, 5],
"testArray": [{"testPropOne": 1, "testPropTwo": "One"}, {"testPropOne": 2, "testPropTwo": "Two"}, {"testPropOne": 3, "testPropTwo": "Three"}, {"testPropOne": 4, "testPropTwo": "Four"}],
"testObjectArrayWithoutKeys": []
}];
const testResponseSchema = Joi.object().keys({
first_name: Joi.string(),
last_name: Joi.string(),
age: Joi.number()
});
const csv = hapiCsv.schemaToCsv(testSchema, dataset, ",");
const expectedResult = `testObject.testPropOne,testObject.testPropTwo,testObject.testPropThree,testNumber,testString,testEmail,testDate,testArray_0.testPropOne,testArray_0.testPropTwo,testArray_1.testPropOne,testArray_1.testPropTwo,testArray_2.testPropOne,testArray_2.testPropTwo,testArray_3.testPropOne,testArray_3.testPropTwo,testArray_4.testPropOne,testArray_4.testPropTwo,testPrimitiveArray_0,testPrimitiveArray_1,testPrimitiveArray_2,testPrimitiveArray_3,testPrimitiveArray_4,
"1","2","3","5","test","test@testprovider.com","2016-07-04T13:56:31.000Z","1","One","2","Two","3","Three","4","Four",,,"5","5",,,,`;
expect(csv ,'csv').to.equal(expectedResult);
server.register({ register: HapiCsv, options: { maximumElementsInArray: 5, separator: ',' } }, (err) => {
return done();
});
expect(err, 'error').to.not.exist();
it('Dataset with null object', (done) => {
server.route([{
method: 'GET',
path: '/user',
config: {
handler: function (request, reply) {
const testSchema = Joi.array().required().items({
testObject: Joi.object().keys({
testPropOne: Joi.number().required(),
testPropTwo: Joi.number(),
testPropThree: Joi.string()
}),
testNumber: Joi.number().required(),
testString: Joi.string().allow(null),
testEmail: Joi.string().email({errorLevel: 68}).lowercase().max(1000).required(),
testDate: Joi.date().iso().allow(null),
testArray: Joi.array().items(Joi.object().keys({
testPropOne: Joi.number().required(),
testPropTwo: Joi.string()
})),
testPrimitiveArray: Joi.array().items(Joi.number()),
testObjectWithoutKeys: Joi.object()
});
reply({
first_name: 'firstName',
last_name: 'lastName',
age: 25
});
},
response: {
schema: testResponseSchema
}
}
}, {
method: 'GET',
path: '/userJson',
config: {
handler: function (request, reply) {
const dataset = [{
"testObject": null,
"testNumber": 5,
"testString": "test",
"testEmail": "test@testprovider.com",
"testDate": "2016-07-04T13:56:31.000Z",
"testPrimitiveArray" : [5, 5],
"testArray": [{"testPropOne": 1, "testPropTwo": "One"}, {"testPropOne": 2, "testPropTwo": "Two"}, {"testPropOne": 3, "testPropTwo": "Three"}, {"testPropOne": 4, "testPropTwo": "Four"}],
"testObjectArrayWithoutKeys": []
}];
reply({
first_name: 'firstName',
last_name: 'lastName',
age: 25
});
}
}
}]);
const csv = hapiCsv.schemaToCsv(testSchema, dataset, ",");
const expectedResult = `testObject.testPropOne,testObject.testPropTwo,testObject.testPropThree,testNumber,testString,testEmail,testDate,testArray_0.testPropOne,testArray_0.testPropTwo,testArray_1.testPropOne,testArray_1.testPropTwo,testArray_2.testPropOne,testArray_2.testPropTwo,testArray_3.testPropOne,testArray_3.testPropTwo,testArray_4.testPropOne,testArray_4.testPropTwo,testPrimitiveArray_0,testPrimitiveArray_1,testPrimitiveArray_2,testPrimitiveArray_3,testPrimitiveArray_4,\n,,,"5","test","test@testprovider.com","2016-07-04T13:56:31.000Z","1","One","2","Two","3","Three","4","Four",,,"5","5",,,,`;
expect(csv ,'csv').to.equal(expectedResult);
server.initialize((err) => {
return done();
});
expect(err, 'error').to.not.exist();
it('Parse simple Joi schema with primitive array', (done) => {
server.inject({
'method': 'GET',
'url': '/user',
'headers': {
'Accept': 'application/csv'
}
}, (res) => {
const testSchema = Joi.array().required().items({
testObject: Joi.object().keys({
testPropOne: Joi.number().required(),
testPropTwo: Joi.number(),
testPropThree: Joi.string()
}),
testNumber: Joi.number().required(),
testString: Joi.string().allow(null),
testEmail: Joi.string().email({errorLevel: 68}).lowercase().max(1000).required(),
testDate: Joi.date().iso().allow(null),
testArray: Joi.array().items(Joi.object().keys({
testPropOne: Joi.number().required(),
testPropTwo: Joi.string()
})),
testObjectWithoutKeys: Joi.object()
});
let expectedResult = `first_name,last_name,age,\n"firstName","lastName","25",`;
const dataset = [{
"testObject": {
"testPropOne": 1,
"testPropTwo": 2,
"testPropThree": 3
},
"testNumber": 5,
"testString": "test",
"testEmail": "test@testprovider.com",
"testDate": "2016-07-04T13:56:31.000Z",
"testArray": [{"testPropOne": 1, "testPropTwo": "One"}, {"testPropOne": 2, "testPropTwo": "Two"}, {"testPropOne": 3, "testPropTwo": "Three"}, {"testPropOne": 4, "testPropTwo": "Four"}],
"testObjectArrayWithoutKeys": []
}];
expect(res.result).to.equal(expectedResult);
const csv = hapiCsv.schemaToCsv(testSchema, dataset, ";", 5);
const expectedResult = `testObject.testPropOne;testObject.testPropTwo;testObject.testPropThree;testNumber;testString;testEmail;testDate;testArray_0.testPropOne;testArray_0.testPropTwo;testArray_1.testPropOne;testArray_1.testPropTwo;testArray_2.testPropOne;testArray_2.testPropTwo;testArray_3.testPropOne;testArray_3.testPropTwo;testArray_4.testPropOne;testArray_4.testPropTwo;
"1";"2";"3";"5";"test";"test@testprovider.com";"2016-07-04T13:56:31.000Z";"1";"One";"2";"Two";"3";"Three";"4";"Four";;;`;
expect(res.headers['content-type']).to.equal('application/csv');
expect(csv ,'csv').to.equal(expectedResult);
server.inject({
'method': 'GET',
'url': '/userJson',
'headers': {
'Accept': 'application/json'
}
}, (getResponse) => {
return done();
});
it('Parse Joi schema existing of only a primitive type', (done) => {
expectedResult = {
first_name: 'firstName',
last_name: 'lastName',
age: 25
};
const testSchema = Joi.number();
expect(getResponse.result).to.equal(expectedResult);
const dataset = 5;
server.inject({
'method': 'GET',
'url': '/user',
'headers': {
'Accept': 'application/json'
}
}, (getResponseJson) => {
const csv = hapiCsv.schemaToCsv(testSchema, dataset, ";", 5);
const expectedResult = 5;
expectedResult = {
first_name: 'firstName',
last_name: 'lastName',
age: 25
};
expect(csv ,'csv').to.equal(expectedResult);
expect(getResponseJson.result).to.equal(expectedResult);
return done();
});
});
server.stop(done);
});
});
});
});
});
});
it('Plugin test with advanced schema', (done) => {
const server = new Hapi.Server();
server.connection();
const testResponseSchema = Joi.array().required().items({
testObject: Joi.object().keys({
testPropOne: Joi.number().required(),
testPropTwo: Joi.number(),
testPropThree: Joi.string()
}).allow(null),
testNumber: Joi.number().required(),
testString: Joi.string().allow(null),
testEmail: Joi.string().email({ errorLevel: 68 }).lowercase().max(1000).required(),
testDate: Joi.date().iso().allow(null),
testArray: Joi.array().items(Joi.object().keys({
testPropOne: Joi.number().required(),
testPropTwo: Joi.string()
})),
testObjectArrayWithoutKeys: Joi.object(),
testPrimitiveArray: Joi.array().items(Joi.number())
});
const dataset = [{
'testObject': null,
'testNumber': 5,
'testString': 'test',
'testEmail': 'test@testprovider.com',
'testDate': '2016-07-04T13:56:31.000Z',
'testPrimitiveArray': [5, 5],
'testObjectArrayWithoutKeys': { 'testPropOne': 1 },
'testArray': [{ 'testPropOne': 1, 'testPropTwo': 'One' }, {
'testPropOne': 2,
'testPropTwo': 'Two'
}, { 'testPropOne': 3, 'testPropTwo': 'Three' }, { 'testPropOne': 4, 'testPropTwo': 'Four' }]
}];
server.register({ register: HapiCsv, options: {} }, (err) => {
expect(err, 'error').to.not.exist();
server.route([{
method: 'GET',
path: '/test',
config: {
handler: function (request, reply) {
reply(dataset);
},
response: {
schema: testResponseSchema
}
}
}]);
server.initialize((err) => {
expect(err, 'error').to.not.exist();
server.inject({
method: 'GET',
url: '/test',
headers: {
'Accept': 'text/csv'
}
}, (res) => {
const expectedResult = 'testObject.testPropOne,testObject.testPropTwo,testObject.testPropThree,testNumber,testString,testEmail,testDate,testArray_0.testPropOne,testArray_0.testPropTwo,testArray_1.testPropOne,testArray_1.testPropTwo,testArray_2.testPropOne,testArray_2.testPropTwo,testArray_3.testPropOne,testArray_3.testPropTwo,testArray_4.testPropOne,testArray_4.testPropTwo,testPrimitiveArray_0,testPrimitiveArray_1,testPrimitiveArray_2,testPrimitiveArray_3,testPrimitiveArray_4,\n,,,"5","test","test@testprovider.com","2016-07-04T13:56:31.000Z","1","One","2","Two","3","Three","4","Four",,,"5","5",,,,';
expect(res.result).to.equal(expectedResult);
server.stop(done);
});
});
});
});
it('Test plugin with schema existing of primitive type', (done) => {
const server = new Hapi.Server();
server.connection();
const testResponseSchema = Joi.number();
const dataset = 5;
server.register({ register: HapiCsv, options: {} }, (err) => {
expect(err, 'error').to.not.exist();
server.route([{
method: 'GET',
path: '/test',
config: {
handler: function (request, reply) {
reply(dataset);
},
response: {
schema: testResponseSchema
}
}
}]);
server.initialize((err) => {
expect(err, 'error').to.not.exist();
server.inject({
method: 'GET',
url: '/test',
headers: {
'Accept': 'text/csv'
}
}, (res) => {
expect(res.result).to.equal(5);
server.stop(done);
});
});
});
});
});
});

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc