@ellipticltd/aml-utils 0.16.26 → 0.16.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.circleci/config.yml +87 -0
- package/.claude/settings.local.json +7 -0
- package/.eslintrc +45 -0
- package/.huskyrc +5 -0
- package/.mocharc.json +3 -0
- package/.nvmrc +1 -0
- package/.nycrc.json +11 -0
- package/.releaserc.json +18 -0
- package/.snyk +12 -0
- package/README.md +43 -11
- package/codecov.yml +29 -0
- package/commitlint.config.js +1 -0
- package/dist/errors/errors.js +42 -0
- package/dist/errors/errors.spec.d.ts +1 -0
- package/dist/errors/errors.spec.js +23 -0
- package/dist/file-parser/__tests/file-parser.spec.d.ts +1 -0
- package/dist/file-parser/__tests/file-parser.spec.js +109 -0
- package/dist/file-parser/__tests/parse-row.spec.d.ts +1 -0
- package/dist/file-parser/__tests/parse-row.spec.js +29 -0
- package/dist/file-parser/__tests/sanitize-rows.spec.d.ts +1 -0
- package/dist/file-parser/__tests/sanitize-rows.spec.js +78 -0
- package/{lib → dist}/file-parser/errors.js +1 -1
- package/{lib → dist}/file-parser/file-parser.d.ts +4 -2
- package/dist/file-parser/file-parser.js +55 -0
- package/{lib → dist}/file-parser/parse-row.js +1 -2
- package/{lib/file-parser/sanitizeRows.d.ts → dist/file-parser/sanitzeRows.d.ts} +1 -3
- package/dist/file-parser/sanitzeRows.js +18 -0
- package/dist/formatting/formatting.js +17 -0
- package/dist/formatting/formatting.spec.d.ts +1 -0
- package/dist/formatting/formatting.spec.js +37 -0
- package/{lib → dist}/index.d.ts +1 -1
- package/dist/index.js +22 -0
- package/dist/middleware/middleware.js +22 -0
- package/dist/orm-helpers/ormHelpers.js +17 -0
- package/dist/orm-helpers/ormHelpers.spec.d.ts +1 -0
- package/dist/orm-helpers/ormHelpers.spec.js +38 -0
- package/{lib → dist}/structured-file-parser/errors.js +0 -1
- package/{lib → dist}/structured-file-parser/parse-row.js +1 -2
- package/{lib → dist}/structured-file-parser/sanitize-rows.js +4 -3
- package/{lib → dist}/structured-file-parser/structured-file-parser.d.ts +2 -2
- package/dist/structured-file-parser/structured-file-parser.js +98 -0
- package/{lib → dist}/types/types.d.ts +6 -0
- package/dist/types/types.js +203 -0
- package/{lib → dist}/validations/validations.d.ts +110 -161
- package/dist/validations/validations.js +470 -0
- package/dist/validations/validations.spec.d.ts +1 -0
- package/dist/validations/validations.spec.js +463 -0
- package/lib/errors/errors.js +30 -19
- package/lib/errors/errors.spec.js +37 -0
- package/lib/file-parser/__tests/file-parser.spec.js +107 -0
- package/lib/file-parser/__tests/parse-row.spec.js +35 -0
- package/lib/file-parser/__tests/sanitize-rows.spec.js +88 -0
- package/lib/file-parser/errors.ts +7 -0
- package/lib/file-parser/file-parser.ts +84 -0
- package/lib/file-parser/parse-row.ts +52 -0
- package/lib/file-parser/sanitzeRows.ts +32 -0
- package/lib/formatting/formatting.js +12 -11
- package/lib/formatting/formatting.spec.js +45 -0
- package/lib/index.ts +19 -0
- package/lib/middleware/middleware.js +17 -14
- package/lib/orm-helpers/ormHelpers.js +13 -12
- package/lib/orm-helpers/ormHelpers.spec.js +41 -0
- package/lib/structured-file-parser/errors.ts +25 -0
- package/lib/structured-file-parser/parse-row.ts +52 -0
- package/lib/structured-file-parser/sanitize-rows.ts +24 -0
- package/lib/structured-file-parser/structured-file-parser.ts +155 -0
- package/lib/types/types.js +203 -191
- package/lib/validations/validations.js +461 -669
- package/lib/validations/validations.spec.js +603 -0
- package/package.json +61 -8
- package/tsconfig.json +26 -0
- package/lib/errors/errors.js.map +0 -1
- package/lib/file-parser/errors.js.map +0 -1
- package/lib/file-parser/file-parser.js +0 -59
- package/lib/file-parser/file-parser.js.map +0 -1
- package/lib/file-parser/parse-row.js.map +0 -1
- package/lib/file-parser/sanitizeRows.js +0 -15
- package/lib/file-parser/sanitizeRows.js.map +0 -1
- package/lib/formatting/formatting.js.map +0 -1
- package/lib/index.js +0 -21
- package/lib/index.js.map +0 -1
- package/lib/middleware/middleware.js.map +0 -1
- package/lib/orm-helpers/ormHelpers.js.map +0 -1
- package/lib/structured-file-parser/errors.js.map +0 -1
- package/lib/structured-file-parser/parse-row.js.map +0 -1
- package/lib/structured-file-parser/sanitize-rows.js.map +0 -1
- package/lib/structured-file-parser/structured-file-parser.js +0 -95
- package/lib/structured-file-parser/structured-file-parser.js.map +0 -1
- package/lib/types/types.js.map +0 -1
- package/lib/validations/validations.js.map +0 -1
- /package/{lib → dist}/errors/errors.d.ts +0 -0
- /package/{lib → dist}/file-parser/errors.d.ts +0 -0
- /package/{lib → dist}/file-parser/parse-row.d.ts +0 -0
- /package/{lib → dist}/formatting/formatting.d.ts +0 -0
- /package/{lib → dist}/middleware/middleware.d.ts +0 -0
- /package/{lib → dist}/orm-helpers/ormHelpers.d.ts +0 -0
- /package/{lib → dist}/structured-file-parser/errors.d.ts +0 -0
- /package/{lib → dist}/structured-file-parser/parse-row.d.ts +0 -0
- /package/{lib → dist}/structured-file-parser/sanitize-rows.d.ts +0 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import sinon from 'sinon';
|
|
2
|
+
import { expect } from 'chai';
|
|
3
|
+
import parseFile from '../file-parser';
|
|
4
|
+
import * as sanitizeRows from '../sanitzeRows';
|
|
5
|
+
import * as parseRow from '../parse-row';
|
|
6
|
+
|
|
7
|
+
describe('file-parser', () => {
|
|
8
|
+
const dependencies = {
|
|
9
|
+
processRow: sinon.spy(),
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const parsedRows = [];
|
|
13
|
+
let mockParseRow;
|
|
14
|
+
let mockSanitizeRows;
|
|
15
|
+
let mockSetTimeout;
|
|
16
|
+
|
|
17
|
+
before(() => {
|
|
18
|
+
mockParseRow = sinon.stub(parseRow, 'default').returns(parsedRows);
|
|
19
|
+
mockSanitizeRows = sinon.stub(sanitizeRows, 'default').returns([{}]);
|
|
20
|
+
mockSetTimeout = sinon.stub(global, 'setTimeout').callsFake((callback) => callback());
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
sinon.resetHistory();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
after(() => {
|
|
28
|
+
sinon.restore();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should call parseRow once per input new line', async () => {
|
|
32
|
+
const input = '\n';
|
|
33
|
+
|
|
34
|
+
await parseFile(input, 50, dependencies);
|
|
35
|
+
|
|
36
|
+
expect(mockParseRow).to.be.calledTwice;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should call pass parseRow result into sanitizeRows', async () => {
|
|
40
|
+
const input = '\n';
|
|
41
|
+
|
|
42
|
+
await parseFile(input, 50, dependencies);
|
|
43
|
+
|
|
44
|
+
expect(mockSanitizeRows).to.be.calledOnce;
|
|
45
|
+
expect(mockSanitizeRows).to.be.deep.calledWith([[], []], dependencies.processRow);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should throw error when number of rows is more than max rows', async () => {
|
|
49
|
+
const input = '\n';
|
|
50
|
+
|
|
51
|
+
mockSanitizeRows.returns(new Array(20));
|
|
52
|
+
try {
|
|
53
|
+
await parseFile(input, 10, dependencies);
|
|
54
|
+
expect(1).to.equal(2);
|
|
55
|
+
} catch (ex) {
|
|
56
|
+
expect(ex.name).to.equal('TooManyRowsError');
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should throw error when no rows returned from sanitizeRows', async () => {
|
|
61
|
+
const input = '\n';
|
|
62
|
+
|
|
63
|
+
mockSanitizeRows.returns([]);
|
|
64
|
+
try {
|
|
65
|
+
await parseFile(input, 10, dependencies);
|
|
66
|
+
expect(1).to.equal(2);
|
|
67
|
+
} catch (ex) {
|
|
68
|
+
expect(ex.name).to.equal('Error');
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should throw error an error when content is "invalidFileType"', async () => {
|
|
73
|
+
const input = 'invalidFileType';
|
|
74
|
+
|
|
75
|
+
mockSanitizeRows.returns([]);
|
|
76
|
+
try {
|
|
77
|
+
await parseFile(input, 10, dependencies);
|
|
78
|
+
expect(1).to.equal(2);
|
|
79
|
+
} catch (ex) {
|
|
80
|
+
expect(ex.message).to.equal('Invalid file type');
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should call process row once per row in single timeout', async () => {
|
|
85
|
+
const input = '\n';
|
|
86
|
+
|
|
87
|
+
const fakeRows = (new Array(30)).fill({});
|
|
88
|
+
mockSanitizeRows.returns(fakeRows);
|
|
89
|
+
|
|
90
|
+
await parseFile(input, 70, dependencies);
|
|
91
|
+
|
|
92
|
+
expect(dependencies.processRow).to.be.callCount(30);
|
|
93
|
+
expect(mockSetTimeout).to.be.calledOnce;
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should call process row once per row in two timeouts when more than 30 rows', async () => {
|
|
97
|
+
const input = '\n';
|
|
98
|
+
|
|
99
|
+
const fakeRows = (new Array(50)).fill({});
|
|
100
|
+
mockSanitizeRows.returns(fakeRows);
|
|
101
|
+
|
|
102
|
+
await parseFile(input, 70, dependencies);
|
|
103
|
+
|
|
104
|
+
expect(dependencies.processRow).to.be.callCount(50);
|
|
105
|
+
expect(mockSetTimeout).to.be.calledTwice;
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { expect } from 'chai';
|
|
2
|
+
import parseRow from '../parse-row';
|
|
3
|
+
|
|
4
|
+
describe('parse-row', () => {
|
|
5
|
+
it('should handle tabs', () => {
|
|
6
|
+
const input = 'asdas \t asdar \t aaaaa \t bbb bb';
|
|
7
|
+
|
|
8
|
+
const result = parseRow(input);
|
|
9
|
+
expect(result).to.have.length(4);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should handle comas', () => {
|
|
13
|
+
const input = 'asdas, asdar, aaaaa, bbb bb';
|
|
14
|
+
|
|
15
|
+
const result = parseRow(input);
|
|
16
|
+
|
|
17
|
+
expect(result).to.have.length(4);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should handle spaces', () => {
|
|
21
|
+
const input = 'asdas asdar aaaaa bbb bb';
|
|
22
|
+
|
|
23
|
+
const result = parseRow(input);
|
|
24
|
+
|
|
25
|
+
expect(result).to.have.length(4);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should handle spaces', () => {
|
|
29
|
+
const input = 'asdas asdar aaaaa bbb bb';
|
|
30
|
+
|
|
31
|
+
const result = parseRow(input);
|
|
32
|
+
|
|
33
|
+
expect(result).to.have.length(5);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { expect } from 'chai';
|
|
2
|
+
import sinon from 'sinon';
|
|
3
|
+
import sanitizeRows from '../sanitzeRows';
|
|
4
|
+
|
|
5
|
+
describe('sanitize-rows', () => {
|
|
6
|
+
it('should remove empty rows at the start of the array', () => {
|
|
7
|
+
const input = [
|
|
8
|
+
['', '', ''],
|
|
9
|
+
['this', 'is', 'valid'],
|
|
10
|
+
['this', 'is', 'valid'],
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const result = sanitizeRows(input, () => ({
|
|
14
|
+
isValid: true,
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
expect(result).to.have.length(2);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should remove empty rows at the end of the array', () => {
|
|
21
|
+
const input = [
|
|
22
|
+
['this', 'is', 'valid'],
|
|
23
|
+
['this', 'is', 'valid'],
|
|
24
|
+
['', '', ''],
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const result = sanitizeRows(input, () => ({
|
|
28
|
+
isValid: true,
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
expect(result).to.have.length(2);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should remove empty rows at the start and end of the array', () => {
|
|
35
|
+
const input = [
|
|
36
|
+
['', '', ''],
|
|
37
|
+
['', '', ''],
|
|
38
|
+
['', '', ''],
|
|
39
|
+
['this', 'is', 'valid'],
|
|
40
|
+
['this', 'is', 'valid'],
|
|
41
|
+
['', '', ''],
|
|
42
|
+
['', '', ''],
|
|
43
|
+
['', '', ''],
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const result = sanitizeRows(input, () => ({
|
|
47
|
+
isValid: true,
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
expect(result).to.have.length(2);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should remove the first row if it is not empty and not valid', () => {
|
|
54
|
+
const input = [
|
|
55
|
+
['', '', ''],
|
|
56
|
+
['this', 'is', 'invalid'],
|
|
57
|
+
['this', 'is', 'valid'],
|
|
58
|
+
['this', 'is', 'valid'],
|
|
59
|
+
['', '', ''],
|
|
60
|
+
['', '', ''],
|
|
61
|
+
['', '', ''],
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
const returnsFalseOnce = sinon.stub();
|
|
65
|
+
returnsFalseOnce.returns(true);
|
|
66
|
+
returnsFalseOnce.onFirstCall().returns(false);
|
|
67
|
+
|
|
68
|
+
const result = sanitizeRows(input, returnsFalseOnce);
|
|
69
|
+
expect(result).to.have.length(2);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should allow rows with some empty values', () => {
|
|
73
|
+
const input = [
|
|
74
|
+
['this', 'is', 'invalid'],
|
|
75
|
+
['this', 'is', 'valid'],
|
|
76
|
+
['this', 'is', 'valid'],
|
|
77
|
+
['valid', '', ''],
|
|
78
|
+
['', '', ''],
|
|
79
|
+
['', '', ''],
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
const result = sanitizeRows(input, () => ({
|
|
83
|
+
isValid: true,
|
|
84
|
+
}));
|
|
85
|
+
|
|
86
|
+
expect(result).to.have.length(4);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import parseRow from './parse-row';
|
|
2
|
+
import { TooManyRowsError } from './errors';
|
|
3
|
+
import sanitizeRows from './sanitzeRows';
|
|
4
|
+
|
|
5
|
+
export type ProcessingEntry = {
|
|
6
|
+
isValid: boolean,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type Dependencies<T extends ProcessingEntry> = {
|
|
10
|
+
processRow: (cells: string[]) => T,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function chunk<T>(array: T[], size: number): T[][] {
|
|
14
|
+
if (!array.length) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
const head = array.slice(0, size);
|
|
18
|
+
const tail = array.slice(size);
|
|
19
|
+
|
|
20
|
+
return [head, ...chunk(tail, size)];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function processChunk<T extends ProcessingEntry>(
|
|
24
|
+
parsedRows: string[][],
|
|
25
|
+
dependencies: Dependencies<T>,
|
|
26
|
+
): Promise<T[]> {
|
|
27
|
+
const { processRow } = dependencies;
|
|
28
|
+
return new Promise(
|
|
29
|
+
(
|
|
30
|
+
resolve: (value: T[] | undefined) => void,
|
|
31
|
+
reject: (error: Error) => void,
|
|
32
|
+
): void => {
|
|
33
|
+
setTimeout(
|
|
34
|
+
() => {
|
|
35
|
+
try {
|
|
36
|
+
const processedRows = parsedRows.map(processRow);
|
|
37
|
+
resolve(processedRows);
|
|
38
|
+
} catch (ex) {
|
|
39
|
+
reject(ex);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
0,
|
|
43
|
+
);
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function parseFile<T extends ProcessingEntry>(
|
|
49
|
+
fileContent: string,
|
|
50
|
+
maxRows: number,
|
|
51
|
+
dependencies: Dependencies<T>,
|
|
52
|
+
): Promise<T[]> {
|
|
53
|
+
const { processRow } = dependencies;
|
|
54
|
+
|
|
55
|
+
if (fileContent === 'invalidFileType') {
|
|
56
|
+
throw Error('Invalid file type');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const rows = fileContent.split('\n');
|
|
60
|
+
const parsedRows = rows.map(parseRow);
|
|
61
|
+
|
|
62
|
+
const sanitizedRows = sanitizeRows(parsedRows, processRow);
|
|
63
|
+
|
|
64
|
+
if (sanitizedRows.length > maxRows) {
|
|
65
|
+
throw new TooManyRowsError();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (sanitizedRows.length === 0) {
|
|
69
|
+
throw new Error('No rows');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const parsedRowChunks = chunk(sanitizedRows, 30);
|
|
73
|
+
let results: T[] = [];
|
|
74
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
75
|
+
for (const currentRowChunk of parsedRowChunks) {
|
|
76
|
+
// eslint-disable-next-line no-await-in-loop
|
|
77
|
+
const chunkResult = await processChunk(currentRowChunk, dependencies);
|
|
78
|
+
results = results.concat(chunkResult);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return results;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export default parseFile;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { pipe } from 'lodash/fp';
|
|
2
|
+
|
|
3
|
+
const parseRow = (row: string): string[] => {
|
|
4
|
+
const trySplitByTab = (line: string | string[]): string | string[] => {
|
|
5
|
+
if (typeof line === 'string' && line.match('\t')) {
|
|
6
|
+
return line.split('\t');
|
|
7
|
+
}
|
|
8
|
+
return line;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const trySplitByComa = (line: string | string[]): string | string[] => {
|
|
12
|
+
if (typeof line === 'string' && line.match(',')) {
|
|
13
|
+
return line.split(',');
|
|
14
|
+
}
|
|
15
|
+
return line;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const trySplitByMultipleSpaces = (line: string | string[]): string | string[] => {
|
|
19
|
+
if (typeof line === 'string' && line.match(/ {3,}/)) {
|
|
20
|
+
return line.split(/ {3,}/);
|
|
21
|
+
}
|
|
22
|
+
return line;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const trySplitBySpace = (line: string | string[]): string | string[] => {
|
|
26
|
+
if (typeof line === 'string' && line.match(' ')) {
|
|
27
|
+
return line.split(' ');
|
|
28
|
+
}
|
|
29
|
+
return line;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const trySplitByNothing = (line: string | string[]): string[] => {
|
|
33
|
+
if (typeof line === 'string') {
|
|
34
|
+
return [line];
|
|
35
|
+
}
|
|
36
|
+
return line;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const splitRow = pipe(
|
|
40
|
+
trySplitByTab,
|
|
41
|
+
trySplitByComa,
|
|
42
|
+
trySplitByMultipleSpaces,
|
|
43
|
+
trySplitBySpace,
|
|
44
|
+
trySplitByNothing,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const cells = splitRow(row);
|
|
48
|
+
|
|
49
|
+
return cells.map((c: string) => c.trim());
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export default parseRow;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ProcessingEntry } from './file-parser';
|
|
2
|
+
|
|
3
|
+
const sensicalRowCheck = (cells: string[]): boolean => (
|
|
4
|
+
cells.some((cell: string) => !!cell)
|
|
5
|
+
);
|
|
6
|
+
|
|
7
|
+
function sanitizeRows<T extends ProcessingEntry>(
|
|
8
|
+
parsedRows: string[][],
|
|
9
|
+
processRow: (row: string[]) => T,
|
|
10
|
+
): string[][] {
|
|
11
|
+
const firstSensicalRowIndex = parsedRows
|
|
12
|
+
.findIndex(sensicalRowCheck);
|
|
13
|
+
|
|
14
|
+
const lastSensicalRowIndex = [...parsedRows]
|
|
15
|
+
.reverse()
|
|
16
|
+
.findIndex(sensicalRowCheck);
|
|
17
|
+
|
|
18
|
+
const sanitizedRows = parsedRows
|
|
19
|
+
.splice(
|
|
20
|
+
firstSensicalRowIndex,
|
|
21
|
+
parsedRows.length - firstSensicalRowIndex - lastSensicalRowIndex,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const firstRow = processRow(sanitizedRows[0]);
|
|
25
|
+
|
|
26
|
+
if (firstRow.isValid) {
|
|
27
|
+
return sanitizedRows;
|
|
28
|
+
}
|
|
29
|
+
return sanitizedRows.splice(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default sanitizeRows;
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
|
+
|
|
2
3
|
const rethrowError = (fnName, e) => {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
return `${fnName} - ${e.name}: ${e.statusCode} - ${formattedError}`;
|
|
4
|
+
let formattedError;
|
|
5
|
+
if (_.isPlainObject(e.error) || _.isArray(e.error)) {
|
|
6
|
+
formattedError = `\n${JSON.stringify(e.error, null, 2)}`;
|
|
7
|
+
} else {
|
|
8
|
+
formattedError = e.error;
|
|
9
|
+
}
|
|
10
|
+
return `${fnName} - ${e.name}: ${e.statusCode} - ${formattedError}`;
|
|
11
11
|
};
|
|
12
|
+
|
|
12
13
|
const sqlEscapeWildcard = (str) => str.replace(/_|%|\\/g, (x) => `\\${x}`);
|
|
14
|
+
|
|
13
15
|
module.exports = {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
rethrowError,
|
|
17
|
+
sqlEscapeWildcard,
|
|
16
18
|
};
|
|
17
|
-
//# sourceMappingURL=formatting.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const chai = require('chai');
|
|
2
|
+
const { sqlEscapeWildcard, rethrowError } = require('./formatting');
|
|
3
|
+
|
|
4
|
+
const { expect } = chai;
|
|
5
|
+
|
|
6
|
+
describe('formatting.sqlEscapeWildcard', () => {
|
|
7
|
+
describe('when passed an empty string', () => {
|
|
8
|
+
it('returns an empty string', () => sqlEscapeWildcard('').should.equal(''));
|
|
9
|
+
});
|
|
10
|
+
describe('when passed a string with no sql wildcards', () => {
|
|
11
|
+
it('returns the same string', () => {
|
|
12
|
+
const str = 'foo bar';
|
|
13
|
+
return sqlEscapeWildcard(str).should.equal(str);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
return describe('when passed a string with sql wildcards', () => {
|
|
17
|
+
it('returns the string with \\ % _ escaped', () => {
|
|
18
|
+
const str = 'foo\\dfs_erwr%rewr';
|
|
19
|
+
return sqlEscapeWildcard(str).should.equal('foo\\\\dfs\\_erwr\\%rewr');
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('rethrowError', () => {
|
|
25
|
+
it('returns an error when given a string', (
|
|
26
|
+
|
|
27
|
+
) => rethrowError('myFunc', { error: 'Oh dear, myFunc is broken' })
|
|
28
|
+
.should.equal('myFunc - undefined: undefined - Oh dear, myFunc is broken'));
|
|
29
|
+
|
|
30
|
+
it('returns an error when passed an error object', () => rethrowError('myFunc', {
|
|
31
|
+
name: "I'm a teapot",
|
|
32
|
+
statusCode: 418,
|
|
33
|
+
error: "I'm a little teapot, short and stout",
|
|
34
|
+
}).should.equal("myFunc - I'm a teapot: 418 - I'm a little teapot, short and stout"));
|
|
35
|
+
|
|
36
|
+
it('accepts an array containing an error', () => {
|
|
37
|
+
const thrownError = rethrowError('myFunc', {
|
|
38
|
+
error: ["I'm a teapot", 418, "I'm a little teapot, short and stout"],
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const expected = JSON.stringify('myFunc - undefined: undefined - \n[\n "I\'m a teapot",\n 418,\n "I\'m a little teapot, short and stout"\n]');
|
|
42
|
+
|
|
43
|
+
expect(JSON.stringify(thrownError)).to.equal(expected);
|
|
44
|
+
});
|
|
45
|
+
});
|
package/lib/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import types from './types/types';
|
|
2
|
+
import validations from './validations/validations';
|
|
3
|
+
import errors from './errors/errors';
|
|
4
|
+
import formatting from './formatting/formatting';
|
|
5
|
+
import middleware from './middleware/middleware';
|
|
6
|
+
import ormHelpers from './orm-helpers/ormHelpers';
|
|
7
|
+
import fileParser from './file-parser/file-parser';
|
|
8
|
+
import structuredFileParser from './structured-file-parser/structured-file-parser';
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
types,
|
|
12
|
+
validations,
|
|
13
|
+
errors,
|
|
14
|
+
formatting,
|
|
15
|
+
fileParser,
|
|
16
|
+
structuredFileParser,
|
|
17
|
+
middleware,
|
|
18
|
+
ormHelpers,
|
|
19
|
+
};
|
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
const T = require('../types/types');
|
|
2
|
+
|
|
2
3
|
const ensureType = (type) => (req, res, next, id, name) => {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
4
|
+
let error;
|
|
5
|
+
try {
|
|
6
|
+
T.ensureType(type, id, `Invalid ${name}`);
|
|
7
|
+
return next();
|
|
8
|
+
} catch (error1) {
|
|
9
|
+
error = error1;
|
|
10
|
+
return next(error);
|
|
11
|
+
}
|
|
12
12
|
};
|
|
13
|
+
|
|
13
14
|
const ensureUUID = ensureType('UUID');
|
|
15
|
+
|
|
14
16
|
const ensureHex32 = ensureType('Hex32');
|
|
17
|
+
|
|
15
18
|
const ensureInteger = ensureType('IntString');
|
|
19
|
+
|
|
16
20
|
module.exports = {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
ensureType,
|
|
22
|
+
ensureUUID,
|
|
23
|
+
ensureHex32,
|
|
24
|
+
ensureInteger,
|
|
21
25
|
};
|
|
22
|
-
//# sourceMappingURL=middleware.js.map
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
|
+
|
|
2
3
|
const includeNested = (orm, models) => _.map(_.toPairs(models), (arg) => {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
const [modelName, inclusion] = arg;
|
|
5
|
+
const include = includeNested(orm, inclusion);
|
|
6
|
+
const model = orm[modelName];
|
|
7
|
+
if (_.isEmpty(include)) {
|
|
8
|
+
return model;
|
|
9
|
+
}
|
|
10
|
+
return {
|
|
11
|
+
model,
|
|
12
|
+
include,
|
|
13
|
+
};
|
|
13
14
|
});
|
|
15
|
+
|
|
14
16
|
module.exports = {
|
|
15
|
-
|
|
17
|
+
includeNested,
|
|
16
18
|
};
|
|
17
|
-
//# sourceMappingURL=ormHelpers.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const { includeNested } = require('./ormHelpers');
|
|
2
|
+
|
|
3
|
+
describe('ormIncludeNested', () => {
|
|
4
|
+
const orm = {
|
|
5
|
+
a: 'a',
|
|
6
|
+
b: 'b',
|
|
7
|
+
c: 'c',
|
|
8
|
+
d: 'd',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
describe('when passed an empty object', () => {
|
|
12
|
+
it('returns an empty array', () => includeNested(orm, {}).should.deep.equal([]));
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe('when passed an object with no inclusions', () => {
|
|
16
|
+
it('returns an array of just the key', () => includeNested(orm, {
|
|
17
|
+
a: {},
|
|
18
|
+
}).should.deep.equal(['a']));
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('when passed a hierarchy of inclusions', () => {
|
|
22
|
+
it('should nest them in sequelize style', () => includeNested(orm, {
|
|
23
|
+
a: {
|
|
24
|
+
b: {
|
|
25
|
+
c: {},
|
|
26
|
+
d: {},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
}).should.deep.equal([
|
|
30
|
+
{
|
|
31
|
+
model: 'a',
|
|
32
|
+
include: [
|
|
33
|
+
{
|
|
34
|
+
model: 'b',
|
|
35
|
+
include: ['c', 'd'],
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
]));
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/* eslint-disable max-classes-per-file */
|
|
2
|
+
export class InvalidFileError extends Error {
|
|
3
|
+
constructor() {
|
|
4
|
+
super('Invalid file type');
|
|
5
|
+
this.name = 'InvalidFileError';
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export class TooManyRowsError extends Error {
|
|
9
|
+
constructor() {
|
|
10
|
+
super('More than configured number of rows imported');
|
|
11
|
+
this.name = 'TooManyRowsError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export class NoRowsError extends Error {
|
|
15
|
+
constructor() {
|
|
16
|
+
super('No rows');
|
|
17
|
+
this.name = 'NoRowsError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export class InvalidHeadersError extends Error {
|
|
21
|
+
constructor() {
|
|
22
|
+
super('Invalid or incomplete headers list');
|
|
23
|
+
this.name = 'InvalidHeadersError';
|
|
24
|
+
}
|
|
25
|
+
}
|