Skip to content

Commit dce9435

Browse files
Merge pull request #3 from runtimed/feature/errors
Add ExtensionError to allow customizing the response error codes
2 parents dfdd3d6 + 4a35ddd commit dce9435

3 files changed

Lines changed: 100 additions & 2 deletions

File tree

README.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1-
# extensions
1+
# @runtimed/extensions
22

3-
Interface and shared code for building extensions for the runt ecosystem
3+
This package provides the common interfaces needed to plug into the Runtime Web framework and implement custom behavior
4+
5+
# Usage
6+
7+
```ts
8+
import type { BackendExtension } from '@runtimed/extensions';
9+
const extension: BackendExtension = {
10+
apiKey:...
11+
};
12+
export default extension;
13+
```
14+
15+
Once developed, see the documentation at [@runtimed/anode](https://github.com/runtimed/anode) for more information on how to import and use the extension
16+
17+
# Error handling
18+
19+
All extension functions should wrap and throw a standard error defined in [errors.ts](./src/errors.ts)
20+
That way, a proper status code and structured error will be returned to interested clients. All errors thrown by an extension not otherwise handled will become an Unknown (500) status code

src/errors.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
export enum ErrorType {
2+
Unknown = 'unknown',
3+
MissingAuthToken = 'missing_token',
4+
AuthTokenInvalid = 'invalid_token',
5+
AuthTokenWrongSignature = 'wrong_signature',
6+
AuthTokenExpired = 'expired_token',
7+
AccessDenied = 'access_denied',
8+
NotFound = 'not_found',
9+
ServerMisconfigured = 'server_misconfigured',
10+
InvalidRequest = 'invalid_request',
11+
CapabilityNotAvailable = 'capability_not_available',
12+
}
13+
type StatusCode = 200 | 201 | 204 | 400 | 401 | 403 | 404 | 500;
14+
15+
type ExtensionErrorOptions = {
16+
message?: string; // User-friendly message describing what happened
17+
responsePayload?: Record<string, unknown>; // Extra JSON data to be returned to the client
18+
debugPayload?: Record<string, unknown>; // Extra JSON data for debugging. Not returned to the client in production
19+
cause?: Error; // The underlying error, if any
20+
};
21+
22+
export type ErrorPayload = {
23+
type: ErrorType;
24+
message: string;
25+
data: Record<string, unknown>;
26+
debug?: {
27+
stack?: string;
28+
underlying?: {
29+
message: string;
30+
stack?: string;
31+
};
32+
} & Record<string, unknown>;
33+
};
34+
35+
export class RuntError extends Error {
36+
constructor(
37+
public type: ErrorType,
38+
private options: ExtensionErrorOptions
39+
) {
40+
super(options.message ?? `RuntError: ${type}`, { cause: options.cause });
41+
}
42+
43+
get statusCode(): StatusCode {
44+
const StatusCodeMapping: Record<ErrorType, StatusCode> = {
45+
[ErrorType.InvalidRequest]: 400,
46+
[ErrorType.CapabilityNotAvailable]: 400,
47+
[ErrorType.MissingAuthToken]: 401,
48+
[ErrorType.AuthTokenInvalid]: 401,
49+
[ErrorType.AuthTokenExpired]: 401,
50+
[ErrorType.AuthTokenWrongSignature]: 401,
51+
[ErrorType.AccessDenied]: 403,
52+
[ErrorType.NotFound]: 404,
53+
[ErrorType.ServerMisconfigured]: 500,
54+
[ErrorType.Unknown]: 500,
55+
};
56+
return StatusCodeMapping[this.type];
57+
}
58+
59+
public getPayload(debug: boolean): ErrorPayload {
60+
const underlying =
61+
this.cause instanceof Error
62+
? {
63+
message: this.cause.message,
64+
stack: this.cause.stack,
65+
}
66+
: undefined;
67+
return {
68+
type: this.type,
69+
message: this.message,
70+
data: this.options.responsePayload ?? {},
71+
debug: debug
72+
? {
73+
stack: this.stack,
74+
underlying,
75+
...this.options.debugPayload,
76+
}
77+
: undefined,
78+
};
79+
}
80+
}

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { ApiKeyProvider } from './providers/api_key';
22
export type * from './providers/shared';
3+
export * from './errors';
34

45
export type BackendExtension = {
56
apiKey?: ApiKeyProvider;

0 commit comments

Comments
 (0)