UNPKG

fortify-schema

Version:

TypeScript interface-like schema validation system that's easier to use than Zod

177 lines (159 loc) 6.78 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>'; } });`; } }