Skip to content

Instantly share code, notes, and snippets.

@ozywuli
Created January 29, 2019 04:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ozywuli/e6bcd490ac033fe388154262c578966c to your computer and use it in GitHub Desktop.
Save ozywuli/e6bcd490ac033fe388154262c578966c to your computer and use it in GitHub Desktop.
const Immutable = require('immutable');
const flow = require('lodash/flow');
const assert = require('assert');
//==============================================================================
// Error message transformation
//==============================================================================
/**
* Recursively map over a Collection. Continue mapping until encountering a List whose items consist solely of strings. When this occurs, take the List of strings, concatenate them, and include them in the returned Collection
* @param {Collection}
* @returns {Collection}
*/
function transformData(data) {
return data.map(data => {
return Immutable.Map.isMap(data) ? transformData(data) : createString(data)
})
}
/**
* Return collections that are nonempty and contain data
* @param {Map} data - An Immutable Map
* @returns {Map}
*/
function getNonEmptyCollections(data) {
return data.filter(data => data.length || !data.isEmpty());
}
/**
* Convert Immutable Collection to a List and flatten it
* @param {Map} data - An Immutable Map
* @returns {List}
*/
function flattenCollection(data) {
return data.toList().flatten();
}
/**
* Pipes in a List, remove duplicate error items, joins every item into a single concatenated string with each item separated by a period and a space.
* @param {List} data - An Immutable List
* @returns {string} - Concatenated error string
*/
function createString(data) {
return Immutable.Set(data).concat(['']).join('. ').trim();
}
/**
* Checks if the Collection contains a Map or is itself a Map
* @param {Collection} data - An Immutable Collection
* @returns {boolean}
*/
function hasMap(data) {
return data.includes(Immutable.Map()) || Immutable.Map.isMap(data);
}
/**
* Check to see if a passed in key matches a key that represents a Collection whose nested structure should be preserved
* @param {array} keysToKeepnested - Array of keys, each of which represent a Collection whose nested structure should be preserved
* @param {string} key - A key representing a passed in Collection
* @returns {boolean}
*/
function keepNested(keysToKeepNested, key) {
return keysToKeepNested.includes(key);
}
/**
* Decide whether to map over a Collection or ultimately return a concatenated string representing the details of an error
* @param {Collection} data - An Immutable Collection
* @returns {string} - Concatenated error string
*/
function transformCollection(data) {
return hasMap(data) ? transformData(data) : createString(data);
}
/**
* Based on `transformCollection` but returns a flat structure, ie, a concatenated string
* Use Lodash's flow function to pipe data through a series of functions
* @param {Collection} data - An Immutable Collection
* @returns {string} - Concatenated error string
*/
let transformCollectionFlat = flow([getNonEmptyCollections, flattenCollection, transformCollection]);
/**
* Takes in an Immutable Collection of errors and returns a transformed errors Collection
* @param {errors} - Immutable Collection
* @param {array} - Array of keys representing Collections whose nested structuree should be preserved
* @returns {Collection} - Returns transformed errors Collection
*/
function transformErrors(errors, keysToKeepNested) {
return Immutable.Map(errors).map((data, key) => {
return keepNested(keysToKeepNested, key) ? transformCollection(data) : transformCollectionFlat(data);
})
}
//==============================================================================
// Tests
//==============================================================================
it('should tranform errors', () => {
// example error object returned from API converted to Immutable.Map
const errors = Immutable.fromJS({
name: ['This field is required'],
age: ['This field is required', 'Only numeric characters are allowed'],
urls: [{}, {}, {
site: {
code: ['This site code is invalid'],
id: ['Unsupported id'],
}
}],
url: {
site: {
code: ['This site code is invalid'],
id: ['Unsupported id'],
}
},
tags: [{}, {
non_field_errors: ['Only alphanumeric characters are allowed'],
another_error: ['Only alphanumeric characters are allowed'],
third_error: ['Third error']
}, {}, {
non_field_errors: [
'Minumum length of 10 characters is required',
'Only alphanumeric characters are allowed',
],
}],
tag: {
nested: {
non_field_errors: ['Only alphanumeric characters are allowed'],
},
},
});
// in this specific case,
// errors for `url` and `urls` keys should be nested
// see expected object below
const result = transformErrors(errors, ['urls', 'url']);
assert.deepEqual(result.toJS(), {
name: 'This field is required.',
age: 'This field is required. Only numeric characters are allowed.',
urls: [{}, {}, {
site: {
code: 'This site code is invalid.',
id: 'Unsupported id.',
},
}],
url: {
site: {
code: 'This site code is invalid.',
id: 'Unsupported id.',
},
},
tags: 'Only alphanumeric characters are allowed. Third error. ' +
'Minumum length of 10 characters is required.',
tag: 'Only alphanumeric characters are allowed.',
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment