Skip to main content
Glama
validate-lesson-data-v1.1.0.js22.8 kB
#!/usr/bin/env node /** * Lesson Data Validator Tool v1.1.0 - FAIL-FAST RELIABILITY * Comprehensive validation with strict error reporting and no auto-fix fallbacks * @version 1.1.0 (January 20, 2025) * @status ENHANCED - Fail-fast validation with comprehensive error reporting * @reference JIT workflow step 4 of 7 * @enhancement Strict validation, no auto-fix fallbacks, detailed troubleshooting */ export class LessonDataValidatorV110 { constructor() { this.validationErrors = []; this.processingStartTime = null; this.requiredMetadataFields = ['topic', 'gradeLevel', 'subject']; this.requiredWidgetFields = ['id', 'type', 'content']; this.supportedWidgetTypes = this.getSupportedWidgetTypes(); } /** * Main validation entry point with FAIL-FAST approach * @param {Object} input - Contains lessonData with metadata and widgets array * @returns {Object} Validated lesson data OR detailed error information */ async validateLessonData(input) { this.processingStartTime = Date.now(); this.validationErrors = []; try { console.error('[VALIDATE_LESSON_DATA_V110] Starting fail-fast validation'); // FAIL-FAST VALIDATION PHASE const validationResult = this.validateLessonDataComprehensively(input); if (!validationResult.valid) { throw new Error(`Lesson Data validation failed:\n\n${validationResult.errors.join('\n\n')}\n\nReceived input structure: ${JSON.stringify(this.getInputSummary(input), null, 2)}`); } const { lessonData } = validationResult; console.error(`[VALIDATE_LESSON_DATA_V110] Validation passed - Processing lesson: "${lessonData.metadata.topic}"`); console.error(`[VALIDATE_LESSON_DATA_V110] Widgets to validate: ${lessonData.widgets.length}`); // STRUCTURE VALIDATION PHASE this.validateStructureStrict(lessonData); // METADATA VALIDATION PHASE this.validateMetadataStrict(lessonData.metadata); // WIDGETS VALIDATION PHASE this.validateWidgetsArrayStrict(lessonData.widgets); // EDUCATIONAL FLOW VALIDATION PHASE this.validateEducationalFlowStrict(lessonData.widgets); // CONTENT QUALITY VALIDATION PHASE this.validateContentQualityStrict(lessonData); const processingTime = Date.now() - this.processingStartTime; console.error(`[VALIDATE_LESSON_DATA_V110] Success - All validations passed in ${processingTime}ms`); return { success: true, data: { validatedLessonData: lessonData, validationSummary: { totalChecks: this.getValidationCheckCount(), structureValid: true, metadataValid: true, widgetsValid: true, educationalFlowValid: true, contentQualityValid: true }, qualityMetrics: this.calculateQualityMetrics(lessonData), nextStepGuidance: { readyForFormatting: true, formatTool: "format_for_composer", note: "Lesson data is fully validated and ready for Composer JSON formatting" } }, metadata: { version: "1.1.0", timestamp: new Date().toISOString(), processingTime: processingTime, failFastValidation: "passed", widgetsValidated: lessonData.widgets.length } }; } catch (error) { console.error('[VALIDATE_LESSON_DATA_V110] FAIL-FAST ERROR:', error.message); return { success: false, error: { code: 'LESSON_DATA_VALIDATION_FAILED', message: error.message, timestamp: new Date().toISOString(), processingTime: Date.now() - this.processingStartTime, failFast: true, developmentMode: true, troubleshooting: { requiredInputStructure: { lessonData: { metadata: { topic: "string - lesson title/topic", gradeLevel: "string - target grade level", subject: "string - subject area", duration: "string (optional) - estimated duration" }, widgets: [ { id: "string - unique widget identifier", type: "string - widget type (e.g., 'head-1', 'text-1')", content: "object - widget-specific content structure" } ] } }, validationPhases: [ "1. Input structure validation", "2. Metadata completeness and format", "3. Widgets array structure and content", "4. Educational flow and logic", "5. Content quality and substance" ], commonIssues: [ "Missing required metadata fields (topic, gradeLevel, subject)", "Empty or malformed widgets array", "Invalid widget types or missing content", "Poor educational flow or structure", "Insufficient content quality or depth" ], debugSteps: [ "1. Check if lessonData exists and is an object", "2. Verify metadata has required fields", "3. Ensure widgets is an array with valid objects", "4. Check widget types are supported", "5. Validate content structure for each widget" ] } } }; } } /** * COMPREHENSIVE FAIL-FAST VALIDATION for lesson data * No fallbacks - strict validation with detailed error reporting */ validateLessonDataComprehensively(input) { const errors = []; // Basic input structure validation if (!input || typeof input !== 'object') { errors.push("❌ INPUT STRUCTURE ERROR:\nInput must be an object containing lessonData.\nReceived: " + typeof input + "\nRequired format: { lessonData: { metadata: {...}, widgets: [...] } }"); return { valid: false, errors }; } // lessonData existence validation if (!input.lessonData) { errors.push("❌ MISSING LESSONDATA ERROR:\nlessonData parameter is required.\nThis should come from your lesson creation with metadata and widgets.\nRequired format: { lessonData: { metadata: {...}, widgets: [...] } }"); return { valid: false, errors }; } if (typeof input.lessonData !== 'object' || input.lessonData === null) { errors.push(`❌ LESSONDATA TYPE ERROR:\nlessonData must be an object, received: ${typeof input.lessonData}\nValue: ${JSON.stringify(input.lessonData, null, 2)}`); return { valid: false, errors }; } const lessonData = input.lessonData; // Basic structure validation if (!lessonData.metadata && !lessonData.widgets) { errors.push("❌ EMPTY LESSONDATA ERROR:\nlessonData appears to be empty or missing required structure.\nRequired: metadata object and widgets array\nReceived: " + JSON.stringify(Object.keys(lessonData))); return { valid: false, errors }; } console.error(`[VALIDATE_LESSON_DATA_V110] Basic validation passed`); return { valid: true, lessonData, errors: [] }; } /** * STRICT STRUCTURE VALIDATION with no fallbacks */ validateStructureStrict(lessonData) { const errors = []; // Metadata structure if (!lessonData.metadata) { errors.push("❌ MISSING METADATA ERROR:\nmetadata object is required.\nMetadata should contain lesson information like topic, gradeLevel, subject."); throw new Error(errors.join('\n')); } if (typeof lessonData.metadata !== 'object' || lessonData.metadata === null) { errors.push(`❌ METADATA TYPE ERROR:\nmetadata must be an object, received: ${typeof lessonData.metadata}\nValue: ${JSON.stringify(lessonData.metadata)}`); throw new Error(errors.join('\n')); } // Widgets structure if (!lessonData.widgets) { errors.push("❌ MISSING WIDGETS ERROR:\nwidgets array is required.\nWidgets should contain the lesson content structure."); throw new Error(errors.join('\n')); } if (!Array.isArray(lessonData.widgets)) { errors.push(`❌ WIDGETS TYPE ERROR:\nwidgets must be an array, received: ${typeof lessonData.widgets}\nValue: ${JSON.stringify(lessonData.widgets)}`); throw new Error(errors.join('\n')); } if (lessonData.widgets.length === 0) { errors.push("❌ EMPTY WIDGETS ERROR:\nwidgets array cannot be empty.\nAt least one widget is required for a valid lesson.\nExpected: Array of widget objects with id, type, and content."); throw new Error(errors.join('\n')); } console.error(`[VALIDATE_LESSON_DATA_V110] Structure validation passed`); } /** * STRICT METADATA VALIDATION with comprehensive checks */ validateMetadataStrict(metadata) { const errors = []; const missing = []; const invalid = []; // Check required fields this.requiredMetadataFields.forEach(field => { if (!metadata[field]) { missing.push(field); } else if (typeof metadata[field] !== 'string' || metadata[field].trim().length === 0) { invalid.push(`${field}: must be a non-empty string`); } }); if (missing.length > 0) { errors.push(`❌ MISSING METADATA FIELDS ERROR:\nRequired fields missing: ${missing.join(', ')}\n\nRequired metadata fields:\n${this.requiredMetadataFields.map(f => ` • ${f}: string`).join('\n')}\n\nReceived metadata: ${JSON.stringify(metadata, null, 2)}`); } if (invalid.length > 0) { errors.push(`❌ INVALID METADATA VALUES ERROR:\nInvalid field values:\n${invalid.map(i => ` • ${i}`).join('\n')}`); } // Validate specific field formats if (metadata.gradeLevel && !this.isValidGradeLevel(metadata.gradeLevel)) { errors.push(`❌ INVALID GRADE LEVEL ERROR:\nGrade level "${metadata.gradeLevel}" is invalid.\nValid formats: 6º ano, 7º ano, 8º ano, 9º ano, 1º médio, 2º médio, 3º médio, Superior`); } if (metadata.subject && !this.isValidSubject(metadata.subject)) { errors.push(`❌ INVALID SUBJECT ERROR:\nSubject "${metadata.subject}" is invalid.\nValid subjects: Ciências, Matemática, História, Português, Geografia, Arte, Educação Física, Inglês`); } if (errors.length > 0) { throw new Error(errors.join('\n\n')); } console.error(`[VALIDATE_LESSON_DATA_V110] Metadata validation passed`); } /** * STRICT WIDGETS ARRAY VALIDATION with comprehensive checks */ validateWidgetsArrayStrict(widgets) { const errors = []; const widgetIds = new Set(); if (widgets.length > 20) { errors.push(`❌ TOO MANY WIDGETS ERROR:\nToo many widgets: ${widgets.length}\nMaximum recommended: 20 widgets\nConsider condensing content for better user experience.`); } widgets.forEach((widget, index) => { const widgetErrors = this.validateSingleWidgetStrict(widget, index); if (widgetErrors.length > 0) { errors.push(`❌ WIDGET ${index + 1} VALIDATION ERROR:\n${widgetErrors.join('\n')}\nWidget: ${JSON.stringify(widget, null, 2)}`); } // Check for duplicate IDs if (widget.id) { if (widgetIds.has(widget.id)) { errors.push(`❌ DUPLICATE WIDGET ID ERROR:\nWidget ID "${widget.id}" is used multiple times.\nAll widget IDs must be unique.\nWidget index: ${index + 1}`); } else { widgetIds.add(widget.id); } } }); if (errors.length > 0) { throw new Error(errors.join('\n\n')); } console.error(`[VALIDATE_LESSON_DATA_V110] Widgets validation passed - ${widgets.length} widgets`); } /** * STRICT SINGLE WIDGET VALIDATION */ validateSingleWidgetStrict(widget, index) { const errors = []; // Basic structure if (!widget || typeof widget !== 'object') { errors.push(`Widget must be an object, received: ${typeof widget}`); return errors; } // Required fields this.requiredWidgetFields.forEach(field => { if (!widget[field]) { errors.push(`Missing required field: ${field}`); } }); // ID validation if (widget.id) { if (typeof widget.id !== 'string' || widget.id.trim().length === 0) { errors.push(`ID must be a non-empty string`); } } // Type validation if (widget.type) { if (typeof widget.type !== 'string') { errors.push(`Type must be a string, received: ${typeof widget.type}`); } else if (!this.supportedWidgetTypes.includes(widget.type)) { errors.push(`Unsupported widget type: ${widget.type}\nSupported types: ${this.supportedWidgetTypes.join(', ')}`); } } // Content validation if (widget.content) { if (typeof widget.content !== 'object' || widget.content === null) { errors.push(`Content must be an object, received: ${typeof widget.content}`); } else { const contentErrors = this.validateWidgetContentStrict(widget.type, widget.content); if (contentErrors.length > 0) { errors.push(`Content validation failed:\n ${contentErrors.join('\n ')}`); } } } return errors; } /** * STRICT WIDGET CONTENT VALIDATION by type */ validateWidgetContentStrict(widgetType, content) { const errors = []; const baseType = widgetType ? widgetType.split('-')[0] : 'unknown'; switch (baseType) { case 'head': if (!content.title || typeof content.title !== 'string' || content.title.trim().length === 0) { errors.push("Head widget requires non-empty title"); } break; case 'text': if (!content.text || typeof content.text !== 'string' || content.text.trim().length < 10) { errors.push("Text widget requires substantial text content (minimum 10 characters)"); } break; case 'quiz': if (!content.questions || !Array.isArray(content.questions) || content.questions.length === 0) { errors.push("Quiz widget requires questions array with at least one question"); } else { content.questions.forEach((q, i) => { if (!q.question || typeof q.question !== 'string') { errors.push(`Question ${i + 1}: question text required`); } if (!q.options || !Array.isArray(q.options) || q.options.length < 2) { errors.push(`Question ${i + 1}: at least 2 options required`); } if (typeof q.correct !== 'number' || q.correct < 0 || q.correct >= (q.options?.length || 0)) { errors.push(`Question ${i + 1}: valid correct answer index required`); } }); } break; case 'flashcards': if (!content.cards || !Array.isArray(content.cards) || content.cards.length === 0) { errors.push("Flashcards widget requires cards array with at least one card"); } else { content.cards.forEach((card, i) => { if (!card.front || typeof card.front !== 'string') { errors.push(`Card ${i + 1}: front content required`); } if (!card.back || typeof card.back !== 'string') { errors.push(`Card ${i + 1}: back content required`); } }); } break; case 'image': if (!content.src || typeof content.src !== 'string') { errors.push("Image widget requires src (URL or description)"); } if (!content.alt || typeof content.alt !== 'string') { errors.push("Image widget requires alt text for accessibility"); } break; case 'list': if (!content.items || !Array.isArray(content.items) || content.items.length === 0) { errors.push("List widget requires items array with at least one item"); } break; default: if (Object.keys(content).length === 0) { errors.push("Widget content cannot be empty"); } } return errors; } /** * STRICT EDUCATIONAL FLOW VALIDATION */ validateEducationalFlowStrict(widgets) { const errors = []; const widgetTypes = widgets.map(w => w.type); // Check for header if (!widgetTypes.some(type => type && type.startsWith('head-'))) { errors.push("❌ MISSING HEADER ERROR:\nLesson should start with a header widget (head-1 or head-2).\nHeaders provide professional lesson structure and clear objectives."); } // Check for substantial content const contentWidgets = widgetTypes.filter(type => type && (type.startsWith('text-') || type.startsWith('list-'))); if (contentWidgets.length === 0) { errors.push("❌ MISSING CONTENT ERROR:\nLesson needs substantial content widgets (text or list).\nContent widgets provide the main educational material."); } // Check for engagement elements const interactiveWidgets = widgetTypes.filter(type => type && (type.startsWith('quiz-') || type.startsWith('flashcards-') || type.startsWith('image-')) ); if (interactiveWidgets.length === 0) { errors.push("❌ MISSING INTERACTIVE ELEMENTS ERROR:\nLesson should include interactive elements for engagement.\nConsider adding quiz, flashcards, or image widgets."); } // Check widget order logic const headerIndex = widgetTypes.findIndex(type => type && type.startsWith('head-')); if (headerIndex !== 0 && headerIndex !== -1) { errors.push("❌ HEADER POSITION ERROR:\nHeader widget should be the first widget in the lesson.\nHeaders provide context and should appear before content."); } if (errors.length > 0) { throw new Error(errors.join('\n\n')); } console.error(`[VALIDATE_LESSON_DATA_V110] Educational flow validation passed`); } /** * STRICT CONTENT QUALITY VALIDATION */ validateContentQualityStrict(lessonData) { const errors = []; // Calculate content metrics const metrics = this.calculateQualityMetrics(lessonData); if (metrics.totalTextLength < 100) { errors.push("❌ INSUFFICIENT CONTENT ERROR:\nLesson content is too brief for meaningful learning.\nMinimum recommended: 100 characters of educational text.\nCurrent: " + metrics.totalTextLength + " characters"); } if (metrics.educationalDepth < 0.3) { errors.push("❌ LOW EDUCATIONAL DEPTH ERROR:\nContent lacks educational substance.\nEnsure widgets contain detailed explanations, examples, and substantial educational material."); } if (errors.length > 0) { throw new Error(errors.join('\n\n')); } console.error(`[VALIDATE_LESSON_DATA_V110] Content quality validation passed`); } /** * Calculate quality metrics */ calculateQualityMetrics(lessonData) { let totalTextLength = 0; let educationalContent = 0; let interactiveElements = 0; lessonData.widgets.forEach(widget => { if (widget.content) { // Count text content if (widget.content.text) { totalTextLength += widget.content.text.length; educationalContent++; } if (widget.content.title) { totalTextLength += widget.content.title.length; } // Count interactive elements if (widget.content.questions || widget.content.cards) { interactiveElements++; } } }); return { totalTextLength, educationalContent, interactiveElements, widgetCount: lessonData.widgets.length, educationalDepth: educationalContent / Math.max(lessonData.widgets.length, 1) }; } /** * Get input summary for error reporting */ getInputSummary(input) { if (!input) return "null"; if (typeof input !== 'object') return typeof input; const summary = { hasLessonData: !!input.lessonData, lessonDataType: typeof input.lessonData }; if (input.lessonData && typeof input.lessonData === 'object') { summary.hasMetadata = !!input.lessonData.metadata; summary.hasWidgets = !!input.lessonData.widgets; summary.widgetsCount = Array.isArray(input.lessonData.widgets) ? input.lessonData.widgets.length : 'not array'; } return summary; } /** * Get validation check count */ getValidationCheckCount() { return 5; // Structure, Metadata, Widgets, Flow, Quality } /** * Validate grade level format */ isValidGradeLevel(gradeLevel) { const validGradeLevels = [ '6º ano', '7º ano', '8º ano', '9º ano', '1º médio', '2º médio', '3º médio', 'Superior' ]; return validGradeLevels.includes(gradeLevel); } /** * Validate subject */ isValidSubject(subject) { const validSubjects = [ 'Ciências', 'Matemática', 'História', 'Português', 'Geografia', 'Arte', 'Educação Física', 'Inglês' ]; return validSubjects.includes(subject); } /** * Get supported widget types */ getSupportedWidgetTypes() { return [ 'head-1', 'head-2', 'text-1', 'text-2', 'quiz-1', 'quiz-2', 'flashcards-1', 'flashcards-2', 'image-1', 'image-2', 'video-1', 'video-2', 'list-1', 'list-2', 'hotspots-1', 'hotspots-2' ]; } } /** * Create and export the tool instance for JIT server integration */ export function createLessonDataValidatorV110() { const validator = new LessonDataValidatorV110(); return { name: 'validate_lesson_data_v110', description: 'STEP 4 v1.1.0: Validate lesson data with fail-fast comprehensive checks. Ensures data quality and structure with detailed error reporting for development.', inputSchema: { type: 'object', properties: { lessonData: { type: 'object', description: 'Complete lesson data with metadata and widgets array', properties: { metadata: { type: 'object', properties: { topic: { type: 'string' }, gradeLevel: { type: 'string' }, subject: { type: 'string' } }, required: ['topic', 'gradeLevel', 'subject'] }, widgets: { type: 'array', items: { type: 'object', properties: { id: { type: 'string' }, type: { type: 'string' }, content: { type: 'object' } }, required: ['id', 'type', 'content'] } } }, required: ['metadata', 'widgets'] } }, required: ['lessonData'] }, handler: async (input) => { return await validator.validateLessonData(input); } }; }

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/rkm097git/euconquisto-composer-mcp-poc'

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