Skip to content

Commit 0bcf449

Browse files
authored
Enrich API request error handling (#51)
* Use custom error for api calls to expose response object and content to end user * Use error cause for json parse failure * Check for and throw if content-type is not json
1 parent 6a728ec commit 0bcf449

1 file changed

Lines changed: 24 additions & 6 deletions

File tree

src/api-request.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { parse, stringify } from 'node:querystring';
33
import { request, RequestOptions } from 'node:https';
44

55
import { LastFMParams, LastFMUnknownFunction, OptionalConfig } from './types.js';
6+
import { IncomingMessage } from 'node:http';
67

78
export class LastFMApiRequest<T> {
89
public config: OptionalConfig;
@@ -72,13 +73,13 @@ export class LastFMApiRequest<T> {
7273
const paramsStr = stringify(paramsObj);
7374
const options = this.getOptions(method, paramsStr);
7475

75-
const LastFMapiRequest = new Promise<string>((resolve, reject) => {
76+
const LastFMapiRequest = new Promise<[IncomingMessage, string]>((resolve, reject) => {
7677
const httpRequest = request(options, httpResponse => {
7778
let data = '';
7879

7980
httpResponse.setEncoding('utf8');
8081
httpResponse.on('data', chunk => (data += chunk));
81-
httpResponse.on('end', () => resolve(data));
82+
httpResponse.on('end', () => resolve([httpResponse, data]));
8283
httpResponse.on('error', err => reject(err));
8384
});
8485

@@ -89,13 +90,17 @@ export class LastFMApiRequest<T> {
8990
}
9091

9192
httpRequest.end();
92-
}).then((response: string) => {
93+
}).then(([response, content]) => {
94+
if(response.headers['content-type'] !== 'application/json') {
95+
throw new LastFMResponseError(`lastfm-ts-api: Expected JSON response but received '${response.headers['content-type']}'`, { response, content });
96+
}
97+
9398
let data;
9499

95100
try {
96-
data = JSON.parse(response);
101+
data = JSON.parse(content);
97102
} catch (err) {
98-
throw new Error(`lastfm-ts-api: Unable to parse LastFM API response to JSON. API response is ${err}`);
103+
throw new LastFMResponseError(`lastfm-ts-api: Unable to parse LastFM API response to JSON.`, { cause: err, response, content });
99104
}
100105

101106
if (data?.error) {
@@ -109,7 +114,7 @@ export class LastFMApiRequest<T> {
109114
msg = data.message;
110115
}
111116

112-
throw new Error(`lastfm-ts-api: ${msg}`);
117+
throw new LastFMResponseError(`lastfm-ts-api: ${msg}`, { response, content });
113118
}
114119

115120
return data;
@@ -178,4 +183,17 @@ export class LastFMApiRequest<T> {
178183
}
179184
}
180185

186+
export class LastFMResponseError extends Error {
187+
public response?: IncomingMessage;
188+
public content?: string;
189+
190+
public constructor(message: string, options?: ErrorOptions & { response?: IncomingMessage, content?: string }) {
191+
super(message, options);
192+
this.name = 'LastFMResponseError';
193+
const { content, response } = options ?? {};
194+
this.response = response;
195+
this.content = content;
196+
}
197+
}
198+
181199
export default LastFMApiRequest;

0 commit comments

Comments
 (0)