Skip to content

Commit 3491a6d

Browse files
merlinnotthechenky
authored andcommitted
Increase type strictness of HttpsError class (#521)
* Increase type strictness of HttpsError * Unhide toJSON method on HttpsErrors * Remove unused import * Hide httpErrorCode and toJSON on HttpsError class
1 parent de59422 commit 3491a6d

File tree

1 file changed

+78
-88
lines changed

1 file changed

+78
-88
lines changed

src/providers/https.ts

Lines changed: 78 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import * as _ from 'lodash';
2727
import { apps } from '../apps';
2828
import { HttpsFunction, optionsToTrigger, Runnable } from '../cloud-functions';
2929
import { DeploymentOptions } from '../function-configuration';
30-
import { assertNever } from '../utilities/assertions';
3130

3231
export interface Request extends express.Request {
3332
rawBody: Buffer;
@@ -128,33 +127,65 @@ export type FunctionsErrorCode =
128127
| 'data-loss'
129128
| 'unauthenticated';
130129

130+
export type CanonicalErrorCodeName =
131+
| 'OK'
132+
| 'CANCELLED'
133+
| 'UNKNOWN'
134+
| 'INVALID_ARGUMENT'
135+
| 'DEADLINE_EXCEEDED'
136+
| 'NOT_FOUND'
137+
| 'ALREADY_EXISTS'
138+
| 'PERMISSION_DENIED'
139+
| 'UNAUTHENTICATED'
140+
| 'RESOURCE_EXHAUSTED'
141+
| 'FAILED_PRECONDITION'
142+
| 'ABORTED'
143+
| 'OUT_OF_RANGE'
144+
| 'UNIMPLEMENTED'
145+
| 'INTERNAL'
146+
| 'UNAVAILABLE'
147+
| 'DATA_LOSS';
148+
149+
interface HttpErrorCode {
150+
canonicalName: CanonicalErrorCodeName;
151+
status: number;
152+
}
153+
131154
/**
132-
* Standard error codes for different ways a request can fail, as defined by:
155+
* Standard error codes and HTTP statuses for different ways a request can fail,
156+
* as defined by:
133157
* https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
134158
*
135159
* This map is used primarily to convert from a client error code string to
136-
* to the HTTP format error code string, and make sure it's in the supported set.
160+
* to the HTTP format error code string and status, and make sure it's in the
161+
* supported set.
137162
*/
138-
const errorCodeMap: { [name: string]: string } = {
139-
ok: 'OK',
140-
cancelled: 'CANCELLED',
141-
unknown: 'UNKNOWN',
142-
'invalid-argument': 'INVALID_ARGUMENT',
143-
'deadline-exceeded': 'DEADLINE_EXCEEDED',
144-
'not-found': 'NOT_FOUND',
145-
'already-exists': 'ALREADY_EXISTS',
146-
'permission-denied': 'PERMISSION_DENIED',
147-
unauthenticated: 'UNAUTHENTICATED',
148-
'resource-exhausted': 'RESOURCE_EXHAUSTED',
149-
'failed-precondition': 'FAILED_PRECONDITION',
150-
aborted: 'ABORTED',
151-
'out-of-range': 'OUT_OF_RANGE',
152-
unimplemented: 'UNIMPLEMENTED',
153-
internal: 'INTERNAL',
154-
unavailable: 'UNAVAILABLE',
155-
'data-loss': 'DATA_LOSS',
163+
const errorCodeMap: { [name in FunctionsErrorCode]: HttpErrorCode } = {
164+
ok: { canonicalName: 'OK', status: 200 },
165+
cancelled: { canonicalName: 'CANCELLED', status: 499 },
166+
unknown: { canonicalName: 'UNKNOWN', status: 500 },
167+
'invalid-argument': { canonicalName: 'INVALID_ARGUMENT', status: 400 },
168+
'deadline-exceeded': { canonicalName: 'DEADLINE_EXCEEDED', status: 504 },
169+
'not-found': { canonicalName: 'NOT_FOUND', status: 404 },
170+
'already-exists': { canonicalName: 'ALREADY_EXISTS', status: 409 },
171+
'permission-denied': { canonicalName: 'PERMISSION_DENIED', status: 403 },
172+
unauthenticated: { canonicalName: 'UNAUTHENTICATED', status: 401 },
173+
'resource-exhausted': { canonicalName: 'RESOURCE_EXHAUSTED', status: 429 },
174+
'failed-precondition': { canonicalName: 'FAILED_PRECONDITION', status: 400 },
175+
aborted: { canonicalName: 'ABORTED', status: 409 },
176+
'out-of-range': { canonicalName: 'OUT_OF_RANGE', status: 400 },
177+
unimplemented: { canonicalName: 'UNIMPLEMENTED', status: 501 },
178+
internal: { canonicalName: 'INTERNAL', status: 500 },
179+
unavailable: { canonicalName: 'UNAVAILABLE', status: 503 },
180+
'data-loss': { canonicalName: 'DATA_LOSS', status: 500 },
156181
};
157182

183+
interface HttpErrorWireFormat {
184+
details?: unknown;
185+
message: string;
186+
status: CanonicalErrorCodeName;
187+
}
188+
158189
/**
159190
* An explicit error that can be thrown from a handler to send an error to the
160191
* client that called the function.
@@ -164,88 +195,46 @@ export class HttpsError extends Error {
164195
* A standard error code that will be returned to the client. This also
165196
* determines the HTTP status code of the response, as defined in code.proto.
166197
*/
167-
readonly code: FunctionsErrorCode;
198+
public readonly code: FunctionsErrorCode;
168199

169200
/**
170201
* Extra data to be converted to JSON and included in the error response.
171202
*/
172-
readonly details?: unknown;
203+
public readonly details: unknown;
204+
205+
/**
206+
* A wire format representation of a provided error code.
207+
*
208+
* @hidden
209+
*/
210+
public readonly httpErrorCode: HttpErrorCode;
173211

174212
constructor(code: FunctionsErrorCode, message: string, details?: unknown) {
175213
super(message);
176214

177-
if (!errorCodeMap[code]) {
178-
throw new Error('Unknown error status: ' + code);
215+
// A sanity check for non-TypeScript consumers.
216+
if (code in errorCodeMap === false) {
217+
throw new Error(`Unknown error code: ${code}.`);
179218
}
180219

181220
this.code = code;
182221
this.details = details;
183-
}
184-
185-
/**
186-
* @hidden
187-
* A string representation of the Google error code for this error for HTTP.
188-
*/
189-
get status() {
190-
return errorCodeMap[this.code];
191-
}
192-
193-
/**
194-
* @hidden
195-
* Returns the canonical http status code for the given error.
196-
*/
197-
get httpStatus(): number {
198-
switch (this.code) {
199-
case 'ok':
200-
return 200;
201-
case 'cancelled':
202-
return 499;
203-
case 'unknown':
204-
return 500;
205-
case 'invalid-argument':
206-
return 400;
207-
case 'deadline-exceeded':
208-
return 504;
209-
case 'not-found':
210-
return 404;
211-
case 'already-exists':
212-
return 409;
213-
case 'permission-denied':
214-
return 403;
215-
case 'unauthenticated':
216-
return 401;
217-
case 'resource-exhausted':
218-
return 429;
219-
case 'failed-precondition':
220-
return 400;
221-
case 'aborted':
222-
return 409;
223-
case 'out-of-range':
224-
return 400;
225-
case 'unimplemented':
226-
return 501;
227-
case 'internal':
228-
return 500;
229-
case 'unavailable':
230-
return 503;
231-
case 'data-loss':
232-
return 500;
233-
// This should never happen as long as the type system is doing its job.
234-
default:
235-
assertNever(this.code);
236-
}
222+
this.httpErrorCode = errorCodeMap[code];
237223
}
238224

239225
/** @hidden */
240-
public toJSON() {
241-
const json: any = {
242-
status: this.status,
243-
message: this.message,
226+
public toJSON(): HttpErrorWireFormat {
227+
const {
228+
details,
229+
httpErrorCode: { canonicalName: status },
230+
message,
231+
} = this;
232+
233+
return {
234+
...(details === undefined ? {} : { details }),
235+
message,
236+
status,
244237
};
245-
if (!_.isUndefined(this.details)) {
246-
json.details = this.details;
247-
}
248-
return json;
249238
}
250239
}
251240

@@ -472,8 +461,9 @@ export function _onCallWithOptions(
472461
error = new HttpsError('internal', 'INTERNAL');
473462
}
474463

475-
const status = error.httpStatus;
464+
const { status } = error.httpErrorCode;
476465
const body = { error: error.toJSON() };
466+
477467
res.status(status).send(body);
478468
}
479469
};

0 commit comments

Comments
 (0)