Skip to main content
Glama
langiumValidator.js•8.83 kB
/** * Langium-based Mermaid Diagram Validator * Handles validation for newer diagram types that use Langium parsers instead of Jison */ const logger = require('../utils/logger'); class LangiumValidator { constructor() { this.parsers = {}; this.initializers = { pie: async () => { try { // For now, we'll use the pre-built parser from the parent repo const { parse } = await import('../../language/generated/parse.js'); this.parsers.pie = { parse: (text) => parse('pie', text) }; } catch (error) { // Fallback to basic validation since Langium setup is complex logger.warn('Langium parser not available, using basic validation for pie charts'); this.parsers.pie = { parse: this.basicPieValidation.bind(this) }; } }, gitGraph: async () => { try { const { parse } = await import('../../language/generated/parse.js'); this.parsers.gitGraph = { parse: (text) => parse('gitGraph', text) }; } catch (error) { logger.warn('Langium parser not available, using basic validation for git diagrams'); this.parsers.gitGraph = { parse: this.basicGitValidation.bind(this) }; } }, info: async () => { try { const { parse } = await import('../../language/generated/parse.js'); this.parsers.info = { parse: (text) => parse('info', text) }; } catch (error) { logger.warn('Langium parser not available, using basic validation for info diagrams'); this.parsers.info = { parse: this.basicInfoValidation.bind(this) }; } }, architecture: async () => { try { const { parse } = await import('../../language/generated/parse.js'); this.parsers.architecture = { parse: (text) => parse('architecture', text) }; } catch (error) { logger.warn('Langium parser not available, using basic validation for architecture diagrams'); this.parsers.architecture = { parse: this.basicArchitectureValidation.bind(this) }; } }, radar: async () => { try { const { parse } = await import('../../language/generated/parse.js'); this.parsers.radar = { parse: (text) => parse('radar', text) }; } catch (error) { logger.warn('Langium parser not available, using basic validation for radar charts'); this.parsers.radar = { parse: this.basicRadarValidation.bind(this) }; } }, packet: async () => { try { const { parse } = await import('../../language/generated/parse.js'); this.parsers.packet = { parse: (text) => parse('packet', text) }; } catch (error) { logger.warn('Langium parser not available, using basic validation for packet diagrams'); this.parsers.packet = { parse: this.basicPacketValidation.bind(this) }; } }, treemap: async () => { try { const { parse } = await import('../../language/generated/parse.js'); this.parsers.treemap = { parse: (text) => parse('treemap', text) }; } catch (error) { logger.warn('Langium parser not available, using basic validation for treemap diagrams'); this.parsers.treemap = { parse: this.basicTreemapValidation.bind(this) }; } }, zenuml: async () => { try { // ZenUML has a separate package, use basic validation for now logger.warn('ZenUML parser not available, using basic validation for ZenUML diagrams'); this.parsers.zenuml = { parse: this.basicZenUMLValidation.bind(this) }; } catch (error) { logger.warn('ZenUML parser not available, using basic validation for ZenUML diagrams'); this.parsers.zenuml = { parse: this.basicZenUMLValidation.bind(this) }; } } }; } async validateWithLangiumGrammar(content, diagramType, result) { try { const startTime = Date.now(); // Initialize parser if not already done if (!this.parsers[diagramType]) { if (this.initializers[diagramType]) { await this.initializers[diagramType](); } else { throw new Error(`No Langium parser available for diagram type: ${diagramType}`); } } const parser = this.parsers[diagramType]; if (!parser) { throw new Error(`Failed to initialize parser for: ${diagramType}`); } // Attempt to parse the content await parser.parse(content); const endTime = Date.now(); const processingTime = endTime - startTime; result.valid = true; result.errors = []; result.warnings = []; result.metadata.validationMethod = 'langium_grammar'; result.metadata.processingTime = processingTime; result.metadata.customValidator = true; logger.info(`Langium validation passed for ${diagramType}`, { diagramType, contentLength: content.length, processingTime }); } catch (error) { result.valid = false; // Parse Langium error format if (error.result && (error.result.lexerErrors || error.result.parserErrors)) { const errors = []; if (error.result.lexerErrors) { error.result.lexerErrors.forEach(lexError => { errors.push({ type: 'lexer_error', message: lexError.message, line: lexError.line || 1, column: lexError.column || 1, expected: null, found: null }); }); } if (error.result.parserErrors) { error.result.parserErrors.forEach(parseError => { errors.push({ type: 'syntax_error', message: parseError.message, line: parseError.token?.startLine || 1, column: parseError.token?.startColumn || 1, expected: null, found: parseError.token?.image || null }); }); } result.errors = errors; } else { result.errors = [{ type: 'syntax_error', message: error.message || 'Failed to parse diagram', line: 1, column: 1, expected: null, found: null }]; } result.warnings = []; result.metadata.validationMethod = 'langium_grammar'; result.metadata.customValidator = true; logger.warn(`Langium validation failed for ${diagramType}`, { diagramType, error: error.message, contentLength: content.length }); } return result; } // Basic fallback validations for when Langium parsers are not available basicPieValidation(content) { if (!content.includes('pie')) { throw new Error('Pie diagram must start with "pie"'); } if (!content.match(/"[^"]+"\s*:\s*[\d.]+/)) { throw new Error('Pie chart requires at least one data entry in format "Label" : Value'); } return { isValid: true }; } basicGitValidation(content) { if (!content.match(/gitGraph/i)) { throw new Error('Git diagram must start with "gitGraph"'); } return { isValid: true }; } basicInfoValidation(content) { if (!content.includes('info')) { throw new Error('Info diagram must start with "info"'); } return { isValid: true }; } basicArchitectureValidation(content) { if (!content.includes('architecture-beta')) { throw new Error('Architecture diagram must start with "architecture-beta"'); } return { isValid: true }; } basicRadarValidation(content) { if (!content.includes('radar')) { throw new Error('Radar chart must start with "radar"'); } return { isValid: true }; } basicPacketValidation(content) { if (!content.includes('packet-beta')) { throw new Error('Packet diagram must start with "packet-beta"'); } return { isValid: true }; } basicTreemapValidation(content) { if (!content.includes('treemap-beta')) { throw new Error('Treemap diagram must start with "treemap-beta"'); } return { isValid: true }; } basicZenUMLValidation(content) { if (!content.includes('zenuml')) { throw new Error('ZenUML diagram must start with "zenuml"'); } // Basic check for participant interaction syntax (->) if (!content.match(/\w+\s*->\s*\w+/)) { throw new Error('ZenUML diagram requires participant interactions using "->" syntax'); } return { isValid: true }; } isLangiumDiagram(diagramType) { return ['pie', 'gitGraph', 'info', 'architecture', 'radar', 'packet', 'treemap', 'zenuml'].includes(diagramType); } } module.exports = LangiumValidator;

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ai-of-mine/fast-mermaid-validator-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server