From 75fd6cb5f1357d00105c83b8afe76c91ee48aea8 Mon Sep 17 00:00:00 2001 From: Darius J Chuck Date: Tue, 27 Aug 2024 22:18:47 +0200 Subject: [PATCH] Document error handling behavior of JsonHigh. Add a helper to check for errors (isError). Add test for error handling. --- JsonLow.d.ts | 1 + JsonLow.js | 3 +++ README.md | 49 ++++++++++++++++++++++++++++++++++++++++------ test/error.test.js | 30 ++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 6 deletions(-) create mode 100644 test/error.test.js diff --git a/JsonLow.d.ts b/JsonLow.d.ts index 22dbf8d..6f94dd7 100644 --- a/JsonLow.d.ts +++ b/JsonLow.d.ts @@ -59,6 +59,7 @@ export interface JsonUnexpectedEndFeedback { export type JsonStandardFeedback = JsonErrorFeedback | JsonUnexpectedFeedback; export type JsonStandardEnd = JsonErrorFeedback | JsonUnexpectedEndFeedback; export declare const error: (message: string) => JsonErrorFeedback +export declare const isError: (message: unknown) => boolean export declare const unexpected: (code: number, context: string, expected: Array) => JsonUnexpectedFeedback export declare const unexpectedEnd: (context?: string, expected?: Array) => JsonUnexpectedEndFeedback export declare const isZeroNine: (code: number) => boolean diff --git a/JsonLow.js b/JsonLow.js index 030fb59..3853281 100644 --- a/JsonLow.js +++ b/JsonLow.js @@ -52,6 +52,9 @@ export const error = (message) => { message, } } +export const isError = (value) => { + return value !== null && typeof value === 'object' && value.type === JsonFeedbackType.error +} export const unexpected = (code, context, expected) => { return { type: JsonFeedbackType.error, diff --git a/README.md b/README.md index c0a2d8e..f29819b 100644 --- a/README.md +++ b/README.md @@ -154,12 +154,6 @@ See [**JsonHigh.d.ts**](JsonHigh.d.ts) for type information and [Quickstart](#qu * `chunk` which accepts a JSON chunk to parse. It returns the stream object for chaining. * `end` with no arguments which signals that the current JSON document is finished. If there is no error, it calls the corresponding `end` event handler, passing its return value to the caller. -### Error handling - -If there is an error when parsing a `chunk`, an exception is thrown. - -If there is an error at the `end`, that error is returned to the caller. The user-provided `end` event handler is not called. - ### Events There are 4 event handlers without arguments which indicate start and end of structures: @@ -176,6 +170,49 @@ And 2 event handlers with one argument which capture primitives: Finally, there is the argumentless `end` event handler which is called by the `end` method of the stream to confirm that the parsed JSON document is complete and valid. +Note that an event handler won't be called if there is an error in the parsed JSON, see [error handling](#error-handling). + +### Error handling + +If there is an error when parsing a `chunk`, an `Error` is thrown, containing a serialized JSON object with details in the error message. + +If there is an error at the `end`, that error is returned to the caller. The user-provided `end` event handler is not called, so it should not contain any [cleanup](#cleanup) code. + +### Cleanup + +To run cleanup code at the end of parsing a document regardless of whether there was an error or not, **don't put that code in the end handler**. Instead put it after `.end()`, like so: + +```js +// ... +stream.end() +cleanup() +``` + +If you want to also handle an error, you can use the `isError` helper: + +```js +import {isError} from '@xtao-org/jsonhilo' + +// ... + +const ret = stream.end() +if (isError(ret)) { handle(ret) } // handle error +cleanup() +``` + +If your error handler can throw, you can use `try-catch-finally`: + +```js +import {isError} from '@xtao-org/jsonhilo' + +// ... + +const ret = stream.end() +try { if (isError(ret)) { handle(ret) } } +catch (e) { /* optional */ } +finally { cleanup() } +``` + ## Fast Achieving optimal performance without sacrificing simplicity and correctness was a design goal of JsonHilo. This goal was realized and for applications without extreme performance requirements JsonHilo should be more than fast enough. diff --git a/test/error.test.js b/test/error.test.js new file mode 100644 index 0000000..37c792d --- /dev/null +++ b/test/error.test.js @@ -0,0 +1,30 @@ +import {isError, JsonHigh} from '../mod.js' + +Deno.test('error handling', async () => { + // based on a user-provided example from https://github.com/xtao-org/jsonhilo/issues/6 + const handlers = { + end: () => { + throw Error('Expected end handler not to be called!') + }, + } + const stream = JsonHigh(handlers) + const writable = new WritableStream({ + write: (chunk) => { + const decoded = new TextDecoder().decode(chunk) + stream.chunk(decoded) + }, + close: () => { + const ret = stream.end() + if (isError(ret) === false) { + throw Error('Expected error to be returned by stream.end()!') + } + }, + }) + const readable = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode('{"a":2,"b":3')) + controller.close() + }, + }) + await readable.pipeTo(writable) +}) \ No newline at end of file