Skip to content

Commit 203b667

Browse files
authored
Merge pull request #16 from Applura/problem-details
convert: every error to a problem details object
2 parents 00e80af + 171f75f commit 203b667

File tree

4 files changed

+50
-46
lines changed

4 files changed

+50
-46
lines changed

index.js

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,3 @@
11
import Client, { bootstrap } from "./src/client.js";
2-
import {
3-
ImplementationError,
4-
LibraryError,
5-
MissingContentTypeError,
6-
RequestError,
7-
ResponseError,
8-
ServerError,
9-
UnexpectedContentError,
10-
UnexpectedContentTypeError,
11-
UnprocessableResponseError,
12-
UsageError,
13-
} from "./src/errors.js";
14-
152
export default Client;
163
export { bootstrap };
17-
export {
18-
ImplementationError,
19-
LibraryError,
20-
MissingContentTypeError,
21-
RequestError,
22-
ResponseError,
23-
ServerError,
24-
UnexpectedContentError,
25-
UnexpectedContentTypeError,
26-
UnprocessableResponseError,
27-
UsageError,
28-
};

src/client.js

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
ImplementationError,
3+
LibraryError,
34
MissingContentTypeError,
45
RequestError,
56
ServerError,
@@ -122,7 +123,7 @@ export default function Client(initialURL) {
122123
return;
123124
}
124125
lastResource = resource || lastResource;
125-
lastProblem = problem;
126+
lastProblem = problem instanceof LibraryError ? problem.detail : problem;
126127
lastURL = url || lastURL;
127128
send();
128129
};
@@ -200,7 +201,6 @@ export default function Client(initialURL) {
200201
mimeType.startsWith("application/problem+json"))
201202
) {
202203
const problem = new UnexpectedContentTypeError(
203-
response,
204204
`the server responded in with an unrecognizable media type: ${mimeType}`,
205205
{ response },
206206
);
@@ -251,25 +251,31 @@ export default function Client(initialURL) {
251251
update(id, { resource, url });
252252
return true;
253253
}
254-
const errorDetails = (doc.errors || []).filter((e) => e.detail).map((e) =>
255-
`detail: ${e.detail}`
254+
console.assert(
255+
mimeType.startsWith("application/vnd.api+json") ||
256+
mimeType.startsWith("application/problem+json"),
256257
);
258+
const errorDetail = mimeType.startsWith("application/problem+json")
259+
? doc.detail
260+
: (doc.errors || []).filter((e) => e.detail).map((e) =>
261+
`detail: ${e.detail}`
262+
).join(", ");
257263
if (response.status >= 400 && response.status <= 499) {
258264
const problem = new RequestError(
259265
[
260266
"request error",
261267
`${response.status} ${response.statusText}`,
262-
...errorDetails,
268+
errorDetail,
263269
].join(": "),
264-
{ doc, response },
270+
{ response },
265271
);
266272
update(id, { problem, url });
267273
} else if (response.status >= 500 && response.status <= 599) {
268274
const problem = new ServerError(response, [
269275
"response error",
270276
`${response.status} ${response.statusText}`,
271-
...errorDetails,
272-
], { doc, response });
277+
errorDetail,
278+
], { response });
273279
update(id, { problem, url });
274280
} else {
275281
throw new ImplementationError(

src/client_test.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@ import Client, { isLocalURL } from "./client.js";
22
import {
33
assert,
44
assertEquals,
5-
assertInstanceOf,
65
} from "https://deno.land/std@0.185.0/testing/asserts.ts";
76
import TestServer from "./internal/testing/server.js";
8-
import { UnexpectedContentTypeError } from "./errors.js";
97

108
Deno.test("Client", async (t) => {
119
const serverOptions = { hostname: "0.0.0.0", port: 3003 };
@@ -135,7 +133,10 @@ Deno.test("Client", async (t) => {
135133
let { status } = client.response();
136134
assertEquals(status, 200);
137135
assertEquals(resource, undefined);
138-
assertInstanceOf(problem, UnexpectedContentTypeError);
136+
assertEquals(
137+
problem.type,
138+
"https://docs.applura.com/client/v2/errors#UnexpectedContentTypeError",
139+
);
139140
// Get a good response.
140141
server.respondWith(
141142
new Response(
@@ -163,7 +164,10 @@ Deno.test("Client", async (t) => {
163164
({ status } = client.response());
164165
assertEquals(status, 200);
165166
assertEquals(resource.id, "200 resource");
166-
assertInstanceOf(problem, UnexpectedContentTypeError);
167+
assertEquals(
168+
problem.type,
169+
"https://docs.applura.com/client/v2/errors#UnexpectedContentTypeError",
170+
);
167171
client.stop();
168172
});
169173
});

src/errors.js

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,18 @@ export class LibraryError extends Error {
44
super(message, options);
55
this.name = "LibraryError";
66
}
7+
get detail() {
8+
return {
9+
type: `https://docs.applura.com/client/v2/errors#${this.name}`,
10+
title: this.name,
11+
detail: this.message,
12+
};
13+
}
714
}
815

916
// Raised when the implementation of this library has caused an error. For example, when a known edge case has not been
1017
// handled.
11-
export class ImplementationError extends Error {
18+
export class ImplementationError extends LibraryError {
1219
constructor(message, options) {
1320
super(message, options);
1421
this.name = "ImplementationError";
@@ -23,22 +30,34 @@ export class UsageError extends LibraryError {
2330
}
2431
}
2532

26-
// Raised when an HTTP request causes an HTTP client error, i.e. for HTTP status codes >=300 and <=399.
27-
export class RequestError extends LibraryError {
28-
constructor(message, { doc, response, ...options }) {
33+
// Raised when an HTTP response is in error, i.e. for HTTP status codes >=400.
34+
export class HTTPError extends LibraryError {
35+
constructor(message, { response, ...options }) {
2936
super(message, options);
3037
this.name = "RequestError";
31-
Object.defineProperty(this, "doc", { value: doc });
3238
Object.defineProperty(this, "response", { value: response });
3339
}
40+
get detail() {
41+
return {
42+
...super.detail,
43+
status: this.response.status,
44+
};
45+
}
46+
}
47+
48+
// Raised when an HTTP request causes an HTTP client error, i.e. for HTTP status codes >=400 and <=499.
49+
export class RequestError extends HTTPError {
50+
constructor(message, options) {
51+
super(message, options);
52+
this.name = "RequestError";
53+
}
3454
}
3555

3656
// Raised when an HTTP response causes an error.
37-
export class ResponseError extends LibraryError {
38-
constructor(message, { doc, response, ...options }) {
39-
super(message, { ...options, doc: { value: doc } });
57+
export class ResponseError extends HTTPError {
58+
constructor(message, options) {
59+
super(message, options);
4060
this.name = "ResponseError";
41-
Object.defineProperty(this, "response", { value: response });
4261
}
4362
}
4463

0 commit comments

Comments
 (0)