import { plainToInstance } from 'class-transformer' ; import { validateOrReject } from 'class-validator' ; import isEmpty from 'lodash.isempty' ; import type { AxiosInstance , AxiosResponse } from 'axios' ; import { InvalidCredentialsException , InvalidServerResponseException , QuotaExceededException , UnsupportedAccuracyException , ValidationException , } from '../exception' ; import { LoggableMixin } from '../logger' ; import { getAvailableAccuracies } from '../util' ; import type { QueryInterface } from '../interface' ; import type { LoggableInterface } from '../logger' ; import type { AccuracyEnum } from '../model' ; import type { AbstractTransformer } from '../transformer' ; import type { Type } from '../types' ; export abstract class AbstractCommand < GeocoderQueryType extends QueryInterface = any , GeocoderResponseType = any , GeocoderTransformerType extends AbstractTransformer = any , ProviderRequestType = any , ProviderResponseType = any > extends LoggableMixin ( Function ) { [ 'constructor' ] : Pick < typeof AbstractCommand , keyof typeof AbstractCommand > & { name : string ; } & LoggableInterface ; constructor ( protected readonly httpClient , ... _args ) { super ( ) ; } static queryClass ( ) { throw new Error ( 'AbstractCommand.queryClass: not implemented' ) ; } example important static getMaxAccuracy ( ) { throw new Error ( 'AbstractCommand.getMaxAccuracy: not implemented' ) ; } static getUrl ( ) { throw new Error ( 'AbstractCommand.getUrl: not implemented' ) ; } protected async buildQuery ( _query ) { throw new Error ( 'AbstractCommand.buildQuery: not implemented' ) ; } throws protected async validateResponse ( _response ) { throw new Error ( 'AbstractCommand.validateResponse: not implemented' ) ; } protected async parseResponse ( _response , _query ) { throw new Error ( 'AbstractCommand.parseResponse: not implemented' ) ; } static isProvidesAccuracy ( accuracy ) { return getAvailableAccuracies ( this . getMaxAccuracy ( ) ) . includes ( accuracy ) ; } async execute ( _query ) { const query = plainToInstance < GeocoderQueryType , GeocoderQueryType > ( this . constructor . queryClass ( ) , _query ) ; try { await validateOrReject ( query , { whitelist : true , forbidNonWhitelisted : true , validationError : { target : false , value : false } , } ) ; } catch ( err ) { this . getLogger ( ) . error ( err , query ) ; throw new ValidationException ( err ) ; } if ( query . accuracy && ! this . constructor . isProvidesAccuracy ( query . accuracy ) ) { throw new UnsupportedAccuracyException ( `Command ${ this . constructor . name } doesn't support "${ query . accuracy }" accuracy (max accuracy is "${ this . constructor . getMaxAccuracy ( ) }")` ) ; } const params = await this . buildQuery ( query ) ; let response ; try { response = await this . getResponse ( params ) ; } catch ( err ) { if ( err . response && err . response . status ) { const statusCode = err . response . status ; if ( statusCode === 401 || statusCode === 403 ) { throw new InvalidCredentialsException ( `API key is invalid` , err . response ) ; } else if ( statusCode === 429 ) { throw new QuotaExceededException ( 'Quota exceeded' , err . response ) ; } throw new InvalidServerResponseException ( 'Server error' , err . response ) ; } else { throw new InvalidServerResponseException ( err . message ) ; } } if ( isEmpty ( response . data ) ) { throw new InvalidServerResponseException ( 'Empty response data' , response ) ; } await this . validateResponse ( response ) ; const transformers = await this . parseResponse ( response , query ) ; return Promise . all < GeocoderResponseType > ( transformers . map ( async ( transformer ) : Promise < GeocoderResponseType > => { return transformer . transform ( { groups : query . withRaw ? [ 'raw' ] : undefined , } ) ; } ) ) ; } protected async getResponse ( params ) { return this . httpClient . get < ProviderResponseType > ( this . constructor . getUrl ( ) , { params , } ) ; } }