UNPKG

@ichigo_san/graphing

Version:

A lightweight UML-style diagram editor built with React Flow and Tailwind CSS

483 lines (466 loc) 12.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ValidationService = void 0; var _ajv = _interopRequireDefault(require("ajv")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } class ValidationService { constructor() { this.ajv = new _ajv.default({ allErrors: true, verbose: true }); this.schemas = new Map(); this.setupDefaultSchemas(); } /** * Setup default validation schemas */ setupDefaultSchemas() { // Main diagram schema const diagramSchema = { type: 'object', properties: { containers: { type: 'array', items: { type: 'object', required: ['id', 'label', 'position', 'size'], properties: { id: { type: 'string' }, label: { type: 'string' }, position: { type: 'object', required: ['x', 'y'], properties: { x: { type: 'number' }, y: { type: 'number' } } }, size: { type: 'object', required: ['width', 'height'], properties: { width: { type: 'number' }, height: { type: 'number' } } }, color: { type: 'string' }, bgColor: { type: 'string' }, borderColor: { type: 'string' }, icon: { type: 'string' }, description: { type: 'string' }, zIndex: { type: 'number' } } } }, nodes: { type: 'array', items: { type: 'object', required: ['id', 'label', 'position'], properties: { id: { type: 'string' }, label: { type: 'string' }, type: { type: 'string' }, position: { type: 'object', required: ['x', 'y'], properties: { x: { type: 'number' }, y: { type: 'number' } } }, parentContainer: { type: 'string' }, size: { type: 'object', properties: { width: { type: 'number' }, height: { type: 'number' } } }, color: { type: 'string' }, borderColor: { type: 'string' }, icon: { type: 'string' }, description: { type: 'string' }, zIndex: { type: 'number' } } } }, connections: { type: 'array', items: { type: 'object', required: ['id', 'source', 'target'], properties: { id: { type: 'string' }, source: { type: 'string' }, target: { type: 'string' }, label: { type: 'string' }, type: { type: 'string' }, animated: { type: 'boolean' }, description: { type: 'string' }, waypoints: { type: 'array', items: { type: 'object', properties: { x: { type: 'number' }, y: { type: 'number' } } } }, markerStart: { type: 'string' }, markerEnd: { type: 'string' }, intersection: { type: 'string' }, style: { type: 'object', properties: { strokeWidth: { type: 'number' }, strokeDasharray: { type: 'string' }, stroke: { type: 'string' } } }, zIndex: { type: 'number' } } } }, metadata: { type: 'object', properties: { name: { type: 'string' }, description: { type: 'string' }, version: { type: 'string' }, exportDate: { type: 'string' } } } }, required: ['containers', 'nodes', 'connections'] }; this.registerSchema('diagram', diagramSchema); // Node validation schema const nodeSchema = { type: 'object', required: ['id', 'label', 'position'], properties: { id: { type: 'string' }, label: { type: 'string' }, type: { type: 'string' }, position: { type: 'object', required: ['x', 'y'], properties: { x: { type: 'number' }, y: { type: 'number' } } } } }; this.registerSchema('node', nodeSchema); // Connection validation schema const connectionSchema = { type: 'object', required: ['id', 'source', 'target'], properties: { id: { type: 'string' }, source: { type: 'string' }, target: { type: 'string' }, label: { type: 'string' }, type: { type: 'string' } } }; this.registerSchema('connection', connectionSchema); } /** * Register a new validation schema * @param {string} name - Schema name * @param {Object} schema - JSON Schema object */ registerSchema(name, schema) { const validate = this.ajv.compile(schema); this.schemas.set(name, { schema, validate }); } /** * Validate data against a registered schema * @param {string} schemaName - Name of the schema to use * @param {Object} data - Data to validate * @returns {Object} Validation result */ validate(schemaName, data) { const schemaInfo = this.schemas.get(schemaName); if (!schemaInfo) { throw new Error(`Schema '${schemaName}' not found`); } const isValid = schemaInfo.validate(data); return { isValid, errors: schemaInfo.validate.errors || [], errorText: this.ajv.errorsText(schemaInfo.validate.errors) }; } /** * Validate diagram data (default validation) * @param {Object} data - Diagram data to validate * @returns {Object} Validated data or throws error */ async validateDiagram(data) { // Use simple validation instead of strict schema validation if (!this.isValidDiagramStructure(data)) { throw new Error('Diagram validation failed: Invalid diagram structure. Required fields: containers, nodes, connections'); } // Additional validation for required fields in containers, nodes, and connections const validationErrors = []; // Validate containers if (data.containers && Array.isArray(data.containers)) { data.containers.forEach((container, index) => { if (!container.id || !container.label || !container.position) { validationErrors.push(`Container ${index}: Missing required fields (id, label, position)`); } }); } // Validate nodes if (data.nodes && Array.isArray(data.nodes)) { data.nodes.forEach((node, index) => { if (!node.id || !node.label || !node.position) { validationErrors.push(`Node ${index}: Missing required fields (id, label, position)`); } }); } // Validate connections if (data.connections && Array.isArray(data.connections)) { data.connections.forEach((connection, index) => { if (!connection.id || !connection.source || !connection.target) { validationErrors.push(`Connection ${index}: Missing required fields (id, source, target)`); } }); } if (validationErrors.length > 0) { throw new Error(`Diagram validation failed: ${validationErrors.join('; ')}`); } return data; } /** * Validate a single node * @param {Object} node - Node data to validate * @returns {Object} Validation result */ validateNode(node) { return this.validate('node', node); } /** * Validate a single connection * @param {Object} connection - Connection data to validate * @returns {Object} Validation result */ validateConnection(connection) { return this.validate('connection', connection); } /** * Validate JSON string * @param {string} jsonString - JSON string to validate * @returns {Object} Parsed and validated data */ validateJsonString(jsonString) { try { const data = JSON.parse(jsonString); return this.validate('diagram', data); } catch (parseError) { return { isValid: false, errors: [{ message: `JSON parse error: ${parseError.message}` }], errorText: `Invalid JSON: ${parseError.message}` }; } } /** * Get validation errors in a human-readable format * @param {Array} errors - Validation errors array * @returns {string} Formatted error message */ formatValidationErrors(errors) { if (!errors || errors.length === 0) { return 'No validation errors'; } return errors.map(error => { const path = error.instancePath || 'root'; return `${path}: ${error.message}`; }).join('\n'); } /** * Check if data structure is valid for diagram creation * @param {Object} data - Data to check * @returns {boolean} True if valid structure */ isValidDiagramStructure(data) { if (!data || typeof data !== 'object') { return false; } const requiredArrays = ['containers', 'nodes', 'connections']; return requiredArrays.every(key => Array.isArray(data[key])); } /** * Validate technical details format * @param {Object} technicalDetails - Technical details object * @returns {Object} Validation result */ validateTechnicalDetails(technicalDetails) { const schema = { type: 'object', properties: { protocol: { type: 'string' }, performance: { type: 'object', properties: { latency: { type: 'string' }, throughput: { type: 'string' }, timeout: { type: 'string' } } }, security: { type: 'string' }, scaling: { type: 'string' }, infrastructure: { type: 'string' }, monitoring: { type: 'string' } } }; const validate = this.ajv.compile(schema); const isValid = validate(technicalDetails); return { isValid, errors: validate.errors || [], errorText: this.ajv.errorsText(validate.errors) }; } } exports.ValidationService = ValidationService;