@@ -27,7 +27,6 @@ import * as _ from 'lodash';
2727import { apps } from '../apps' ;
2828import { HttpsFunction , optionsToTrigger , Runnable } from '../cloud-functions' ;
2929import { DeploymentOptions } from '../function-configuration' ;
30- import { assertNever } from '../utilities/assertions' ;
3130
3231export 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