UNPKG

fortify-schema

Version:

A modern TypeScript validation library designed around familiar interface syntax and powerful conditional validation. Experience schema validation that feels natural to TypeScript developers while unlocking advanced runtime validation capabilities.

177 lines (159 loc) 6.61 kB
import { InteractiveDocumentation, InteractiveOptions } from "../../../../types/extension.type"; import { SchemaInterface } from "../../../../types/SchemaValidator.type"; /** * Interactive documentation generator */ export class InteractiveDocumentationGenerator { constructor( private schema: SchemaInterface, private options: InteractiveOptions ) {} generate(): InteractiveDocumentation { return { html: this.generateInteractiveHTML(), css: this.generateCSS(), javascript: this.generateJavaScript() }; } private generateInteractiveHTML(): string { return ` <div id="schema-playground"> <h1>${this.options.title || 'Schema Playground'}</h1> <div class="playground-container"> <div class="schema-panel"> <h2>Schema</h2> <pre id="schema-display"></pre> </div> <div class="input-panel"> <h2>Test Data</h2> <textarea id="test-input" placeholder="Enter JSON data to validate..."></textarea> <button id="validate-btn">Validate</button> </div> <div class="result-panel"> <h2>Result</h2> <div id="validation-result"></div> </div> </div> </div>`; } private generateCSS(): string { return ` .playground-container { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 1rem; } .schema-panel, .input-panel, .result-panel { border: 1px solid #e1e5e9; border-radius: 6px; padding: 1rem; } #test-input { width: 100%; height: 200px; font-family: monospace; } #validate-btn { background: #0366d6; color: white; border: none; padding: 0.5rem 1rem; border-radius: 4px; cursor: pointer; } .valid { color: #28a745; } .invalid { color: #dc3545; }`; } private generateJavaScript(): string { const schemaStr = JSON.stringify(this.schema, null, 2); return ` document.getElementById('schema-display').textContent = ${schemaStr}; // Real validation function function validateData(schema, data) { const errors = []; for (const [fieldName, fieldType] of Object.entries(schema)) { const value = data[fieldName]; const fieldErrors = validateField(fieldType, value, fieldName); if (fieldErrors.length > 0) { errors.push(...fieldErrors); } } return errors; } function validateField(fieldType, value, fieldName) { const errors = []; if (typeof fieldType === 'string') { const isOptional = fieldType.includes('?'); const cleanType = fieldType.replace('?', ''); // Handle null/undefined for optional fields if (isOptional && (value === null || value === undefined)) { return errors; } // Required field cannot be null/undefined if (!isOptional && (value === null || value === undefined)) { errors.push(fieldName + ': Field is required'); return errors; } // Type-specific validation if (cleanType === 'email') { if (typeof value !== 'string' || !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(value)) { errors.push(fieldName + ': Invalid email format'); } } else if (cleanType === 'url') { if (typeof value !== 'string') { errors.push(fieldName + ': URL must be a string'); } else { try { new URL(value); } catch { errors.push(fieldName + ': Invalid URL format'); } } } else if (cleanType === 'uuid') { if (typeof value !== 'string' || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value)) { errors.push(fieldName + ': Invalid UUID format'); } } else if (cleanType === 'number') { if (typeof value !== 'number' || isNaN(value)) { errors.push(fieldName + ': Must be a valid number'); } } else if (cleanType === 'positive') { if (typeof value !== 'number' || isNaN(value) || value <= 0) { errors.push(fieldName + ': Must be a positive number'); } } else if (cleanType === 'int') { if (typeof value !== 'number' || isNaN(value) || !Number.isInteger(value)) { errors.push(fieldName + ': Must be an integer'); } } else if (cleanType === 'boolean') { if (typeof value !== 'boolean') { errors.push(fieldName + ': Must be a boolean'); } } else if (cleanType === 'string') { if (typeof value !== 'string') { errors.push(fieldName + ': Must be a string'); } } else if (cleanType.startsWith('string(')) { if (typeof value !== 'string') { errors.push(fieldName + ': Must be a string'); } else { const match = cleanType.match(/string\\((\\d+)?,?(\\d+)?\\)/); if (match) { const minLength = match[1] ? parseInt(match[1]) : 0; const maxLength = match[2] ? parseInt(match[2]) : Infinity; if (value.length < minLength) { errors.push(fieldName + ': String too short (min ' + minLength + ')'); } if (value.length > maxLength) { errors.push(fieldName + ': String too long (max ' + maxLength + ')'); } } } } else if (cleanType.includes('[]')) { if (!Array.isArray(value)) { errors.push(fieldName + ': Must be an array'); } } } return errors; } document.getElementById('validate-btn').addEventListener('click', function() { const input = document.getElementById('test-input').value; const result = document.getElementById('validation-result'); try { const data = JSON.parse(input); const schema = ${schemaStr}; const validationErrors = validateData(schema, data); if (validationErrors.length === 0) { result.innerHTML = '<div class="valid">✓ Valid data</div>'; } else { result.innerHTML = '<div class="invalid">✗ Validation errors:<br>' + validationErrors.map(err => '• ' + err).join('<br>') + '</div>'; } } catch (e) { result.innerHTML = '<div class="invalid">✗ Invalid JSON: ' + e.message + '</div>'; } });`; } }