import { parseString } from 'xml2js' ; import { INode , IStatusCodeMessages , JsonObject } from '.' ; /**
 * Top-level properties where an error message can be found in an API response.
 */ const ERROR_MESSAGE_PROPERTIES = [ 'error' , 'message' , 'Message' , 'msg' , 'messages' , 'description' , 'reason' , 'detail' , 'details' , 'errors' , 'errorMessage' , 'errorMessages' , 'ErrorMessage' , 'error_message' , '_error_message' , 'errorDescription' , 'error_description' , 'error_summary' , 'title' , 'text' , 'field' , 'err' , 'type' , ] ; /**
 * Top-level properties where an HTTP error code can be found in an API response.
 */ const ERROR_STATUS_PROPERTIES = [ 'statusCode' , 'status' , 'code' , 'status_code' , 'errorCode' , 'error_code' , ] ; /**
 * Properties where a nested object can be found in an API response.
 */ const ERROR_NESTING_PROPERTIES = [ 'error' , 'err' , 'response' , 'body' , 'data' ] ; /**
 * Base class for specific NodeError-types, with functionality for finding
 * a value recursively inside an error object.
 */ abstract class NodeError extends Error { description : string | null | undefined ; cause : Error | JsonObject ; node : INode ; timestamp : number ; constructor ( node , error ) { super ( ) ; this . name = this . constructor . name ; this . cause = error ; this . node = node ; this . timestamp = Date . now ( ) ; if ( error . message ) { this . message = error . message as string ; } } param { JsonObject } error param { string [ ] } potentialKeys param { string [ ] } traversalKeys returns { string | null } protected findProperty ( error , potentialKeys , traversalKeys = [ ] ) { for ( const key of potentialKeys ) { if ( error [ key ] ) { if ( typeof error [ key ] === 'string' ) return error [ key ] as string ; if ( typeof error [ key ] === 'number' ) return error [ key ] ! . toString ( ) ; if ( Array . isArray ( error [ key ] ) ) { const resolvedErrors = error [ key ] . map ( ( error ) => { if ( typeof error === 'string' ) return error ; if ( typeof error === 'number' ) return error . toString ( ) ; if ( this . isTraversableObject ( error ) ) { return this . findProperty ( error , potentialKeys ) ; } return null ; } ) . filter ( ( errorValue ) => errorValue !== null ) ; if ( resolvedErrors . length === 0 ) { return null ; } return resolvedErrors . join ( ' | ' ) ; } if ( this . isTraversableObject ( error [ key ] ) ) { const property = this . findProperty ( error [ key ] as JsonObject , potentialKeys ) ; if ( property ) { return property ; } } } } for ( const key of traversalKeys ) { if ( this . isTraversableObject ( error [ key ] ) ) { const property = this . findProperty ( error [ key ] as JsonObject , potentialKeys , traversalKeys ) ; if ( property ) { return property ; } } } return null ; } /**
     * Check if a value is an object with at least one key, i.e. it can be traversed.
     */ protected isTraversableObject ( value ) { return ( value && typeof value === 'object' && ! Array . isArray ( value ) && ! ! Object . keys ( value ) . length ) ; } /**
     * Remove circular references from objects.
     */ protected removeCircularRefs ( obj , seen = new Set ( ) ) { seen . add ( obj ) ; Object . entries ( obj ) . forEach ( ( [ key , value ] ) => { if ( this . isTraversableObject ( value ) ) { seen . has ( value ) ? ( obj [ key ] = { circularReference : true } ) : this . removeCircularRefs ( value , seen ) ; return ; } if ( Array . isArray ( value ) ) { value . forEach ( ( val , index ) => { if ( seen . has ( val ) ) { value [ index ] = { circularReference : true } ; return ; } if ( this . isTraversableObject ( val ) ) { this . removeCircularRefs ( val , seen ) ; } } ) ; } } ) ; } } /**
 * Class for instantiating an operational error, e.g. an invalid credentials error.
 */ export class NodeOperationError extends NodeError { constructor ( node , error , options ? ) { if ( typeof error === 'string' ) { error = new Error ( error ) ; } super ( node , error ) ; if ( options ?. description ) { this . description = options . description ; } } } const STATUS_CODE_MESSAGES = { '4XX' : 'Your request is invalid or could not be processed by the service' , '400' : 'Bad request - please check your parameters' , '401' : 'Authorization failed - please check your credentials' , '402' : 'Payment required - perhaps check your payment details?' , '403' : 'Forbidden - perhaps check your credentials?' , '404' : 'The resource you are requesting could not be found' , '405' : 'Method not allowed - please check you are using the right HTTP method' , '429' : 'The service is receiving too many requests from you! Perhaps take a break?' , '5XX' : 'The service failed to process your request' , '500' : 'The service was not able to process your request' , '502' : 'Bad gateway - the service failed to handle your request' , '503' : 'Service unavailable - perhaps try again later?' , '504' : 'Gateway timed out - perhaps try again later?' , } ; const UNKNOWN_ERROR_MESSAGE = 'UNKNOWN ERROR - check the detailed error for more information' ; /**
 * Class for instantiating an error in an API response, e.g. a 404 Not Found response,
 * with an HTTP error code, an error message and a description.
 */ export class NodeApiError extends NodeError { httpCode : string | null ; constructor ( node , error , { message , description , httpCode , parseXml , } = { } ) { super ( node , error ) ; if ( error . error ) { this . removeCircularRefs ( error . error as JsonObject ) ; } if ( message ) { this . message = message ; this . description = description ; this . httpCode = httpCode ?? null ; return ; } this . httpCode = this . findProperty ( error , ERROR_STATUS_PROPERTIES , ERROR_NESTING_PROPERTIES ) ; this . setMessage ( ) ; if ( parseXml ) { this . setDescriptionFromXml ( error . error as string ) ; return ; } this . description = this . findProperty ( error , ERROR_MESSAGE_PROPERTIES , ERROR_NESTING_PROPERTIES ) ; } private setDescriptionFromXml ( xml ) { parseString ( xml , { explicitArray : false } , ( _ , result ) => { if ( ! result ) return ; const topLevelKey = Object . keys ( result ) [ 0 ] ; this . description = this . findProperty ( result [ topLevelKey ] , ERROR_MESSAGE_PROPERTIES , [ 'Error' ] . concat ( ERROR_NESTING_PROPERTIES ) ) ; } ) ; } returns { void } private setMessage ( ) { if ( ! this . httpCode ) { this . httpCode = null ; this . message = UNKNOWN_ERROR_MESSAGE ; return ; } if ( STATUS_CODE_MESSAGES [ this . httpCode ] ) { this . message = STATUS_CODE_MESSAGES [ this . httpCode ] ; return ; } switch ( this . httpCode . charAt ( 0 ) ) { case '4' : this . message = STATUS_CODE_MESSAGES [ '4XX' ] ; break ; case '5' : this . message = STATUS_CODE_MESSAGES [ '5XX' ] ; break ; default : this . message = UNKNOWN_ERROR_MESSAGE ; } } }