UNPKG

obj2buf

Version:

A type-safe encoder/decoder for structured binary data with snake_case API design

174 lines (153 loc) 5.58 kB
/** * @fileoverview Schema class for wrapping and managing a single Type */ const { ParserError } = require('./types'); /** * Schema class for wrapping a single Type with encoding/decoding capabilities * @class */ module.exports = class Schema { /** * Create a new Schema instance * @constructor * @param {import('./types').Type} type - The root type for this schema */ constructor(type) { if (!type) { throw new ParserError('Schema requires a Type'); } /** * The root type for this schema * @type {import('./types').Type} * @private */ this._type = type; } /** * Get the total byte length required for the schema * Returns null if the type has variable length * @type {number|null} * @readonly */ get byte_length() { return this._type.byte_length; } /** * Calculate the total byte length for a specific value * @param {*} value - The value to calculate length for * @returns {number} The total byte length required for encoding this value */ calculate_byte_length(value) { return this._type.calculate_byte_length(value); } /** * Get whether the schema has a static (fixed) length * @type {boolean} * @readonly */ get is_static_length() { return this._type.is_static_length; } /** * Validates a value against this schema * @param {*} value - The value to validate * @throws {ParserError} If validation fails */ validate(value) { try { this._type.validate(value); } catch (error) { if (error instanceof ParserError) { throw new ParserError(`Schema validation failed: ${error.message}`); } throw error; } } /** * Encode a value into a provided buffer according to the schema * @param {*} value - The value to encode * @param {Buffer} buffer - Buffer to write to (required) * @param {number} [offset=0] - Offset in the buffer to start writing at * @param {Object} [options={}] - Encoding options * @param {boolean} [options.unsafe=false] - Skip validation for performance * @returns {number} The number of bytes written * @throws {ParserError} If buffer is not provided or is too small */ encode(value, buffer, offset = 0, options = {}) { if (!buffer) { throw new ParserError('Buffer is required for encode(). Use serialize() to auto-allocate a buffer.'); } if (!options.unsafe) { if (!Buffer.isBuffer(buffer)) { throw new ParserError('Provided buffer must be a Buffer instance'); } const byte_length = this.calculate_byte_length(value); if ((buffer.length - offset) < byte_length) { throw new ParserError(`Buffer is too small. Required: ${byte_length}, Available: ${buffer.length - offset}`); } } return this._type.encode(value, buffer, offset, options); } /** * Serialize a value into a new buffer according to the schema * @param {*} value - The value to serialize * @param {number} [offset=0] - Number of bytes to pad at the start of the buffer * @param {Object} [options={}] - Encoding options * @param {boolean} [options.unsafe=false] - Skip validation for performance * @returns {Buffer} A new buffer containing the encoded data */ serialize(value, offset = 0, options = {}) { const byte_length = this.calculate_byte_length(value); const buffer = Buffer.alloc(byte_length + offset); this._type.encode(value, buffer, offset, options); return buffer; } /** * Decode a buffer into a value according to the schema * @param {Buffer} buffer - The buffer to decode * @param {number} [offset=0] - Offset in the buffer to start reading from * @returns {{value: *, bytes_read: number}} The decoded value and bytes read * @throws {ParserError} If the buffer is too small for the schema */ decode(buffer, offset = 0) { return this._type.decode(buffer, offset); } /** * Deserialize a buffer into a value according to the schema * @param {Buffer} buffer - The buffer to deserialize * @param {number} [offset=0] - Offset in the buffer to start reading from * @returns {*} The deserialized value (without wrapper) * @throws {ParserError} If the buffer is too small for the schema */ deserialize(buffer, offset = 0) { const result = this._type.decode(buffer, offset); return result.value; } /** * Convert the schema to a JSON representation (non-snake_case alias) * @returns {Object} JSON representation of the schema */ toJSON() { return this.to_json(); } /** * Convert the schema to a JSON representation * @returns {Object} JSON representation of the schema */ to_json() { return { type: 'Schema', root_type: this._type.to_json() }; } /** * Create a Schema instance from a JSON representation * @param {Object} obj - JSON representation of the schema * @returns {Schema} A new Schema instance * @static */ static from_json(obj) { const { from_json } = require('./types'); return new Schema(from_json(obj.root_type)); } }