@jsonjoy.com/json-pack
Version:
High-performance JSON serialization library
253 lines • 9.94 kB
JavaScript
"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