@jsonjoy.com/json-pack
Version:
High-performance JSON serialization library
321 lines • 11.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AvroSchemaEncoder = void 0;
const AvroEncoder_1 = require("./AvroEncoder");
const AvroSchemaValidator_1 = require("./AvroSchemaValidator");
class AvroSchemaEncoder {
constructor(writer) {
this.writer = writer;
this.namedSchemas = new Map();
this.encoder = new AvroEncoder_1.AvroEncoder(writer);
this.validator = new AvroSchemaValidator_1.AvroSchemaValidator();
}
encode(value, schema, selectedIndex) {
this.writer.reset();
this.namedSchemas.clear();
if (!this.validator.validateSchema(schema)) {
throw new Error('Invalid Avro schema');
}
if (!this.validator.validateValue(value, schema)) {
throw new Error('Value does not conform to schema');
}
this.collectNamedSchemas(schema);
if (Array.isArray(schema) && selectedIndex !== undefined) {
this.writeUnion(value, schema, selectedIndex);
}
else {
this.writeValue(value, schema);
}
return this.writer.flush();
}
writeNull(schema) {
this.validateSchemaType(schema, 'null');
this.encoder.writeNull();
}
writeBoolean(value, schema) {
this.validateSchemaType(schema, 'boolean');
this.encoder.writeBoolean(value);
}
writeInt(value, schema) {
this.validateSchemaType(schema, 'int');
if (!Number.isInteger(value) || value < -2147483648 || value > 2147483647) {
throw new Error('Value is not a valid 32-bit integer');
}
this.encoder.writeInt(value);
}
writeLong(value, schema) {
this.validateSchemaType(schema, 'long');
this.encoder.writeLong(value);
}
writeFloat(value, schema) {
this.validateSchemaType(schema, 'float');
this.encoder.writeFloat(value);
}
writeDouble(value, schema) {
this.validateSchemaType(schema, 'double');
this.encoder.writeDouble(value);
}
writeBytes(value, schema) {
this.validateSchemaType(schema, 'bytes');
this.encoder.writeBin(value);
}
writeString(value, schema) {
this.validateSchemaType(schema, 'string');
this.encoder.writeStr(value);
}
writeRecord(value, schema) {
if (typeof schema === 'object' && schema.type !== 'record') {
throw new Error('Schema is not a record schema');
}
const recordSchema = this.resolveSchema(schema);
if (recordSchema.type !== 'record') {
throw new Error('Schema is not a record schema');
}
for (let i = 0; i < recordSchema.fields.length; i++) {
const field = recordSchema.fields[i];
const fieldValue = value[field.name];
if (fieldValue !== undefined) {
this.writeValue(fieldValue, field.type);
}
else if (field.default !== undefined) {
this.writeValue(field.default, field.type);
}
else {
throw new Error(`Missing required field: ${field.name}`);
}
}
}
writeEnum(value, schema) {
if (typeof schema === 'object' && schema.type !== 'enum') {
throw new Error('Schema is not an enum schema');
}
const enumSchema = this.resolveSchema(schema);
if (enumSchema.type !== 'enum') {
throw new Error('Schema is not an enum schema');
}
const index = enumSchema.symbols.indexOf(value);
if (index === -1) {
throw new Error(`Invalid enum value: ${value}`);
}
this.writeVarIntSigned(this.encodeZigZag32(index));
}
writeArray(value, schema) {
if (typeof schema === 'object' && schema.type !== 'array') {
throw new Error('Schema is not an array schema');
}
const arraySchema = this.resolveSchema(schema);
if (arraySchema.type !== 'array') {
throw new Error('Schema is not an array schema');
}
this.writeVarIntUnsigned(value.length);
const length = value.length;
for (let i = 0; i < length; i++) {
this.writeValue(value[i], arraySchema.items);
}
this.writeVarIntUnsigned(0);
}
writeMap(value, schema) {
if (typeof schema === 'object' && schema.type !== 'map') {
throw new Error('Schema is not a map schema');
}
const mapSchema = this.resolveSchema(schema);
if (mapSchema.type !== 'map') {
throw new Error('Schema is not a map schema');
}
const entries = Object.entries(value);
this.writeVarIntUnsigned(entries.length);
const length = entries.length;
for (let i = 0; i < length; i++) {
const entry = entries[i];
this.encoder.writeStr(entry[0]);
this.writeValue(entry[1], mapSchema.values);
}
this.writeVarIntUnsigned(0);
}
writeUnion(value, schema, selectedIndex) {
if (!Array.isArray(schema)) {
throw new Error('Schema is not a union schema');
}
let index = selectedIndex;
if (index === undefined) {
index = schema.findIndex((subSchema) => this.validator.validateValue(value, subSchema));
if (index === -1) {
throw new Error('Value does not match any schema in the union');
}
}
if (index < 0 || index >= schema.length) {
throw new Error('Invalid union index');
}
this.writeVarIntSigned(this.encodeZigZag32(index));
this.writeValue(value, schema[index]);
}
writeFixed(value, schema) {
if (typeof schema === 'object' && schema.type !== 'fixed') {
throw new Error('Schema is not a fixed schema');
}
const fixedSchema = this.resolveSchema(schema);
if (fixedSchema.type !== 'fixed') {
throw new Error('Schema is not a fixed schema');
}
if (value.length !== fixedSchema.size) {
throw new Error(`Fixed value length ${value.length} does not match schema size ${fixedSchema.size}`);
}
this.writer.buf(value, value.length);
}
writeNumber(value, schema) {
const resolvedSchema = this.resolveSchema(schema);
const schemaType = typeof resolvedSchema === 'string'
? resolvedSchema
: Array.isArray(resolvedSchema)
? 'union'
: resolvedSchema.type;
switch (schemaType) {
case 'int':
this.writeInt(value, schema);
break;
case 'long':
this.writeLong(value, schema);
break;
case 'float':
this.writeFloat(value, schema);
break;
case 'double':
this.writeDouble(value, schema);
break;
default:
throw new Error(`Schema type ${schemaType} is not a numeric type`);
}
}
writeValue(value, schema) {
const resolvedSchema = this.resolveSchema(schema);
if (typeof resolvedSchema === 'string') {
switch (resolvedSchema) {
case 'null':
this.encoder.writeNull();
break;
case 'boolean':
this.encoder.writeBoolean(value);
break;
case 'int':
this.encoder.writeInt(value);
break;
case 'long':
this.encoder.writeLong(value);
break;
case 'float':
this.encoder.writeFloat(value);
break;
case 'double':
this.encoder.writeDouble(value);
break;
case 'bytes':
this.encoder.writeBin(value);
break;
case 'string':
this.encoder.writeStr(value);
break;
default:
throw new Error(`Unknown primitive type: ${resolvedSchema}`);
}
return;
}
if (Array.isArray(resolvedSchema)) {
this.writeUnion(value, resolvedSchema);
return;
}
switch (resolvedSchema.type) {
case 'record':
this.writeRecord(value, resolvedSchema);
break;
case 'enum':
this.writeEnum(value, resolvedSchema);
break;
case 'array':
this.writeArray(value, resolvedSchema);
break;
case 'map':
this.writeMap(value, resolvedSchema);
break;
case 'fixed':
this.writeFixed(value, resolvedSchema);
break;
default:
throw new Error(`Unknown schema type: ${resolvedSchema.type}`);
}
}
validateSchemaType(schema, expectedType) {
const resolvedSchema = this.resolveSchema(schema);
const actualType = typeof resolvedSchema === 'string'
? resolvedSchema
: Array.isArray(resolvedSchema)
? 'union'
: resolvedSchema.type;
if (actualType !== expectedType) {
throw new Error(`Expected schema type ${expectedType}, got ${actualType}`);
}
}
resolveSchema(schema) {
if (typeof schema === 'string') {
const namedSchema = this.namedSchemas.get(schema);
return namedSchema || schema;
}
return schema;
}
collectNamedSchemas(schema) {
if (typeof schema === 'string' || Array.isArray(schema)) {
return;
}
if (typeof schema === 'object' && schema !== null) {
switch (schema.type) {
case 'record':
const recordSchema = schema;
const recordFullName = this.getFullName(recordSchema.name, recordSchema.namespace);
this.namedSchemas.set(recordFullName, recordSchema);
recordSchema.fields.forEach((field) => this.collectNamedSchemas(field.type));
break;
case 'enum':
const enumSchema = schema;
const enumFullName = this.getFullName(enumSchema.name, enumSchema.namespace);
this.namedSchemas.set(enumFullName, enumSchema);
break;
case 'fixed':
const fixedSchema = schema;
const fixedFullName = this.getFullName(fixedSchema.name, fixedSchema.namespace);
this.namedSchemas.set(fixedFullName, fixedSchema);
break;
case 'array':
this.collectNamedSchemas(schema.items);
break;
case 'map':
this.collectNamedSchemas(schema.values);
break;
}
}
}
getFullName(name, namespace) {
return namespace ? `${namespace}.${name}` : name;
}
writeVarIntUnsigned(value) {
const writer = this.writer;
let n = value >>> 0;
while (n >= 0x80) {
writer.u8((n & 0x7f) | 0x80);
n >>>= 7;
}
writer.u8(n & 0x7f);
}
writeVarIntSigned(value) {
const writer = this.writer;
let n = value >>> 0;
while (n >= 0x80) {
writer.u8((n & 0x7f) | 0x80);
n >>>= 7;
}
writer.u8(n & 0x7f);
}
encodeZigZag32(value) {
return (value << 1) ^ (value >> 31);
}
}
exports.AvroSchemaEncoder = AvroSchemaEncoder;
//# sourceMappingURL=AvroSchemaEncoder.js.map