2023-12-29 19:17:58 +00:00
|
|
|
import { describe, expect, it } from 'vitest'
|
2025-03-20 19:37:24 +00:00
|
|
|
import { deserializeTCompactProtocol, readVarInt } from '../src/thrift.js'
|
2024-05-28 20:59:06 +00:00
|
|
|
import { reader } from './helpers.js'
|
2023-12-29 19:17:58 +00:00
|
|
|
|
|
|
|
|
describe('deserializeTCompactProtocol function', () => {
|
|
|
|
|
|
|
|
|
|
it('parses basic types correctly', () => {
|
|
|
|
|
const buffer = new ArrayBuffer(128)
|
|
|
|
|
const view = new DataView(buffer)
|
|
|
|
|
let index = 0
|
|
|
|
|
|
|
|
|
|
// Boolean
|
|
|
|
|
view.setUint8(index++, 0x11) // Field 1 type TRUE
|
|
|
|
|
view.setUint8(index++, 0x12) // Field 2 type FALSE
|
|
|
|
|
|
|
|
|
|
// Byte
|
|
|
|
|
view.setUint8(index++, 0x13) // Field 3 type BYTE
|
|
|
|
|
view.setUint8(index++, 0x7f) // Max value for a signed byte
|
|
|
|
|
|
|
|
|
|
// Int16
|
|
|
|
|
view.setUint8(index++, 0x14) // Field 4 type int16
|
|
|
|
|
view.setUint8(index++, 0xfe) // 0xfffe zigzag => 16-bit max value 0x7fff
|
|
|
|
|
view.setUint8(index++, 0xff)
|
|
|
|
|
view.setUint8(index++, 0x3)
|
|
|
|
|
|
|
|
|
|
// Int32
|
|
|
|
|
view.setUint8(index++, 0x15) // Field 5 type int32
|
|
|
|
|
view.setUint8(index++, 0xfe) // 0xfffffffe zigzag => 32-bit max value 0x7fffffff
|
|
|
|
|
view.setUint8(index++, 0xff)
|
|
|
|
|
view.setUint8(index++, 0xff)
|
|
|
|
|
view.setUint8(index++, 0xff)
|
|
|
|
|
view.setUint8(index++, 0x0f)
|
|
|
|
|
|
|
|
|
|
// Int64
|
|
|
|
|
view.setUint8(index++, 0x16) // Field 6 type int64
|
|
|
|
|
view.setUint8(index++, 0xfe)
|
|
|
|
|
view.setUint8(index++, 0xff)
|
|
|
|
|
view.setUint8(index++, 0xff)
|
|
|
|
|
view.setUint8(index++, 0xff)
|
|
|
|
|
view.setUint8(index++, 0xff)
|
|
|
|
|
view.setUint8(index++, 0xff)
|
|
|
|
|
view.setUint8(index++, 0xff)
|
|
|
|
|
view.setUint8(index++, 0xff)
|
|
|
|
|
view.setUint8(index++, 0xff)
|
|
|
|
|
view.setUint8(index++, 0x01)
|
|
|
|
|
|
|
|
|
|
// Double
|
|
|
|
|
view.setUint8(index++, 0x17) // Field 7 type DOUBLE
|
|
|
|
|
view.setFloat64(index, 123.456, true)
|
|
|
|
|
index += 8
|
|
|
|
|
|
|
|
|
|
// String
|
|
|
|
|
const str = 'Hello, Thrift!'
|
|
|
|
|
view.setUint8(index++, 0x18) // Field 8 type STRING
|
|
|
|
|
// write string length as varint
|
|
|
|
|
const stringLengthVarInt = toVarInt(str.length)
|
|
|
|
|
stringLengthVarInt.forEach(byte => view.setUint8(index++, byte))
|
|
|
|
|
// write string bytes
|
|
|
|
|
for (let i = 0; i < str.length; i++) {
|
|
|
|
|
view.setUint8(index++, str.charCodeAt(i))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Mark the end of the structure
|
|
|
|
|
view.setUint8(index, 0x00) // STOP field
|
|
|
|
|
|
2024-05-01 07:55:16 +00:00
|
|
|
const reader = { view, offset: 0 }
|
|
|
|
|
const value = deserializeTCompactProtocol(reader)
|
|
|
|
|
expect(reader.offset).toBe(index + 1)
|
2023-12-29 19:17:58 +00:00
|
|
|
|
|
|
|
|
// Assertions for each basic type
|
2024-01-03 01:16:33 +00:00
|
|
|
expect(value.field_1).toBe(true) // TRUE
|
|
|
|
|
expect(value.field_2).toBe(false) // FALSE
|
|
|
|
|
expect(value.field_3).toBe(0x7f) // BYTE
|
|
|
|
|
expect(value.field_4).toBe(0x7fff) // I16
|
|
|
|
|
expect(value.field_5).toBe(0x7fffffff) // I32
|
|
|
|
|
expect(value.field_6).toBe(BigInt('0x7fffffffffffffff')) // I64
|
|
|
|
|
expect(value.field_7).toBeCloseTo(123.456) // DOUBLE
|
2024-05-04 07:38:19 +00:00
|
|
|
expect(new TextDecoder().decode(value.field_8)).toBe('Hello, Thrift!') // STRING
|
2023-12-29 19:17:58 +00:00
|
|
|
})
|
|
|
|
|
|
2024-08-19 01:23:54 +00:00
|
|
|
it('parses rle-dict column index correctly', () => {
|
|
|
|
|
const buffer = new Uint8Array([25, 17, 2, 25, 24, 8, 0, 0, 0, 0, 0, 0, 0, 0, 25, 24, 8, 0, 0, 0, 0, 0, 0, 0, 0, 21, 2, 25, 22, 0, 0])
|
|
|
|
|
const view = new DataView(buffer.buffer)
|
|
|
|
|
const reader = { view, offset: 0 }
|
|
|
|
|
const value = deserializeTCompactProtocol(reader)
|
|
|
|
|
expect(value.field_1).toEqual([false])
|
|
|
|
|
expect(value.field_2).toEqual([new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0])])
|
|
|
|
|
expect(value.field_3).toEqual([new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0])])
|
|
|
|
|
expect(value.field_4).toEqual(1)
|
|
|
|
|
expect(value.field_5).toEqual([0n])
|
|
|
|
|
expect(value.field_6).toBeUndefined()
|
|
|
|
|
expect(value.field_7).toBeUndefined()
|
|
|
|
|
expect(value.field_8).toBeUndefined()
|
|
|
|
|
})
|
|
|
|
|
|
2023-12-29 19:17:58 +00:00
|
|
|
})
|
2024-05-28 20:59:06 +00:00
|
|
|
|
|
|
|
|
describe('readVarInt', () => {
|
|
|
|
|
it('read single-byte varint', () => {
|
|
|
|
|
expect(readVarInt(reader([0x01]))).toBe(1)
|
|
|
|
|
expect(readVarInt(reader([0x7f]))).toBe(127)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('read multi-byte varint', () => {
|
|
|
|
|
// 129 as varint (0b10000001 00000001)
|
|
|
|
|
expect(readVarInt(reader([0x81, 0x01]))).toBe(129)
|
|
|
|
|
// 16515 as varint (0b10000011 10000010 00000001)
|
|
|
|
|
expect(readVarInt(reader([0x83, 0x82, 0x01]))).toBe(16643)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('read maximum int32 varint', () => {
|
|
|
|
|
// 2147483647 as varint (0b11111111 11111111 11111111 11111111 00000111)
|
|
|
|
|
expect(readVarInt(reader([0xff, 0xff, 0xff, 0xff, 0x07]))).toBe(2147483647)
|
|
|
|
|
})
|
|
|
|
|
})
|
2025-03-20 19:37:24 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Convert int to varint. Outputs 1-5 bytes for int32.
|
|
|
|
|
*
|
|
|
|
|
* @param {number} n
|
|
|
|
|
* @returns {number[]}
|
|
|
|
|
*/
|
|
|
|
|
function toVarInt(n) {
|
|
|
|
|
let idx = 0
|
|
|
|
|
const varInt = []
|
|
|
|
|
while (true) {
|
|
|
|
|
if ((n & ~0x7f) === 0) {
|
|
|
|
|
varInt[idx++] = n
|
|
|
|
|
break
|
|
|
|
|
} else {
|
|
|
|
|
varInt[idx++] = n & 0x7f | 0x80
|
|
|
|
|
n >>>= 7
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return varInt
|
|
|
|
|
}
|