-
-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #499 from streamich/resp-improvements
Adds RESP v2 support
- Loading branch information
Showing
6 changed files
with
195 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import {RESP} from './constants'; | ||
import {RespAttributes, RespPush, RespVerbatimString} from './extensions'; | ||
import {JsonPackExtension} from '../JsonPackExtension'; | ||
import {RespEncoder} from './RespEncoder'; | ||
import type {IWriter, IWriterGrowable} from '../../util/buffers'; | ||
|
||
const REG_RN = /[\r\n]/; | ||
const isSafeInteger = Number.isSafeInteger; | ||
|
||
/** | ||
* Implements RESP v2 encoding. | ||
*/ | ||
export class RespEncoderLegacy<W extends IWriter & IWriterGrowable = IWriter & IWriterGrowable> extends RespEncoder<W> { | ||
public writeAny(value: unknown): void { | ||
switch (typeof value) { | ||
case 'number': | ||
return this.writeNumber(value as number); | ||
case 'string': | ||
return this.writeStr(value); | ||
case 'boolean': | ||
return this.writeSimpleStr(value ? 'TRUE' : 'FALSE'); | ||
case 'object': { | ||
if (!value) return this.writeNull(); | ||
if (value instanceof Array) return this.writeArr(value); | ||
if (value instanceof Uint8Array) return this.writeBin(value); | ||
if (value instanceof Error) return this.writeErr(value.message); | ||
if (value instanceof Set) return this.writeSet(value); | ||
if (value instanceof JsonPackExtension) { | ||
if (value instanceof RespPush) return this.writeArr(value.val); | ||
if (value instanceof RespVerbatimString) return this.writeStr(value.val); | ||
if (value instanceof RespAttributes) return this.writeObj(value.val); | ||
} | ||
return this.writeObj(value as Record<string, unknown>); | ||
} | ||
case 'undefined': | ||
return this.writeUndef(); | ||
case 'bigint': | ||
return this.writeSimpleStrAscii(value + ''); | ||
default: | ||
return this.writeUnknown(value); | ||
} | ||
} | ||
|
||
public writeNumber(num: number): void { | ||
if (isSafeInteger(num)) this.writeInteger(num); | ||
else this.writeSimpleStrAscii(num + ''); | ||
} | ||
|
||
public writeStr(str: string): void { | ||
const length = str.length; | ||
if (length < 64 && !REG_RN.test(str)) this.writeSimpleStr(str); | ||
else this.writeBulkStr(str); | ||
} | ||
|
||
public writeNull(): void { | ||
this.writeNullArr(); | ||
} | ||
|
||
public writeErr(str: string): void { | ||
if (str.length < 64 && !REG_RN.test(str)) this.writeSimpleErr(str); | ||
else this.writeBulkStr(str); | ||
} | ||
|
||
public writeSet(set: Set<unknown>): void { | ||
this.writeArr([...set]); | ||
} | ||
|
||
public writeArr(arr: unknown[]): void { | ||
const writer = this.writer; | ||
const length = arr.length; | ||
writer.u8(RESP.ARR); // * | ||
this.writeLength(length); | ||
writer.u16(RESP.RN); // \r\n | ||
for (let i = 0; i < length; i++) { | ||
const val = arr[i]; | ||
if (val === null) this.writeNullStr(); | ||
else this.writeAny(val); | ||
} | ||
} | ||
|
||
public writeObj(obj: Record<string, unknown>): void { | ||
const writer = this.writer; | ||
const keys = Object.keys(obj); | ||
const length = keys.length; | ||
writer.u8(RESP.ARR); // % | ||
this.writeLength(length << 1); | ||
writer.u16(RESP.RN); // \r\n | ||
for (let i = 0; i < length; i++) { | ||
const key = keys[i]; | ||
this.writeStr(key); | ||
const val = obj[key]; | ||
if (val === null) this.writeNullStr(); | ||
else this.writeAny(val); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import {RespEncoderLegacy} from '../RespEncoderLegacy'; | ||
|
||
const encode = (value: unknown): string => { | ||
const encoder = new RespEncoderLegacy(); | ||
const encoded = encoder.encode(value); | ||
return Buffer.from(encoded).toString(); | ||
}; | ||
|
||
test('can encode simple strings', () => { | ||
expect(encode('')).toBe('+\r\n'); | ||
expect(encode('asdf')).toBe('+asdf\r\n'); | ||
}); | ||
|
||
test('can encode simple errors', () => { | ||
expect(encode(new Error('asdf'))).toBe('-asdf\r\n'); | ||
}); | ||
|
||
test('can encode integers', () => { | ||
expect(encode(0)).toBe(':0\r\n'); | ||
expect(encode(123)).toBe(':123\r\n'); | ||
expect(encode(-422469777)).toBe(':-422469777\r\n'); | ||
}); | ||
|
||
test('can encode bulk strings', () => { | ||
expect(encode('ab\nc')).toBe('$4\r\nab\nc\r\n'); | ||
expect(encode(new Uint8Array([65]))).toBe('$1\r\nA\r\n'); | ||
}); | ||
|
||
test('can encode arrays', () => { | ||
expect(encode(['a', 1])).toBe('*2\r\n+a\r\n:1\r\n'); | ||
}); | ||
|
||
test('encodes null as nullable array', () => { | ||
expect(encode(null)).toBe('*-1\r\n'); | ||
}); | ||
|
||
test('encodes null in nested structure as nullable string', () => { | ||
expect(encode(['a', 'b', null])).toBe('*3\r\n+a\r\n+b\r\n$-1\r\n'); | ||
}); | ||
|
||
test('encodes booleans as strings', () => { | ||
expect(encode(true)).toBe('+TRUE\r\n'); | ||
expect(encode(false)).toBe('+FALSE\r\n'); | ||
}); | ||
|
||
test('encodes floats as strings', () => { | ||
expect(encode(1.23)).toBe('+1.23\r\n'); | ||
}); | ||
|
||
test('encodes objects as 2-tuple arrays', () => { | ||
expect(encode({foo: 'bar'})).toBe('*2\r\n+foo\r\n+bar\r\n'); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
export * from './constants'; | ||
export * from './extensions'; | ||
export * from './RespEncoder'; | ||
export * from './RespEncoderLegacy'; | ||
export * from './RespDecoder'; | ||
export * from './RespStreamingDecoder'; |