Skip to content

Commit 2685aa6

Browse files
committed
feat: added getJSON() method
1 parent f212cc5 commit 2685aa6

6 files changed

Lines changed: 81 additions & 15 deletions

File tree

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ Please refer to the [compatibility](#4-compatibility) section for known issues a
8282

8383
## 2. Usage
8484

85+
> [!TIP]
86+
> Starting from `sync-request-curl@3.2.0`, you can replace `JSON.parse(res.body.toString())` with `res.getJSON()`.
87+
8588
Try with [Replit](https://replit.com/@nktnet1/sync-request-curl-example#index.js).
8689

8790
```typescript
@@ -140,6 +143,7 @@ const res = request(
140143
},
141144
}
142145
);
146+
143147
console.log('Status Code:', res.statusCode);
144148
const jsonBody = JSON.parse(res.body.toString());
145149
console.log('Returned JSON Object:', jsonBody);
@@ -334,19 +338,21 @@ export interface Options {
334338

335339
- **`statusCode`** - a number representing the HTTP status code (e.g. `200`, `400`, `401`, `403`)
336340
- **`headers`** - HTTP response headers. The keys/properties of the object will always be in lowercase, e.g. `"content-type"` instead of `"Content-Type"`
341+
- **`url`** - the final URL used in the request after all redirections are followed and all query string parameters appended
337342
- **`body`** - a string or buffer - for JSON responses, use `JSON.parse(response.body.toString())` to get the returned data as an object
338343
- **`getBody`** - a function with an optional `encoding` argument that returns the `body` if `encoding` is undefined, otherwise `body.toString(encoding)`. If `statusCode >= 300`, an `Error` is thrown instead
339-
- **`url`** - the final URL used in the request after all redirections are followed, and with the query string parameters appended
344+
- **`getJSON`** - a function that returns the body as `JSON`. an `Error` is thrown if the body cannot be parsed.
340345

341346
In [src/types.ts](src/types.ts), the `Response` interface is defined as:
342347

343348
```typescript
344349
export interface Response {
345350
statusCode: number;
346351
headers: IncomingHttpHeaders;
352+
url: string;
347353
body: string | Buffer;
348354
getBody: (encoding?: BufferEncoding) => string | Buffer; // simplified
349-
url: string;
355+
getJSON: <T = any>(encoding?: BufferEncoding) => T
350356
}
351357
```
352358

src/request.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Curl, Easy } from 'node-libcurl';
2-
import { HttpVerb, Options, Response, GetBody } from './types';
2+
import { HttpVerb, Options, Response, GetBody, GetJSON } from './types';
33
import {
4-
checkGetBodyStatus,
4+
checkValidStatusCode,
55
checkValidCurlCode,
66
handleQs,
77
parseReturnedHeaders,
@@ -165,14 +165,39 @@ const request = (method: HttpVerb, url: string, options: Options = {}): Response
165165
* @returns {Buffer | string} buffer body by default, string body with encoding
166166
*/
167167
const getBody: GetBody = (encoding?) => {
168-
checkGetBodyStatus(statusCode, body);
168+
checkValidStatusCode(statusCode, body);
169169
return typeof encoding === 'string' ? body.toString(encoding) as any : body;
170170
};
171171

172+
/**
173+
* Get the JSON-parsed body of a response.
174+
*
175+
* @throws {Error} if the body is nto a valid JSON
176+
* @returns {any} parsed JSON body
177+
*/
178+
const getJSON: GetJSON = (encoding?) => {
179+
try {
180+
return JSON.parse(body.toString(encoding));
181+
} catch (err) {
182+
throw new Error(`
183+
The server body response for
184+
- ${method}
185+
- ${url}
186+
cannot be parsed as JSON.
187+
188+
Body:
189+
${body.toString(encoding)}
190+
191+
JSON-Parsing Error Message:
192+
${err instanceof Error ? err.message : String(err)}
193+
`);
194+
}
195+
};
196+
172197
url = curl.getInfo('EFFECTIVE_URL').data as string;
173198

174199
curl.close();
175-
return { statusCode, headers, body, getBody, url };
200+
return { statusCode, headers, url, body, getBody, getJSON };
176201
};
177202

178203
export default request;

src/types.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,16 @@ export interface Options {
4848
}
4949

5050
// Infer type `string` if encoding is specified, otherwise `string | Buffer
51-
export type GetBody = <encoding extends BufferEncoding | undefined>(arg?: encoding)
52-
=> encoding extends BufferEncoding ? string : Buffer;
51+
export type GetBody = <Encoding extends BufferEncoding | undefined>(arg?: Encoding)
52+
=> Encoding extends BufferEncoding ? string : Buffer;
53+
54+
export type GetJSON = <T = any>(encoding?: BufferEncoding) => T;
5355

5456
export interface Response {
5557
statusCode: number;
5658
headers: IncomingHttpHeaders;
59+
url: string;
5760
body: string | Buffer;
5861
getBody: GetBody;
59-
url: string;
62+
getJSON: GetJSON;
6063
}

src/utils.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,16 +99,19 @@ export const checkValidCurlCode = (code: CurlCode, requestInputs: RequestInputs)
9999
* @param {Buffer} body - The body of the HTTP response.
100100
* @throws {Error} if the status code is >= 300.
101101
*/
102-
export const checkGetBodyStatus = (statusCode: number, body: Buffer) => {
102+
export const checkValidStatusCode = (statusCode: number, body: Buffer) => {
103103
if (statusCode >= 300) {
104104
throw new Error(`
105-
Server responded with status code ${statusCode}
105+
Server responded with status code
106+
${statusCode}
106107
107-
Body: ${body.toString()}
108+
Body:
109+
${body.toString()}
108110
109-
Use 'res.body' instead of 'res.getBody()' to not have any errors thrown.
110-
The status code (in this case, ${statusCode}) can be checked manually
111-
with res.statusCode.
111+
Use 'res.body' instead of 'res.getBody()' to not have any errors thrown.
112+
113+
The status code (in this case, ${statusCode}) can be checked manually
114+
with res.statusCode.
112115
`);
113116
}
114117
};

tests/app/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ app.post('/timeout', async (c) => {
6363
return c.json({});
6464
});
6565

66+
app.post('/text', async (c) => {
67+
return c.text('Hello world!');
68+
});
69+
6670
app.onError((err, c) => {
6771
const status = err instanceof HTTPException ? err.status : 500;
6872
const message = err instanceof HTTPException ? err.message : 'Internal Server Error';

tests/request.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,3 +265,28 @@ describe('Redirects', () => {
265265
expect(noRedirect).toMatchObject({ code: 302 });
266266
});
267267
});
268+
269+
// ========================================================================= //
270+
271+
describe('v3.2.0 getJSON method', () => {
272+
test('Valid JSON body', () => {
273+
const value = {
274+
1: 'one',
275+
two: 2,
276+
three: true,
277+
four: null,
278+
nested: {
279+
five: false,
280+
array: [{ key: 'value' }, { key2: 'value2' }]
281+
}
282+
};
283+
const res = request('POST', `${SERVER_URL}/post`, { json: { value } });
284+
const jsonBody = res.getJSON();
285+
expect(jsonBody).toStrictEqual({ value });
286+
});
287+
288+
test('Invalid JSON body', () => {
289+
const res = request('POST', `${SERVER_URL}/text`);
290+
expect(() => res.getJSON()).toThrow(Error);
291+
});
292+
});

0 commit comments

Comments
 (0)