86 lines
2.3 KiB
TypeScript
86 lines
2.3 KiB
TypeScript
|
|
import type { LoaderContext } from '@rspack/core';
|
||
|
|
import { parse } from 'csv-parse/sync';
|
||
|
|
import { parseSchema, createValidator, parseValue } from '../index.js';
|
||
|
|
|
||
|
|
export interface CsvLoaderOptions {
|
||
|
|
delimiter?: string;
|
||
|
|
quote?: string;
|
||
|
|
escape?: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
interface PropertyConfig {
|
||
|
|
name: string;
|
||
|
|
schema: any;
|
||
|
|
validator: (value: unknown) => boolean;
|
||
|
|
parser: (valueString: string) => unknown;
|
||
|
|
}
|
||
|
|
|
||
|
|
export default function csvLoader(
|
||
|
|
this: LoaderContext<CsvLoaderOptions>,
|
||
|
|
content: string
|
||
|
|
): string {
|
||
|
|
const options = this.getOptions() || {};
|
||
|
|
const delimiter = options.delimiter ?? ',';
|
||
|
|
const quote = options.quote ?? '"';
|
||
|
|
const escape = options.escape ?? '\\';
|
||
|
|
|
||
|
|
const records = parse(content, {
|
||
|
|
delimiter,
|
||
|
|
quote,
|
||
|
|
escape,
|
||
|
|
relax_column_count: true,
|
||
|
|
});
|
||
|
|
|
||
|
|
if (records.length < 2) {
|
||
|
|
throw new Error('CSV must have at least 2 rows: headers and schemas');
|
||
|
|
}
|
||
|
|
|
||
|
|
const headers = records[0];
|
||
|
|
const schemas = records[1];
|
||
|
|
|
||
|
|
if (headers.length !== schemas.length) {
|
||
|
|
throw new Error(
|
||
|
|
`Header count (${headers.length}) does not match schema count (${schemas.length})`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
const propertyConfigs: PropertyConfig[] = headers.map((header: string, index: number) => {
|
||
|
|
const schemaString = schemas[index];
|
||
|
|
const schema = parseSchema(schemaString);
|
||
|
|
return {
|
||
|
|
name: header,
|
||
|
|
schema,
|
||
|
|
validator: createValidator(schema),
|
||
|
|
parser: (valueString: string) => parseValue(schema, valueString),
|
||
|
|
};
|
||
|
|
});
|
||
|
|
|
||
|
|
const dataRows = records.slice(2);
|
||
|
|
const objects = dataRows.map((row: string[], rowIndex: number) => {
|
||
|
|
const obj: Record<string, unknown> = {};
|
||
|
|
propertyConfigs.forEach((config, colIndex) => {
|
||
|
|
const rawValue = row[colIndex] ?? '';
|
||
|
|
try {
|
||
|
|
const parsed = config.parser(rawValue);
|
||
|
|
if (!config.validator(parsed)) {
|
||
|
|
throw new Error(
|
||
|
|
`Validation failed for property "${config.name}" at row ${rowIndex + 3}: ${rawValue}`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
obj[config.name] = parsed;
|
||
|
|
} catch (error) {
|
||
|
|
if (error instanceof Error) {
|
||
|
|
throw new Error(
|
||
|
|
`Failed to parse property "${config.name}" at row ${rowIndex + 3}, column ${colIndex + 1}: ${error.message}`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
throw error;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
return obj;
|
||
|
|
});
|
||
|
|
|
||
|
|
const json = JSON.stringify(objects, null, 2);
|
||
|
|
return `export default ${json};`;
|
||
|
|
}
|