Socket
Socket
Sign inDemoInstall

hapi-csv

Package Overview
Dependencies
Maintainers
3
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 3.0.2 to 3.1.0

coverage.html

71

lib/index.js

@@ -5,4 +5,4 @@ 'use strict';

const Hoek = require('hoek');
const Async = require('async');
const internals = {

@@ -14,3 +14,3 @@ routeMap: new Map()

options.separator = options.separator || ',';
internals.separator = options.separator || ',';
internals.maximumElementsInArray = options.maximumElementsInArray || 5;

@@ -40,3 +40,2 @@

if (path.endsWith('.csv')) {
request.setUrl(`${path.substring(0, path.length - 4)}${request.url.search}`);

@@ -66,5 +65,30 @@ request.headers.accept = 'text/csv';

if (preferedType && internals.routeMap.has(internals.createRouteMethodString(request.route.path, request.route.method))) {
if (!(preferedType && internals.routeMap.has(internals.createRouteMethodString(request.route.path, request.route.method)))) {
return reply.continue();
}
const dynamicHandlerObject = request.route.settings.plugins['hapi-csv'] || {};
const resolvedSchemasObject = {};
return Async.forEachOf(dynamicHandlerObject, (handler, path, callback) => {
return handler(request, (err, dynamicSchema) => {
if (err) {
return callback(err);
}
resolvedSchemasObject[path] = dynamicSchema;
return callback();
});
}, (err) => {
if (err) {
return reply(err);
}
const schema = internals.routeMap.get(internals.createRouteMethodString(request.route.path, request.route.method));
const csv = internals.schemaToCsv(schema, request.response.source, options.separator, options.resultKey);
const csv = internals.schemaToCsv(schema, resolvedSchemasObject, request.response.source, options.resultKey);

@@ -76,5 +100,3 @@ // FUTURE add header=present but wait for response on https://github.com/hapijs/hapi/issues/3243

return reply(csv).type(preferedType).header('content-disposition', 'attachment;');
}
return reply.continue();
});
});

@@ -90,3 +112,3 @@

internals.schemaToCsv = (schema, dataset, separator, resultKey) => {
internals.schemaToCsv = (schema, dynamicSchemas, dataset, resultKey) => {

@@ -105,6 +127,6 @@ // We return the dataset if the dataset is not an array or an object, just a primitive type

const headerQueryArray = internals.parseSchema(schemaDescription);
const headerQueryArray = internals.parseSchema(schemaDescription, dynamicSchemas);
const headerQueryMap = internals.arrayToMap(headerQueryArray);
return internals.headerQueryMapToCsv(headerQueryMap, dataset, separator);
return internals.headerQueryMapToCsv(headerQueryMap, dataset);
};

@@ -114,6 +136,10 @@

* Recursive function which parses a Joi schema to an array of header, query entries
* `parrentIsArray` is true when the previous call to the function handled a joi schema of type array
* `parentIsArray` is true when the previous call to the function handled a joi schema of type array
*/
internals.parseSchema = (joiSchema, keyName, parrentIsArray) => {
internals.parseSchema = (joiSchema, dynamicSchemas, keyName, path, parentIsArray) => {
if (dynamicSchemas[path]) {
joiSchema = Joi.compile(dynamicSchemas[path]).describe();
}
if (joiSchema.type === 'object') {

@@ -123,3 +149,3 @@

const childrenKeys = Object.keys(joiSchema.children);
let children = childrenKeys.map((key) => internals.parseSchema(joiSchema.children[key], key));
let children = childrenKeys.map((key) => internals.parseSchema(joiSchema.children[key], dynamicSchemas, key, path !== undefined ? `${path}.${key}` : key));

@@ -138,3 +164,3 @@ if (!keyName) {

// If the previous call was not for a joi schema of type array, we alter the keys to prefix them with the keyName
if (!parrentIsArray) {
if (!parentIsArray) {
const name = `${keyName}.${key}`;

@@ -157,3 +183,3 @@ child[name] = child[key];

const item = joiSchema.items[0];
const parsedItem = internals.parseSchema(item, keyName, true);
const parsedItem = internals.parseSchema(item, dynamicSchemas, keyName, path, true);

@@ -165,3 +191,3 @@ if (keyName && parsedItem) {

prefixedItemArray.push(parsedItem.map((headerQuery, index) => {
prefixedItemArray.push(parsedItem.map((headerQuery) => {

@@ -206,3 +232,3 @@ const key = Object.keys(headerQuery)[0];

internals.headerQueryMapToCsv = (headerQueryMap, dataset, separator) => {
internals.headerQueryMapToCsv = (headerQueryMap, dataset) => {

@@ -212,3 +238,3 @@ let headerRow = '';

for (const header of headerQueryMap.keys()) {
headerRow += `${header}${separator}`;
headerRow += `${header}${internals.separator}`;
}

@@ -249,3 +275,3 @@

dataRow += separator;
dataRow += internals.separator;
}

@@ -268,2 +294,5 @@

internals.createRouteMethodString = (route, method) => route + '_' + method;
internals.createRouteMethodString = (route, method) => {
return `${route}_${method}`;
};

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

{
"name": "hapi-csv",
"version": "3.0.2",
"version": "3.1.0",
"description": "Hapi plugin for converting a Joi response schema and dataset to csv",

@@ -24,12 +24,13 @@ "main": "lib/index.js",

"dependencies": {
"hoek": "4.x.x"
"hoek": "4.x.x",
"async": "^2.5.0"
},
"devDependencies": {
"joi": "10.x.x",
"joi": "^11.0.0",
"code": "4.x.x",
"hapi": "16.x.x",
"lab": "12.x.x"
"lab": "14.x.x"
},
"peerDependencies": {
"hapi": ">=13.x.x",
"hapi": ">=16.1.1",
"joi": ">=10.x.x"

@@ -36,0 +37,0 @@ },

# Hapi-csv [![Build Status](https://travis-ci.org/Salesflare/hapi-csv.svg?branch=master)](https://travis-ci.org/Salesflare/hapi-csv)
## What
Converts the response to csv based on the Joi response schema when the Accept header includes `text/csv` or `application/csv` or the requested route ends with `.csv`

@@ -8,3 +9,3 @@

`npm install hapi-csv`
`npm install --save hapi-csv`

@@ -15,11 +16,12 @@ Register the hapi-csv plugin on the server

server.register({
register: require('hapi-csv'),
options: {
maximumElementsInArray: 5,
separator: ','
}
register: require('hapi-csv'),
options: {
maximumElementsInArray: 5,
separator: ',',
resultKey: 'items'
}
}, function (err) {
if (err) throw err;
...
if (err) throw err;
...
});

@@ -50,9 +52,13 @@ ```

Or do `GET /users.csv`.
The header approach is prefered.
The header approach is preferred.
When the request path ends in `.csv` the `.csv` part will be stripped and the accept header will be set to `text/csv`.
Currently the `content-disposition` header is set to `attachment;` by default since this plugin is intended for exporting purposes, if this hinders you just let us know.
Currently the `Content-Disposition` header is set to `attachment;` by default since this plugin is intended for exporting purposes, if this hinders you just let us know.
To handle typical pagination responses like
### Paginated responses
To handle typical pagination responses pass the `resultKey` option. The value is the top level key you want to convert to csv.
```json
// paginated response
{

@@ -67,4 +73,2 @@ "page": 1,

pass in the `resultKey` option:
```javascript

@@ -74,3 +78,3 @@ server.register({

options: {
resultKey: 'items'
resultKey: 'items' // We only want the `items` in csv
}

@@ -83,1 +87,71 @@ }, function (err) {

```
### Dynamic schemas
Hapi-csv supports dynamic response schemas as well.
Imagine one of your property's schema is dynamic but you still want to export the value of it to csv.
You can tell hapi-csv to translate a given key on the fly when it is converting the response to csv (`onPreResponse`).
On the route config set the plugin config to an object like
```javascript
{
'keyPath': (request, callback) => {
return callback(/* Error, Joi schema */)
}
}
```
The key is the path of the property you want to resolve dynamically.
E.g.
```javascript
Joi.object().keys({
a: Joi.object(),
b: Joi.object().keys({
c: Joi.object()
})
})
```
If you want to convert `a` the key would be `a`.
For `c` it would be `b.c`.
Full example:
```javascript
server.route([{
...,
config: {
...,
response: {
schema: Joi.object().keys({
first_name: Joi.string(),
last_name: Joi.string(),
age: Joi.number(),
custom: Joi.object(),
deepCustom: Joi.object().keys({
deepestCustom: Joi.object()
})
})
},
plugins: {
'hapi-csv': {
'custom': (request, callback) => {
const schema = Joi.object().keys({
id: Joi.number(),
name: Joi.string()
});
return callback(null, schema);
},
'deepCustom.deepestCustom': (request, callback) => {
return callback(new Error('nope'));
}
}
}
}
])
```

@@ -76,15 +76,15 @@ 'use strict';

},
{
method: 'POST',
path: '/user',
config: {
handler: function (request, reply) {
{
method: 'POST',
path: '/user',
config: {
handler: function (request, reply) {
return reply(postUser);
},
response: {
schema: testPostResponseSchema
}
return reply(postUser);
},
response: {
schema: testPostResponseSchema
}
}, {
}
}, {
method: 'GET',

@@ -554,2 +554,144 @@ path: '/userWithoutSchema',

describe('Dynamic schemas', () => {
it('Uses dynamic schemas', (done) => {
const user = {
first_name: 'firstName',
last_name: 'lastName',
age: 25,
tag: { id: 1, name: 'guitar' }
};
const userCSV = 'first_name,last_name,age,tag.id,tag.name,\n"firstName","lastName","25","1","guitar",';
const server = new Hapi.Server();
server.connection();
return server.register({
register: HapiCsv
}, (err) => {
expect(err, 'error').to.not.exist();
server.route([{
method: 'GET',
path: '/test',
config: {
handler: function (request, reply) {
return reply(user);
},
response: {
schema: Joi.object().keys({
first_name: Joi.string(),
last_name: Joi.string(),
age: Joi.number(),
tag: Joi.object()
})
},
plugins: {
'hapi-csv': {
'tag': (request, callback) => {
const schema = Joi.object().keys({
id: Joi.number(),
name: Joi.string()
});
return callback(null, schema);
}
}
}
}
}]);
return server.initialize((err) => {
expect(err, 'error').to.not.exist();
return server.inject({
method: 'GET',
url: '/test',
headers: {
'Accept': 'text/csv'
}
}, (res) => {
expect(res.result, 'result').to.equal(userCSV);
expect(res.headers['content-type']).to.equal('text/csv; charset=utf-8');
expect(res.headers['content-disposition']).to.equal('attachment;');
return server.stop(done);
});
});
});
});
it('Uses dynamic schemas: resolver function throws an error', (done) => {
const user = {
first_name: 'firstName',
last_name: 'lastName',
age: 25,
tag: { id: 1, name: 'guitar' }
};
const server = new Hapi.Server();
server.connection();
return server.register({
register: HapiCsv
}, (err) => {
expect(err, 'error').to.not.exist();
server.route([{
method: 'GET',
path: '/test',
config: {
handler: function (request, reply) {
return reply(user);
},
response: {
schema: Joi.object().keys({
first_name: Joi.string(),
last_name: Joi.string(),
age: Joi.number(),
tag: Joi.object()
})
},
plugins: {
'hapi-csv': {
'tag': (request, callback) => {
return callback(new Error('ERROR'));
}
}
}
}
}]);
return server.initialize((err) => {
expect(err, 'error').to.not.exist();
return server.inject({
method: 'GET',
url: '/test',
headers: {
'Accept': 'text/csv'
}
}, (res) => {
expect(res.statusCode, 'statusCode').to.equal(500);
return server.stop(done);
});
});
});
});
});
describe('Result key (e.g. for pagination)', () => {

@@ -556,0 +698,0 @@

Sorry, the diff of this file is not supported yet

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