jsonbinpack
Version:
A space-efficient schema-driven binary JSON serialization format based on JSON Schema
555 lines (452 loc) • 15.1 kB
text/typescript
/*
* Copyright 2021 Juan Cruz Viotti
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import tap from 'tap'
import {
STRING_DICTIONARY_COMPRESSOR,
STRING_BROTLI,
URL_PROTOCOL_HOST_REST,
RFC3339_DATE_INTEGER_TRIPLET,
BOUNDED_PREFIX_LENGTH_8BIT_FIXED,
ROOF_PREFIX_LENGTH_ENUM_VARINT,
UTF8_STRING_NO_LENGTH,
SHARED_STRING_POINTER_RELATIVE_OFFSET,
FLOOR_PREFIX_LENGTH_ENUM_VARINT,
STRING_UNBOUNDED_SCOPED_PREFIX_LENGTH
} from '../../../lib/encoder/string/encode'
import {
BoundedOptions,
RoofOptions,
FloorOptions
} from '../../../lib/encoder/string/options'
import {
ResizableBuffer,
EncodingContext,
getDefaultEncodingContext
} from '../../../lib/encoder'
tap.test('STRING_DICTIONARY_COMPRESSOR: should encode "" with [ bar ]', (test) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(1))
const bytesWritten: number =
STRING_DICTIONARY_COMPRESSOR(buffer, 0, '', {
index: [ 'bar' ],
dictionary: {
bar: 0
}
}, context)
test.strictSame(buffer.getBuffer(), Buffer.from([ 0x00 ]))
test.is(bytesWritten, 1)
test.end()
})
tap.test('STRING_DICTIONARY_COMPRESSOR: should encode "foo bar baz" with [ bar ]', (test) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(10))
const bytesWritten: number =
STRING_DICTIONARY_COMPRESSOR(buffer, 0, 'foo bar baz', {
index: [ 'bar' ],
dictionary: {
bar: 0
}
}, context)
test.strictSame(buffer.getBuffer(), Buffer.from([
// Whole string length
0x0b,
// Zigzag negative length + 'foo'
0x07, 0x66, 0x6f, 0x6f,
// 'bar'
0x02,
// Zigzag negative length + 'baz'
0x07, 0x62, 0x61, 0x7a
]))
test.is(bytesWritten, 10)
test.end()
})
tap.test('STRING_DICTIONARY_COMPRESSOR: should encode "foo bar foo" with [ bar ]', (test) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(11))
const bytesWritten: number =
STRING_DICTIONARY_COMPRESSOR(buffer, 0, 'foo bar foo', {
index: [ 'bar' ],
dictionary: {
bar: 0
}
}, context)
test.strictSame(buffer.getBuffer(), Buffer.from([
// Whole string length
0x0b,
// Zigzag negative length + 'foo'
0x07, 0x66, 0x6f, 0x6f,
// 'bar'
0x02,
// Start of shared string
0x00,
// Zigzag negative length + offset 'foo'
0x03, 0x06
]))
test.is(bytesWritten, 9)
test.end()
})
tap.test('STRING_DICTIONARY_COMPRESSOR: should encode "bar foo foo" with [ bar ]', (test) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(10))
const bytesWritten: number =
STRING_DICTIONARY_COMPRESSOR(buffer, 0, 'bar foo foo', {
index: [ 'bar' ],
dictionary: {
bar: 0
}
}, context)
test.strictSame(buffer.getBuffer(), Buffer.from([
// Whole string length
0x0b,
// 'bar'
0x02,
// Zigzag negative length
0x0f,
// 'foo foo'
0x66, 0x6f, 0x6f, 0x20, 0x66, 0x6f, 0x6f
]))
test.is(bytesWritten, 10)
test.end()
})
tap.test('STRING_BROTLI: should encode "foo bar baz"', (test) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(16))
const bytesWritten: number = STRING_BROTLI(buffer, 0, 'foo bar baz', {}, context)
test.strictSame(buffer.getBuffer(), Buffer.from([
0x0f, 0x0b, 0x05, 0x80, 0x66, 0x6f, 0x6f, 0x20,
0x62, 0x61, 0x72, 0x20, 0x62, 0x61, 0x7a, 0x03
]))
test.is(bytesWritten, 16)
test.end()
})
tap.test('URL_PROTOCOL_HOST_REST: should encode "https://google.com"', (test) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(18))
const bytesWritten: number =
URL_PROTOCOL_HOST_REST(buffer, 0, 'https://google.com', {}, context)
test.strictSame(buffer.getBuffer(), Buffer.from([
// Protocol length
0x06,
// 'https'
0x68, 0x74, 0x74, 0x70, 0x73,
// Host length
0x0b,
// 'google.com'
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
// Rest length
0x01
]))
test.is(bytesWritten, 18)
test.end()
})
tap.test('URL_PROTOCOL_HOST_REST: should encode "https://google.com/"', (test) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(19))
const bytesWritten: number =
URL_PROTOCOL_HOST_REST(buffer, 0, 'https://google.com/', {}, context)
test.strictSame(buffer.getBuffer(), Buffer.from([
// Protocol length
0x06,
// 'https'
0x68, 0x74, 0x74, 0x70, 0x73,
// Host length
0x0b,
// 'google.com'
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
// Rest length
0x02,
0x2f
]))
test.is(bytesWritten, 19)
test.end()
})
tap.test('URL_PROTOCOL_HOST_REST: should encode "https://google.com/foo"', (test) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(22))
const bytesWritten: number =
URL_PROTOCOL_HOST_REST(buffer, 0, 'https://google.com/foo', {}, context)
test.strictSame(buffer.getBuffer(), Buffer.from([
// Protocol length
0x06,
// 'https'
0x68, 0x74, 0x74, 0x70, 0x73,
// Host length
0x0b,
// 'google.com'
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
// Rest length
0x05,
0x2f, 0x66, 0x6f, 0x6f
]))
test.is(bytesWritten, 22)
test.end()
})
tap.test('URL_PROTOCOL_HOST_REST: should encode "https://google.com/foo?bar=1"', (test) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(28))
const bytesWritten: number =
URL_PROTOCOL_HOST_REST(buffer, 0, 'https://google.com/foo?bar=1', {}, context)
test.strictSame(buffer.getBuffer(), Buffer.from([
// Protocol length
0x06,
// 'https'
0x68, 0x74, 0x74, 0x70, 0x73,
// Host length
0x0b,
// 'google.com'
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d,
// Rest length
0x0b,
0x2f, 0x66, 0x6f, 0x6f,
// Query string
0x3f, 0x62, 0x61, 0x72, 0x3d, 0x31
]))
test.is(bytesWritten, 28)
test.end()
})
tap.test('RFC3339_DATE_INTEGER_TRIPLET: should encode "2014-10-01"', (test) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(4))
const bytesWritten: number =
RFC3339_DATE_INTEGER_TRIPLET(buffer, 0, '2014-10-01', {}, context)
test.strictSame(buffer.getBuffer(), Buffer.from([ 0xde, 0x07, 0x0a, 0x01 ]))
test.is(bytesWritten, 4)
test.end()
})
tap.test('ROOF_PREFIX_LENGTH_ENUM_VARINT: should encode "foo" (..4)', (
test
) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(4))
const bytesWritten: number =
ROOF_PREFIX_LENGTH_ENUM_VARINT(buffer, 0, 'foo', {
maximum: 4
}, context)
test.strictSame(buffer.getBuffer(), Buffer.from([ 0x02, 0x66, 0x6f, 0x6f ]))
test.is(bytesWritten, 4)
test.end()
})
tap.test('ROOF_PREFIX_LENGTH_ENUM_VARINT: should encode "fooo" (..4)', (
test
) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(4))
const bytesWritten: number =
ROOF_PREFIX_LENGTH_ENUM_VARINT(buffer, 0, 'fooo', {
maximum: 4
}, context)
test.strictSame(buffer.getBuffer(), Buffer.from([ 0x01, 0x66, 0x6f, 0x6f, 0x6f ]))
test.is(bytesWritten, 5)
test.end()
})
tap.test('FLOOR_PREFIX_LENGTH_ENUM_VARINT: should encode "foo" (3..)', (
test
) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(4))
const bytesWritten: number =
FLOOR_PREFIX_LENGTH_ENUM_VARINT(buffer, 0, 'foo', {
minimum: 3
}, context)
test.strictSame(buffer.getBuffer(), Buffer.from([ 0x01, 0x66, 0x6f, 0x6f ]))
test.is(bytesWritten, 4)
test.end()
})
tap.test('BOUNDED_PREFIX_LENGTH_8BIT_FIXED: should encode a shared string', (
test
) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(10))
const options: BoundedOptions = {
minimum: 0,
maximum: 4
}
const bytesWritten1: number = BOUNDED_PREFIX_LENGTH_8BIT_FIXED(
buffer, 0, 'foo', options, context)
const bytesWritten2: number = BOUNDED_PREFIX_LENGTH_8BIT_FIXED(
buffer, bytesWritten1, 'foo', options, context)
test.strictSame(buffer.getBuffer(), Buffer.from([
// String length + foo
0x04, 0x66, 0x6f, 0x6f,
// Start of pointer
0x00,
// String length
0x04,
// Pointer (current = 6 - location = 1)
0x05
]))
test.is(context.strings.get('foo'), 1)
test.false(context.keys.has('foo'))
test.is(bytesWritten1, 4)
test.is(bytesWritten2, 3)
test.end()
})
tap.test('ROOF_PREFIX_LENGTH_ENUM_VARINT: should encode a shared string', (
test
) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(10))
const options: RoofOptions = {
maximum: 4
}
const bytesWritten1: number = ROOF_PREFIX_LENGTH_ENUM_VARINT(
buffer, 0, 'foo', options, context)
const bytesWritten2: number = ROOF_PREFIX_LENGTH_ENUM_VARINT(
buffer, bytesWritten1, 'foo', options, context)
test.strictSame(buffer.getBuffer(), Buffer.from([
// String length + foo
0x02, 0x66, 0x6f, 0x6f,
// Start of pointer
0x00,
// String length
0x02,
// Pointer (current = 6 - location = 1)
0x05
]))
test.is(context.strings.get('foo'), 1)
test.false(context.keys.has('foo'))
test.is(bytesWritten1, 4)
test.is(bytesWritten2, 3)
test.end()
})
tap.test('UTF8_STRING_NO_LENGTH: should encode a string', (
test
) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(3))
const bytesWritten: number = UTF8_STRING_NO_LENGTH(
buffer, 0, 'foo', {
size: 3
}, context)
test.strictSame(buffer.getBuffer(), Buffer.from([
// 'foo'
0x66, 0x6f, 0x6f
]))
test.is(context.strings.get('foo'), 0)
test.false(context.keys.has('foo'))
test.is(bytesWritten, 3)
test.end()
})
tap.test('SHARED_STRING_POINTER_RELATIVE_OFFSET: should encode a shared string', (
test
) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(4))
const bytesWritten1: number = UTF8_STRING_NO_LENGTH(
buffer, 0, 'foo', {
size: 3
}, context)
const bytesWritten2: number = SHARED_STRING_POINTER_RELATIVE_OFFSET(
buffer, bytesWritten1, 'foo', {
size: 3
}, context)
test.strictSame(buffer.getBuffer(), Buffer.from([
// 'foo'
0x66, 0x6f, 0x6f,
// Offset
0x03
]))
test.is(context.strings.get('foo'), 0)
test.false(context.keys.has('foo'))
test.is(bytesWritten1, 3)
test.is(bytesWritten2, 1)
test.end()
})
tap.test('FLOOR_PREFIX_LENGTH_ENUM_VARINT: should encode a shared string', (
test
) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(10))
const options: FloorOptions = {
minimum: 3
}
const bytesWritten1: number = FLOOR_PREFIX_LENGTH_ENUM_VARINT(
buffer, 0, 'foo', options, context)
const bytesWritten2: number = FLOOR_PREFIX_LENGTH_ENUM_VARINT(
buffer, bytesWritten1, 'foo', options, context)
test.strictSame(buffer.getBuffer(), Buffer.from([
// String length + foo
0x01, 0x66, 0x6f, 0x6f,
// Start of pointer
0x00,
// String length
0x01,
// Pointer (current = 6 - location = 1)
0x05
]))
test.is(context.strings.get('foo'), 1)
test.false(context.keys.has('foo'))
test.is(bytesWritten1, 4)
test.is(bytesWritten2, 3)
test.end()
})
tap.test('STRING_UNBOUNDED_SCOPED_PREFIX_LENGTH: should encode "foo"', (
test
) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(4))
const bytesWritten: number =
STRING_UNBOUNDED_SCOPED_PREFIX_LENGTH(buffer, 0, 'foo', {}, context)
test.strictSame(buffer.getBuffer(), Buffer.from([ 0x04, 0x66, 0x6f, 0x6f ]))
test.is(bytesWritten, 4)
test.end()
})
tap.test('STRING_UNBOUNDED_SCOPED_PREFIX_LENGTH: should encode a shared string', (
test
) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(10))
const bytesWritten1: number = STRING_UNBOUNDED_SCOPED_PREFIX_LENGTH(
buffer, 0, 'foo', {}, context)
const bytesWritten2: number = STRING_UNBOUNDED_SCOPED_PREFIX_LENGTH(
buffer, bytesWritten1, 'foo', {}, context)
test.strictSame(buffer.getBuffer(), Buffer.from([
// String length + foo
0x04, 0x66, 0x6f, 0x6f,
// Start of pointer
0x00,
// Pointer (current = 5 - location = 0)
0x05
]))
test.is(context.strings.get('foo'), 1)
test.is(context.keys.get('foo'), 4)
test.is(bytesWritten1, 4)
test.is(bytesWritten2, 2)
test.end()
})
tap.test('STRING_UNBOUNDED_SCOPED_PREFIX_LENGTH: should not encode a shared non-key string', (
test
) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(10))
const bytesWritten1: number = FLOOR_PREFIX_LENGTH_ENUM_VARINT(
buffer, 0, 'foo', {
minimum: 3
}, context)
const bytesWritten2: number = STRING_UNBOUNDED_SCOPED_PREFIX_LENGTH(
buffer, bytesWritten1, 'foo', {}, context)
test.strictSame(buffer.getBuffer(), Buffer.from([
// String length + foo
0x01, 0x66, 0x6f, 0x6f,
// String length + foo
0x04, 0x66, 0x6f, 0x6f
]))
test.is(context.strings.get('foo'), 5)
test.is(context.keys.get('foo'), 4)
test.is(bytesWritten1, 4)
test.is(bytesWritten2, 4)
test.end()
})