UNPKG

@jsonjoy.com/json-pack

Version:

High-performance JSON serialization library

253 lines 9.94 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.XdrSchemaValidator = void 0; /** * XDR schema validator for validating XDR schemas and values according to RFC 4506. */ class XdrSchemaValidator { /** * Validates an XDR schema structure. */ validateSchema(schema) { try { return this.validateSchemaInternal(schema); } catch { return false; } } /** * Validates if a value conforms to the given XDR schema. */ validateValue(value, schema) { try { return this.validateValueInternal(value, schema); } catch { return false; } } validateSchemaInternal(schema) { if (!schema || typeof schema !== 'object' || !schema.type) { return false; } switch (schema.type) { // Primitive types case 'void': case 'int': case 'unsigned_int': case 'boolean': case 'hyper': case 'unsigned_hyper': case 'float': case 'double': case 'quadruple': return true; case 'enum': return this.validateEnumSchema(schema); // Wide primitive types case 'opaque': return this.validateOpaqueSchema(schema); case 'vopaque': return this.validateVarlenOpaqueSchema(schema); case 'string': return this.validateStringSchema(schema); // Composite types case 'array': return this.validateArraySchema(schema); case 'varray': return this.validateVarlenArraySchema(schema); case 'struct': return this.validateStructSchema(schema); case 'union': return this.validateUnionSchema(schema); case 'optional': return this.validateOptionalSchema(schema); case 'const': return this.validateConstantSchema(schema); default: return false; } } validateEnumSchema(schema) { if (!schema.values || typeof schema.values !== 'object') { return false; } const values = Object.values(schema.values); const uniqueValues = new Set(values); // Check for duplicate values if (values.length !== uniqueValues.size) { return false; } // Check that all values are integers return values.every((value) => Number.isInteger(value)); } validateOpaqueSchema(schema) { return typeof schema.size === 'number' && Number.isInteger(schema.size) && schema.size >= 0; } validateVarlenOpaqueSchema(schema) { return !schema.size || (typeof schema.size === 'number' && Number.isInteger(schema.size) && schema.size >= 0); } validateStringSchema(schema) { return !schema.size || (typeof schema.size === 'number' && Number.isInteger(schema.size) && schema.size >= 0); } validateArraySchema(schema) { if (!schema.elements || typeof schema.size !== 'number' || !Number.isInteger(schema.size) || schema.size < 0) { return false; } return this.validateSchemaInternal(schema.elements); } validateVarlenArraySchema(schema) { if (!schema.elements) { return false; } if (schema.size !== undefined) { if (typeof schema.size !== 'number' || !Number.isInteger(schema.size) || schema.size < 0) { return false; } } return this.validateSchemaInternal(schema.elements); } validateStructSchema(schema) { if (!Array.isArray(schema.fields)) { return false; } const fieldNames = new Set(); for (const field of schema.fields) { if (!Array.isArray(field) || field.length !== 2) { return false; } const [fieldSchema, fieldName] = field; if (typeof fieldName !== 'string' || fieldName === '') { return false; } if (fieldNames.has(fieldName)) { return false; // Duplicate field name } fieldNames.add(fieldName); if (!this.validateSchemaInternal(fieldSchema)) { return false; } } return true; } validateUnionSchema(schema) { if (!Array.isArray(schema.arms) || schema.arms.length === 0) { return false; } const discriminants = new Set(); for (const arm of schema.arms) { if (!Array.isArray(arm) || arm.length !== 2) { return false; } const [discriminant, armSchema] = arm; // Check for duplicate discriminants if (discriminants.has(discriminant)) { return false; } discriminants.add(discriminant); // Validate discriminant type if (typeof discriminant !== 'number' && typeof discriminant !== 'string' && typeof discriminant !== 'boolean') { return false; } if (!this.validateSchemaInternal(armSchema)) { return false; } } // Validate default schema if present if (schema.default && !this.validateSchemaInternal(schema.default)) { return false; } return true; } validateOptionalSchema(schema) { if (!schema.element) { return false; } return this.validateSchemaInternal(schema.element); } validateConstantSchema(schema) { if (typeof schema.value !== 'number' || !Number.isInteger(schema.value)) { return false; } return true; } validateValueInternal(value, schema) { switch (schema.type) { case 'void': return value === null || value === undefined; case 'int': return typeof value === 'number' && Number.isInteger(value) && value >= -2147483648 && value <= 2147483647; case 'unsigned_int': return typeof value === 'number' && Number.isInteger(value) && value >= 0 && value <= 4294967295; case 'boolean': return typeof value === 'boolean'; case 'hyper': return (typeof value === 'number' && Number.isInteger(value)) || typeof value === 'bigint'; case 'unsigned_hyper': return ((typeof value === 'number' && Number.isInteger(value) && value >= 0) || (typeof value === 'bigint' && value >= BigInt(0))); case 'float': case 'double': case 'quadruple': return typeof value === 'number'; case 'enum': { const enumSchema = schema; return typeof value === 'string' && value in enumSchema.values; } case 'opaque': { const opaqueSchema = schema; return value instanceof Uint8Array && value.length === opaqueSchema.size; } case 'vopaque': { const vopaqueSchema = schema; return value instanceof Uint8Array && (!vopaqueSchema.size || value.length <= vopaqueSchema.size); } case 'string': { const stringSchema = schema; return typeof value === 'string' && (!stringSchema.size || value.length <= stringSchema.size); } case 'array': { const arraySchema = schema; return (Array.isArray(value) && value.length === arraySchema.size && value.every((item) => this.validateValueInternal(item, arraySchema.elements))); } case 'varray': { const varraySchema = schema; return (Array.isArray(value) && (!varraySchema.size || value.length <= varraySchema.size) && value.every((item) => this.validateValueInternal(item, varraySchema.elements))); } case 'struct': { const structSchema = schema; if (!value || typeof value !== 'object' || Array.isArray(value)) { return false; } const valueObj = value; return structSchema.fields.every(([fieldSchema, fieldName]) => fieldName in valueObj && this.validateValueInternal(valueObj[fieldName], fieldSchema)); } case 'union': { const unionSchema = schema; // For union validation, we need additional context about which arm is selected // This is a simplified validation - in practice, the discriminant would be known const matchesArm = unionSchema.arms.some(([, armSchema]) => this.validateValueInternal(value, armSchema)); const matchesDefault = unionSchema.default ? this.validateValueInternal(value, unionSchema.default) : false; return matchesArm || matchesDefault; } case 'optional': { const optionalSchema = schema; // Optional values can be null/undefined or match the element schema return value === null || value === undefined || this.validateValueInternal(value, optionalSchema.element); } case 'const': { // Constants have no runtime value validation return true; } default: return false; } } } exports.XdrSchemaValidator = XdrSchemaValidator; //# sourceMappingURL=XdrSchemaValidator.js.map