Skip to content

Commit

Permalink
Merge pull request #498 from streamich/resp-command
Browse files Browse the repository at this point in the history
RESP command decoding
  • Loading branch information
streamich authored Dec 18, 2023
2 parents 948c6cf + 4e7c44b commit 71b72a4
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 1 deletion.
19 changes: 19 additions & 0 deletions src/json-pack/resp/RespDecoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,25 @@ export class RespDecoder<R extends IReader & IReaderResettable = IReader & IRead
}
}

public readCmd(): [cmd: string, ...args: Uint8Array[]] {
const reader = this.reader;
const type = reader.u8();
if (type !== RESP.ARR) throw new Error('INVALID_COMMAND');
const c = reader.peak();
if (c === RESP.MINUS) throw new Error('INVALID_COMMAND');
const length = this.readLength();
if (length === 0) throw new Error('INVALID_COMMAND');
const cmd = this.readAsciiAsStrBulk().toUpperCase();
const args: [cmd: string, ...args: Uint8Array[]] = [cmd];
this.tryUtf8 = false;
for (let i = 1; i < length; i++) {
const type = reader.u8();
if (type !== RESP.STR_BULK) throw new Error('INVALID_COMMAND');
args.push(this.readStrBulk() as Uint8Array);
}
return args;
}

// ---------------------------------------------------------- Boolean reading

public readBool(): boolean {
Expand Down
23 changes: 23 additions & 0 deletions src/json-pack/resp/RespStreamingDecoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,29 @@ export class RespStreamingDecoder {
}
}

/**
* Decode only one RESP command from the stream, if the value is not a
* command, an error will be thrown.
*
* @returns Redis command and its arguments or `undefined` if there is
* not enough data to decode.
*/
public readCmd(): [cmd: string, ...args: Uint8Array[]] | undefined {
const reader = this.reader;
if (reader.size() === 0) return undefined;
const x = reader.x;
try {
const args = this.decoder.readCmd();
reader.consume();
return args;
} catch (error) {
if (error instanceof RangeError) {
reader.x = x;
return undefined;
} else throw error;
}
}

/**
* Skips one value from the stream. If `undefined` is returned, then
* there is not enough data to skip or the stream is finished.
Expand Down
20 changes: 19 additions & 1 deletion src/json-pack/resp/__tests__/RespDecoder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {RespDecoder} from '../RespDecoder';
import {bufferToUint8Array} from '../../../util/buffers/bufferToUint8Array';
import {RespAttributes, RespPush} from '../extensions';
import {Writer} from '../../../util/buffers/Writer';
import {Uint} from '@automerge/automerge';
import {utf8} from '../../../util/buffers/strings';

const decode = (encoded: string | Uint8Array): unknown => {
const decoder = new RespDecoder();
Expand Down Expand Up @@ -215,3 +215,21 @@ describe('nulls', () => {
expect(decoded).toBe(null);
});
});

describe('commands', () => {
test('can decode a PING command', () => {
const encoded = encoder.encodeCmd(['PING']);
const decoder = new RespDecoder();
decoder.reader.reset(encoded);
const decoded = decoder.readCmd();
expect(decoded).toEqual(['PING']);
});

test('can decode a SET command', () => {
const encoded = encoder.encodeCmd(['SET', 'foo', 'bar']);
const decoder = new RespDecoder();
decoder.reader.reset(encoded);
const decoded = decoder.readCmd();
expect(decoded).toEqual(['SET', utf8`foo`, utf8`bar`]);
});
});
9 changes: 9 additions & 0 deletions src/json-pack/resp/__tests__/RespStreamingDecoder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {RespStreamingDecoder} from '../RespStreamingDecoder';
import {RespEncoder} from '../RespEncoder';
import {concatList} from '../../../util/buffers/concat';
import {documents} from '../../../__tests__/json-documents';
import {utf8} from '../../../util/buffers/strings';

const encoder = new RespEncoder();

Expand Down Expand Up @@ -66,3 +67,11 @@ test('can stream 49 bytes at a time', () => {
}
expect(decoded).toEqual(docs);
});

test('can decode a command', () => {
const encoded = encoder.encodeCmd(['SET', 'foo', 'bar']);
const decoder = new RespStreamingDecoder();
decoder.push(encoded);
const decoded = decoder.readCmd();
expect(decoded).toEqual(['SET', utf8`foo`, utf8`bar`]);
});
16 changes: 16 additions & 0 deletions src/util/buffers/strings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {bufferToUint8Array} from './bufferToUint8Array';

export const ascii = (txt: TemplateStringsArray | string | [string]): Uint8Array => {
if (typeof txt === 'string') return ascii([txt]);
[txt] = txt;
const len = txt.length;
const res = new Uint8Array(len);
for (let i = 0; i < len; i++) res[i] = txt.charCodeAt(i);
return res;
};

export const utf8 = (txt: TemplateStringsArray | [string] | string): Uint8Array => {
if (typeof txt === 'string') return utf8([txt]);
[txt] = txt;
return bufferToUint8Array(Buffer.from(txt, 'utf8'));
};

0 comments on commit 71b72a4

Please sign in to comment.