| 
									
										
										
										
											2020-08-26 15:44:50 -07:00
										 |  |  | import { ApiResponseStatus, isNotFoundApiError } from '@datahub/utils/api/shared'; | 
					
						
							| 
									
										
										
										
											2018-02-22 14:26:19 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Extends the built-in Error class with attributes related to treating non 200 OK responses | 
					
						
							|  |  |  |  * at the api layer as exceptions | 
					
						
							| 
									
										
										
										
											2019-08-31 20:51:14 -07:00
										 |  |  |  * @export | 
					
						
							| 
									
										
										
										
											2018-02-22 14:26:19 -08:00
										 |  |  |  * @class ApiError | 
					
						
							|  |  |  |  * @extends {Error} | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2019-08-31 20:51:14 -07:00
										 |  |  | export class ApiError extends Error { | 
					
						
							| 
									
										
										
										
											2018-02-22 14:26:19 -08:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * Timestamp of when the exception occurred | 
					
						
							|  |  |  |    * @readonly | 
					
						
							|  |  |  |    * @memberof ApiError | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   readonly timestamp = new Date(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-26 15:44:50 -07:00
										 |  |  |   /** | 
					
						
							|  |  |  |    *Creates an instance of ApiError, expects the status type, ApiResponseStatus, as the first argument which corresponds to the available HTTP status codes | 
					
						
							|  |  |  |    * @param {ApiResponseStatus} status The status code of the response from the targeted API endpoint | 
					
						
							|  |  |  |    * @param {string} message An error message potentially describing the reason for the error | 
					
						
							|  |  |  |    * @param {...Array<string>} args Additional optional string arguments that may be passed into a javascript Error constructor | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   constructor(readonly status: ApiResponseStatus, message: string, ...args: Array<string>) { | 
					
						
							| 
									
										
										
										
											2018-02-22 14:26:19 -08:00
										 |  |  |     super(...[message, ...args]); | 
					
						
							|  |  |  |     // Fixes downlevel compiler limitation with correct prototype chain adjustment
 | 
					
						
							|  |  |  |     // i.e. ensuring this is also `instanceof` subclass
 | 
					
						
							|  |  |  |     Object.setPrototypeOf(this, ApiError.prototype); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2018-02-21 14:41:05 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-31 20:51:14 -07:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Returns a default msg for a given status | 
					
						
							|  |  |  |  * @param {ApiResponseStatus} status | 
					
						
							|  |  |  |  * @returns {string} | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2020-08-26 15:44:50 -07:00
										 |  |  | export const apiErrorStatusMessage = (status: ApiResponseStatus): string => { | 
					
						
							|  |  |  |   const statusMessages: Record<number, string> = { | 
					
						
							| 
									
										
										
										
											2019-08-31 20:51:14 -07:00
										 |  |  |     [ApiResponseStatus.NotFound]: 'Could not find the requested resource', | 
					
						
							|  |  |  |     [ApiResponseStatus.InternalServerError]: 'An error occurred with the server' | 
					
						
							| 
									
										
										
										
											2020-08-26 15:44:50 -07:00
										 |  |  |   }; | 
					
						
							| 
									
										
										
										
											2019-08-31 20:51:14 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-26 15:44:50 -07:00
										 |  |  |   return statusMessages[status]; | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2018-02-21 14:41:05 -08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Wraps a Response object, pass through json response if no api error, | 
					
						
							|  |  |  |  * otherwise raise exception with error message | 
					
						
							|  |  |  |  * @template T | 
					
						
							| 
									
										
										
										
											2020-08-26 15:44:50 -07:00
										 |  |  |  * @param {Response} response the fetch Response object to check for an error state | 
					
						
							|  |  |  |  * @param {(response: Response) => Promise<T>} cb if response is not an api error, then invoke callback with response object | 
					
						
							| 
									
										
										
										
											2018-02-21 14:41:05 -08:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2020-08-26 15:44:50 -07:00
										 |  |  | export const throwIfApiError = async <T>(response: Response, cb: (response: Response) => Promise<T>): Promise<T> => { | 
					
						
							| 
									
										
										
										
											2018-02-21 14:41:05 -08:00
										 |  |  |   const { status, ok } = response; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-28 10:31:15 -07:00
										 |  |  |   if (!ok) { | 
					
						
							| 
									
										
										
										
											2018-02-21 14:41:05 -08:00
										 |  |  |     const { msg = apiErrorStatusMessage(status) } = await response.json(); | 
					
						
							| 
									
										
										
										
											2018-02-22 14:26:19 -08:00
										 |  |  |     throw new ApiError(status, msg); | 
					
						
							| 
									
										
										
										
											2018-02-21 14:41:05 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-31 20:51:14 -07:00
										 |  |  |   return cb(response); | 
					
						
							| 
									
										
										
										
											2018-02-21 14:41:05 -08:00
										 |  |  | }; | 
					
						
							| 
									
										
										
										
											2020-08-26 15:44:50 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Can be plugged into the .catch() chain of our fetcher as a callback, will return a default value | 
					
						
							|  |  |  |  * if the API has returned a 404, otherwise will let the error be thrown. Helpful when we want a | 
					
						
							|  |  |  |  * clean way to give a default and handle this error | 
					
						
							|  |  |  |  * @param {any} value - value to return if API is a not found error | 
					
						
							|  |  |  |  * @example | 
					
						
							|  |  |  |  * const pokemon = await getJSON({ url: '/pokemon/ash' }).catch(getDefaultIfNotFoundError([])); | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export const getDefaultIfNotFoundError = <T>(value: T): ((e: Error) => T) => (e: Error): T => { | 
					
						
							|  |  |  |   if (isNotFoundApiError(e)) { | 
					
						
							|  |  |  |     return value; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  |   throw e; | 
					
						
							|  |  |  | }; |