Skip to content

Commit 71b72a4

Browse files
authored
Merge pull request #498 from streamich/resp-command
RESP command decoding
2 parents 948c6cf + 4e7c44b commit 71b72a4

File tree

5 files changed

+86
-1
lines changed

5 files changed

+86
-1
lines changed

src/json-pack/resp/RespDecoder.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,25 @@ export class RespDecoder<R extends IReader & IReaderResettable = IReader & IRead
8181
}
8282
}
8383

84+
public readCmd(): [cmd: string, ...args: Uint8Array[]] {
85+
const reader = this.reader;
86+
const type = reader.u8();
87+
if (type !== RESP.ARR) throw new Error('INVALID_COMMAND');
88+
const c = reader.peak();
89+
if (c === RESP.MINUS) throw new Error('INVALID_COMMAND');
90+
const length = this.readLength();
91+
if (length === 0) throw new Error('INVALID_COMMAND');
92+
const cmd = this.readAsciiAsStrBulk().toUpperCase();
93+
const args: [cmd: string, ...args: Uint8Array[]] = [cmd];
94+
this.tryUtf8 = false;
95+
for (let i = 1; i < length; i++) {
96+
const type = reader.u8();
97+
if (type !== RESP.STR_BULK) throw new Error('INVALID_COMMAND');
98+
args.push(this.readStrBulk() as Uint8Array);
99+
}
100+
return args;
101+
}
102+
84103
// ---------------------------------------------------------- Boolean reading
85104

86105
public readBool(): boolean {

src/json-pack/resp/RespStreamingDecoder.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,29 @@ export class RespStreamingDecoder {
6767
}
6868
}
6969

70+
/**
71+
* Decode only one RESP command from the stream, if the value is not a
72+
* command, an error will be thrown.
73+
*
74+
* @returns Redis command and its arguments or `undefined` if there is
75+
* not enough data to decode.
76+
*/
77+
public readCmd(): [cmd: string, ...args: Uint8Array[]] | undefined {
78+
const reader = this.reader;
79+
if (reader.size() === 0) return undefined;
80+
const x = reader.x;
81+
try {
82+
const args = this.decoder.readCmd();
83+
reader.consume();
84+
return args;
85+
} catch (error) {
86+
if (error instanceof RangeError) {
87+
reader.x = x;
88+
return undefined;
89+
} else throw error;
90+
}
91+
}
92+
7093
/**
7194
* Skips one value from the stream. If `undefined` is returned, then
7295
* there is not enough data to skip or the stream is finished.

src/json-pack/resp/__tests__/RespDecoder.spec.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {RespDecoder} from '../RespDecoder';
33
import {bufferToUint8Array} from '../../../util/buffers/bufferToUint8Array';
44
import {RespAttributes, RespPush} from '../extensions';
55
import {Writer} from '../../../util/buffers/Writer';
6-
import {Uint} from '@automerge/automerge';
6+
import {utf8} from '../../../util/buffers/strings';
77

88
const decode = (encoded: string | Uint8Array): unknown => {
99
const decoder = new RespDecoder();
@@ -215,3 +215,21 @@ describe('nulls', () => {
215215
expect(decoded).toBe(null);
216216
});
217217
});
218+
219+
describe('commands', () => {
220+
test('can decode a PING command', () => {
221+
const encoded = encoder.encodeCmd(['PING']);
222+
const decoder = new RespDecoder();
223+
decoder.reader.reset(encoded);
224+
const decoded = decoder.readCmd();
225+
expect(decoded).toEqual(['PING']);
226+
});
227+
228+
test('can decode a SET command', () => {
229+
const encoded = encoder.encodeCmd(['SET', 'foo', 'bar']);
230+
const decoder = new RespDecoder();
231+
decoder.reader.reset(encoded);
232+
const decoded = decoder.readCmd();
233+
expect(decoded).toEqual(['SET', utf8`foo`, utf8`bar`]);
234+
});
235+
});

src/json-pack/resp/__tests__/RespStreamingDecoder.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {RespStreamingDecoder} from '../RespStreamingDecoder';
22
import {RespEncoder} from '../RespEncoder';
33
import {concatList} from '../../../util/buffers/concat';
44
import {documents} from '../../../__tests__/json-documents';
5+
import {utf8} from '../../../util/buffers/strings';
56

67
const encoder = new RespEncoder();
78

@@ -66,3 +67,11 @@ test('can stream 49 bytes at a time', () => {
6667
}
6768
expect(decoded).toEqual(docs);
6869
});
70+
71+
test('can decode a command', () => {
72+
const encoded = encoder.encodeCmd(['SET', 'foo', 'bar']);
73+
const decoder = new RespStreamingDecoder();
74+
decoder.push(encoded);
75+
const decoded = decoder.readCmd();
76+
expect(decoded).toEqual(['SET', utf8`foo`, utf8`bar`]);
77+
});

src/util/buffers/strings.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import {bufferToUint8Array} from './bufferToUint8Array';
2+
3+
export const ascii = (txt: TemplateStringsArray | string | [string]): Uint8Array => {
4+
if (typeof txt === 'string') return ascii([txt]);
5+
[txt] = txt;
6+
const len = txt.length;
7+
const res = new Uint8Array(len);
8+
for (let i = 0; i < len; i++) res[i] = txt.charCodeAt(i);
9+
return res;
10+
};
11+
12+
export const utf8 = (txt: TemplateStringsArray | [string] | string): Uint8Array => {
13+
if (typeof txt === 'string') return utf8([txt]);
14+
[txt] = txt;
15+
return bufferToUint8Array(Buffer.from(txt, 'utf8'));
16+
};

0 commit comments

Comments
 (0)