Skip to content

Commit

Permalink
Fix check for iterabile, improve coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
lahmatiy committed Jun 18, 2024
1 parent 9ebee3a commit 13baf44
Show file tree
Hide file tree
Showing 6 changed files with 38 additions and 22 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ jobs:
strategy:
matrix:
node_version:
- 14.17
- 18.0
- 20
steps:
- uses: actions/checkout@v2
Expand Down Expand Up @@ -78,6 +80,7 @@ jobs:
node_version:
- 14.17.0
- 16
- 18.0
- 18
- 20
- 22
Expand Down
14 changes: 5 additions & 9 deletions src/parse-chunked.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { isIterable } from './utils.js';

const STACK_OBJECT = 1;
const STACK_ARRAY = 2;
const decoder = new TextDecoder();

function isObject(value) {
return value !== null && typeof value === 'object';
}

function adjustPosition(error, parser) {
if (error.name === 'SyntaxError' && parser.jsonParseOffset) {
error.message = error.message.replace(/at position (\d+)/, (_, pos) =>
Expand All @@ -28,15 +26,15 @@ function append(array, elements) {
}

export async function parseChunked(chunkEmitter) {
const iterator = typeof chunkEmitter === 'function'
const iterable = typeof chunkEmitter === 'function'
? chunkEmitter()
: chunkEmitter;

if (isObject(iterator) && (Symbol.iterator in iterator || Symbol.asyncIterator in iterator)) {
if (isIterable(iterable)) {
let parser = new ChunkParser();

try {
for await (const chunk of iterator) {
for await (const chunk of iterable) {
if (typeof chunk !== 'string' && !ArrayBuffer.isView(chunk)) {
throw new TypeError('Invalid chunk: Expected string, TypedArray or Buffer');
}
Expand All @@ -47,8 +45,6 @@ export async function parseChunked(chunkEmitter) {
return parser.finish();
} catch (e) {
throw adjustPosition(e, parser);
} finally {
parser = null;
}
}

Expand Down
23 changes: 14 additions & 9 deletions src/utils.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
function replaceValue(holder, key, value, replacer) {
export function isIterable(value) {
return (
typeof value === 'object' &&
value !== null &&
(
typeof value[Symbol.iterator] === 'function' ||
typeof value[Symbol.asyncIterator] === 'function'
)
);
}

export function replaceValue(holder, key, value, replacer) {
if (value && typeof value.toJSON === 'function') {
value = value.toJSON();
}
Expand Down Expand Up @@ -26,7 +37,7 @@ function replaceValue(holder, key, value, replacer) {
return value;
}

function normalizeReplacer(replacer) {
export function normalizeReplacer(replacer) {
if (typeof replacer === 'function') {
return replacer;
}
Expand All @@ -46,7 +57,7 @@ function normalizeReplacer(replacer) {
return null;
}

function normalizeSpace(space) {
export function normalizeSpace(space) {
if (typeof space === 'number') {
if (!Number.isFinite(space) || space < 1) {
return false;
Expand All @@ -61,9 +72,3 @@ function normalizeSpace(space) {

return false;
}

export {
replaceValue,
normalizeReplacer,
normalizeSpace
};
7 changes: 4 additions & 3 deletions src/web-streams.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
/* eslint-env browser */
import { parseChunked } from './parse-chunked.js';
import { stringifyChunked } from './stringify-chunked.js';
import { isIterable } from './utils.js';

export function parseFromWebStream(stream) {
// 2024/6/17: currently, an @@asyncIterator on a ReadableStream is not widely supported,
// therefore use a fallback using a reader
// https://caniuse.com/mdn-api_readablestream_--asynciterator
return parseChunked(stream && Symbol.asyncIterator in stream ? stream : async function*() {
return parseChunked(isIterable(stream) ? stream : async function*() {
const reader = stream.getReader();

while (true) {
const { done, chunk } = await reader.read();
const { value, done } = await reader.read();

if (done) {
break;
}

yield chunk;
yield value;
}
});
}
Expand Down
4 changes: 3 additions & 1 deletion test/parse-chunked.js
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,9 @@ describe('parseChunked()', () => {
() => ['[1, 2,', 3, ']'],
() => 123,
() => new Uint8Array([1, 2, 3]),
{ on() {} }
{ on() {} },
{ [Symbol.iterator]: null },
{ [Symbol.asyncIterator]: null }
];

for (const value of badValues) {
Expand Down
9 changes: 9 additions & 0 deletions test/web-streams.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ describeIfSupported('parseFromWebStream()', () => {

assert.deepStrictEqual(actual, { foo: 123 });
});

it('should parse ReadableStream with no @@asyncIterator', async () => {
const nonIterableReadableStream = Object.assign(createReadableStream(['{"foo', '":123', '}']), {
[Symbol.asyncIterator]: null
});
const actual = await parseFromWebStream(nonIterableReadableStream);

assert.deepStrictEqual(actual, { foo: 123 });
});
});

describeIfSupported('createStringifyWebStream()', () => {
Expand Down

0 comments on commit 13baf44

Please sign in to comment.