| 1 | import { PlatformErrorCategory } from '../platforms/platforms.exception' |
| 2 | import { categoryFromHttpStatus, isHttpStatusRetryable } from './platform-error-classifier.util' |
| 3 | |
| 4 | export interface GoogleApiErrorDetail { |
| 5 | reason?: string |
| 6 | message?: string |
| 7 | domain?: string |
| 8 | location?: string |
| 9 | locationType?: string |
| 10 | } |
| 11 | |
| 12 | export interface GoogleApiError { |
| 13 | code?: number |
| 14 | message?: string |
| 15 | status?: string |
| 16 | errors?: GoogleApiErrorDetail[] |
| 17 | } |
| 18 | |
| 19 | export interface GoogleApiErrorBody { |
| 20 | error?: string | GoogleApiError |
| 21 | error_description?: string |
| 22 | message?: string |
| 23 | } |
| 24 | |
| 25 | export function googleApiPlatformCode(data?: GoogleApiErrorBody): string | number | undefined { |
| 26 | const error = googleApiErrorObject(data) |
| 27 | return error?.status |
| 28 | ?? error?.errors?.[0]?.reason |
| 29 | ?? error?.code |
| 30 | ?? (typeof data?.error === 'string' ? data.error : undefined) |
| 31 | } |
| 32 | |
| 33 | export function googleApiErrorReason(data?: GoogleApiErrorBody): string | undefined { |
| 34 | const error = googleApiErrorObject(data) |
| 35 | return error?.errors?.[0]?.reason |
| 36 | ?? (typeof data?.error === 'string' ? data.error : undefined) |
| 37 | } |
| 38 | |
| 39 | export function googleApiPlatformMessage(data?: GoogleApiErrorBody): string | undefined { |
| 40 | const error = googleApiErrorObject(data) |
| 41 | return error?.errors?.[0]?.message |
| 42 | ?? error?.message |
| 43 | ?? data?.error_description |
| 44 | ?? data?.message |
| 45 | ?? (typeof data?.error === 'string' ? data.error : undefined) |
| 46 | } |
| 47 | |
| 48 | export function categoryFromGoogleApiError( |
| 49 | status?: number, |
| 50 | data?: GoogleApiErrorBody, |
| 51 | ): PlatformErrorCategory { |
| 52 | const error = googleApiErrorObject(data) |
| 53 | const apiStatus = error?.status |
| 54 | const reason = googleApiErrorReason(data) |
| 55 | |
| 56 | if (isGoogleQuotaReason(reason)) { |
| 57 | return PlatformErrorCategory.Quota |
| 58 | } |
| 59 | if (isGoogleRateLimitReason(reason) || apiStatus === 'RESOURCE_EXHAUSTED' || status === 429) { |
| 60 | return PlatformErrorCategory.RateLimit |
| 61 | } |
| 62 | if ( |
| 63 | status === 401 |
| 64 | || data?.error === 'invalid_grant' |
| 65 | || data?.error === 'invalid_token' |
| 66 | || data?.error === 'unauthorized_client' |
| 67 | || data?.error === 'invalid_client' |
| 68 | ) { |
| 69 | return PlatformErrorCategory.Auth |
| 70 | } |
| 71 | if (apiStatus === 'PERMISSION_DENIED' || status === 403) { |
| 72 | return PlatformErrorCategory.Permission |
| 73 | } |
| 74 | if (apiStatus === 'NOT_FOUND' || status === 404) { |
| 75 | return PlatformErrorCategory.NotFound |
| 76 | } |
| 77 | if (apiStatus === 'ABORTED' || apiStatus === 'ALREADY_EXISTS' || status === 409) { |
| 78 | return PlatformErrorCategory.Conflict |
| 79 | } |
| 80 | if (apiStatus === 'INVALID_ARGUMENT' || apiStatus === 'FAILED_PRECONDITION' || status === 400 || status === 422) { |
| 81 | return PlatformErrorCategory.Validation |
| 82 | } |
| 83 | if (apiStatus === 'UNAVAILABLE' || apiStatus === 'INTERNAL' || status === undefined || status >= 500) { |
| 84 | return PlatformErrorCategory.PlatformUnavailable |
| 85 | } |
| 86 | return categoryFromHttpStatus(status) |
| 87 | } |
| 88 | |
| 89 | export function isGoogleApiErrorRetryable(status?: number, data?: GoogleApiErrorBody): boolean { |
| 90 | const error = googleApiErrorObject(data) |
| 91 | const apiStatus = error?.status |
| 92 | const reason = googleApiErrorReason(data) |
| 93 | |
| 94 | if (isGoogleQuotaReason(reason)) { |
| 95 | return false |
| 96 | } |
| 97 | |
| 98 | return isGoogleRateLimitReason(reason) |
| 99 | || apiStatus === 'RESOURCE_EXHAUSTED' |
| 100 | || apiStatus === 'UNAVAILABLE' |
| 101 | || apiStatus === 'INTERNAL' |
| 102 | || isHttpStatusRetryable(status) |
| 103 | } |
| 104 | |
| 105 | function googleApiErrorObject(data?: GoogleApiErrorBody): GoogleApiError | undefined { |
| 106 | return typeof data?.error === 'object' ? data.error : undefined |
| 107 | } |
| 108 | |
| 109 | function isGoogleRateLimitReason(reason?: string): boolean { |
| 110 | return reason === 'rateLimitExceeded' |
| 111 | || reason === 'userRateLimitExceeded' |
| 112 | } |
| 113 | |
| 114 | function isGoogleQuotaReason(reason?: string): boolean { |
| 115 | return reason === 'quotaExceeded' |
| 116 | || reason === 'dailyLimitExceeded' |
| 117 | || reason === 'uploadLimitExceeded' |
| 118 | } |
| 119 |