Skip to content

Commit c36fbb7

Browse files
committed
refactor: streamline CSV validation logic and improve error handling in tests
1 parent f941663 commit c36fbb7

8 files changed

Lines changed: 295 additions & 219 deletions

src/index.ts

Lines changed: 16 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -402,14 +402,9 @@ export class CSV<T extends Record<string, any>> {
402402
result.valid = false;
403403
result.columnIssues[column] = [...columnValidation.issues];
404404

405-
// If row validation succeeded but column validation failed, update the column value in validated row
406-
if (result.validatedRow && !result.rowIssues) {
407-
if ('value' in columnValidation) {
408-
(result.validatedRow as any)[column] = columnValidation.value;
409-
} else {
410-
// Column validation failed, set to null/undefined or keep original
411-
(result.validatedRow as any)[column] = null;
412-
}
405+
// If column validation failed but provided a corrected value, use it
406+
if (result.validatedRow && 'value' in columnValidation) {
407+
(result.validatedRow as any)[column] = columnValidation.value;
413408
}
414409
} else if (result.validatedRow) {
415410
// Update the validated value in the result
@@ -418,7 +413,7 @@ export class CSV<T extends Record<string, any>> {
418413
}
419414

420415
// If no column issues, remove the empty object
421-
if (Object.keys(result.columnIssues).length === 0) {
416+
if (result.columnIssues && Object.keys(result.columnIssues).length === 0) {
422417
delete result.columnIssues;
423418
}
424419
}
@@ -646,11 +641,10 @@ export class CSV<T extends Record<string, any>> {
646641
throw new CSVError(`Expected object rows for validation, but first parsed record (approx. file line ${firstDataRowActualLine}) is not an object.`);
647642
}
648643
const sampleKeys = Object.keys(parsedData[0]);
649-
parsedData.forEach((row, i) => {
650-
if (!(row && typeof row === 'object')) { throw new CSVError(`Row at approx. file line ${firstDataRowActualLine + i} (parsed index ${i}) is not an object as expected.`); }
651-
const relaxCount = finalMainParserOptions.relax_column_count ?? finalMainParserOptions.relaxColumnCount;
652-
if (Object.keys(row).length !== sampleKeys.length && !relaxCount) { throw new CSVError(`Row at approx. file line ${firstDataRowActualLine + i} (parsed index ${i}) has inconsistent column count. Expected ${sampleKeys.length}, got ${Object.keys(row).length}.`); }
653-
});
644+
parsedData.forEach((row, i) => {
645+
if (!(row && typeof row === 'object')) { throw new CSVError(`Row at approx. file line ${firstDataRowActualLine + i} (parsed index ${i}) is not an object as expected.`); }
646+
if (Object.keys(row).length !== sampleKeys.length) { throw new CSVError(`Row at approx. file line ${firstDataRowActualLine + i} (parsed index ${i}) has inconsistent column count. Expected ${sampleKeys.length}, got ${Object.keys(row).length}.`); }
647+
});
654648
}
655649
}
656650

@@ -796,7 +790,7 @@ export class CSV<T extends Record<string, any>> {
796790
// Default built-in casters
797791
const defaultDefinitions: CustomCastDefinition = {
798792
string: { test: () => true, parse: (v) => String(v) },
799-
number: { test: (v) => !isNaN(Number(v)), parse: (v) => Number(v) },
793+
number: { test: () => true, parse: (v) => Number(v) },
800794
boolean: { test: (v) => ['true', 'false', '1', '0', 'yes', 'no'].includes(v.toLowerCase()), parse: (v) => ['true', '1', 'yes'].includes(v.toLowerCase()) },
801795
date: { test: (v) => !isNaN(Date.parse(v)), parse: (v) => new Date(v) },
802796
object: { test: (v) => { try { JSON.parse(v); return true; } catch { return false; } }, parse: (v) => JSON.parse(v) },
@@ -1500,42 +1494,26 @@ export class CSV<T extends Record<string, any>> {
15001494

15011495
// Validate columns in parallel
15021496
const columnEntries = Object.entries(columnSchemas);
1503-
const columnValidationPromises = columnEntries.map(async ([column, schema]) => {
1504-
if (!schema) return null;
1497+
await Promise.all(columnEntries.map(async ([column, schema]) => {
1498+
if (!schema) return;
15051499

15061500
const columnValidation = await tryValidateStandardSchemaAsync(schema, row[column]);
1507-
return { column, columnValidation };
1508-
});
1509-
1510-
const columnResults = await Promise.all(columnValidationPromises);
1511-
1512-
// Process column validation results
1513-
for (const columnResult of columnResults) {
1514-
if (!columnResult) continue;
1515-
1516-
const { column, columnValidation } = columnResult;
1517-
15181501
if (columnValidation.issues) {
15191502
result.valid = false;
15201503
result.columnIssues[column] = [...columnValidation.issues];
15211504

1522-
// If row validation succeeded but column validation failed, update the column value in validated row
1523-
if (result.validatedRow && !result.rowIssues) {
1524-
if ('value' in columnValidation) {
1525-
(result.validatedRow as any)[column] = columnValidation.value;
1526-
} else {
1527-
// Column validation failed, set to null/undefined or keep original
1528-
(result.validatedRow as any)[column] = null;
1529-
}
1505+
// If column validation failed but provided a corrected value, use it
1506+
if (result.validatedRow && 'value' in columnValidation) {
1507+
(result.validatedRow as any)[column] = columnValidation.value;
15301508
}
15311509
} else if (result.validatedRow) {
15321510
// Update the validated value in the result
15331511
(result.validatedRow as any)[column] = columnValidation.value;
15341512
}
1535-
}
1513+
}));
15361514

15371515
// If no column issues, remove the empty object
1538-
if (Object.keys(result.columnIssues).length === 0) {
1516+
if (result.columnIssues && Object.keys(result.columnIssues).length === 0) {
15391517
delete result.columnIssues;
15401518
}
15411519
}

test/array-mapping.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,39 @@
11
import { describe, it, expect, vi, beforeEach } from 'vitest';
2+
import { vi } from 'vitest';
23
import fs from 'fs';
34
import { HeaderMap, createHeaderMapFns, CsvToArrayConfig, ObjectArrayToCsvConfig } from '../src/headers';
45
import CSV, { CSVError } from '../src';
56

7+
// Mock fs for unit tests
8+
vi.mock('fs', () => ({
9+
default: {
10+
readFileSync: vi.fn(),
11+
writeFileSync: vi.fn(),
12+
createReadStream: vi.fn(),
13+
createWriteStream: vi.fn(),
14+
existsSync: vi.fn(),
15+
mkdtempSync: vi.fn(),
16+
unlinkSync: vi.fn(),
17+
statSync: vi.fn(),
18+
promises: {
19+
readFile: vi.fn(),
20+
writeFile: vi.fn(),
21+
}
22+
},
23+
readFileSync: vi.fn(),
24+
writeFileSync: vi.fn(),
25+
createReadStream: vi.fn(),
26+
createWriteStream: vi.fn(),
27+
existsSync: vi.fn(),
28+
mkdtempSync: vi.fn(),
29+
unlinkSync: vi.fn(),
30+
statSync: vi.fn(),
31+
promises: {
32+
readFile: vi.fn(),
33+
writeFile: vi.fn(),
34+
}
35+
}));
36+
637

738

839
describe('Array Mapping Feature', () => {

test/custom-casting.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,49 @@
11
import { describe, it, expect, vi, beforeEach } from 'vitest';
2+
import { vi } from 'vitest';
23
import fs from 'fs';
34
import path from 'path';
45
import CSV, { CSVError } from '../src';
56

7+
// Mock fs for unit tests
8+
vi.mock('fs', () => ({
9+
default: {
10+
readFileSync: vi.fn(),
11+
writeFileSync: vi.fn(),
12+
createReadStream: vi.fn(),
13+
createWriteStream: vi.fn(),
14+
existsSync: vi.fn(),
15+
mkdtempSync: vi.fn(),
16+
unlinkSync: vi.fn(),
17+
statSync: vi.fn(),
18+
promises: {
19+
readFile: vi.fn(),
20+
writeFile: vi.fn(),
21+
}
22+
},
23+
readFileSync: vi.fn(),
24+
writeFileSync: vi.fn(),
25+
createReadStream: vi.fn(),
26+
createWriteStream: vi.fn(),
27+
existsSync: vi.fn(),
28+
mkdtempSync: vi.fn(),
29+
unlinkSync: vi.fn(),
30+
statSync: vi.fn(),
31+
promises: {
32+
readFile: vi.fn(),
33+
writeFile: vi.fn(),
34+
}
35+
}));
36+
37+
// Mock path for unit tests
38+
vi.mock('path', () => ({
39+
default: {
40+
resolve: vi.fn((p) => p),
41+
join: vi.fn((dir, file) => `${dir}/${file}`),
42+
},
43+
resolve: vi.fn((p) => p),
44+
join: vi.fn((dir, file) => `${dir}/${file}`),
45+
}));
46+
647

748

849
describe('Custom Casting Feature', () => {

0 commit comments

Comments
 (0)