import fs from 'fs'; import path from 'path'; export default class SchemaFileLoader { /** * Load a schema JSON file and resolve all nested json_dtype references * * @param {string} filePath - Absolute or relative path to schema file * @param {Set} visited - Internal circular reference protection * @returns {object} Fully resolved schema JSON */ static load(filePath, visited = new Set()) { const absolutePath = path.resolve(filePath); if (visited.has(absolutePath)) { throw new Error(`Circular schema reference detected: ${absolutePath}`); } visited.add(absolutePath); if (!fs.existsSync(absolutePath)) { throw new Error(`Schema file not found: ${absolutePath}`); } const raw = fs.readFileSync(absolutePath, 'utf-8'); const schema = JSON.parse(raw); const baseDir = path.dirname(absolutePath); return this._resolveSchema(schema, baseDir, visited); } /** * Recursively resolve json_dtype references */ static _resolveSchema(schema, baseDir, visited) { if (typeof schema !== 'object' || schema === null) return schema; for (const field in schema) { const rule = schema[field]; if (!rule || typeof rule !== 'object') continue; // Resolve nested JSON object schema if ( rule.type === 'JSONObject' || rule.type === 'JSONArray' ) { if (typeof rule.json_dtype === 'string') { const nestedPath = path.join(baseDir, rule.json_dtype); rule.json_dtype = this.load(nestedPath, visited); } else if (typeof rule.json_dtype === 'object') { rule.json_dtype = this._resolveSchema( rule.json_dtype, baseDir, visited ); } } // Resolve array of JSON objects if ( rule.type === 'Array' && rule.array_dtype === 'JSONObject' ) { if (typeof rule.json_dtype === 'string') { const nestedPath = path.join(baseDir, rule.json_dtype); rule.json_dtype = this.load(nestedPath, visited); } } } return schema; } }