import { Readable } from 'stream' ; import { AxeResultsList , AxeCoreResults } from 'axe-result-converter' ; import { BlobContentDownloadResponse , BlobStorageClient } from 'azure-services' ; import { inject , injectable } from 'inversify' ; import { CombinedScanResults } from 'storage-documents' ; import { BodyParser , System } from 'common' ; import { DataProvidersCommon } from './data-providers-common' ; export type ReadErrorCode = 'blobNotFound' | 'jsonParseError' | 'streamError' ; export type WriteErrorCode = 'etagMismatch' | 'httpStatusError' ; export type CombinedScanResultsError < ErrorCodeType > = { errorCode : ErrorCodeType ; data ? : string ; } ; export type CombinedScanResultsReadResponse = { error ? : CombinedScanResultsError < ReadErrorCode > ; results ? : CombinedScanResults ; etag ? : string ; } ; export type CombinedScanResultsWriteResponse = { error ? : CombinedScanResultsError < WriteErrorCode > ; etag ? : string ; } ; @ injectable ( ) export class CombinedScanResultsProvider { private static readonly preconditionFailedStatusCode = 412 ; constructor ( @ inject ( BlobStorageClient ) private readonly blobStorageClient , @ inject ( DataProvidersCommon ) private readonly dataProvidersCommon , @ inject ( BodyParser ) private readonly bodyParser ) { } public async writeCombinedResults ( fileId , combinedScanResults , etag ? ) { const filePath = this . dataProvidersCommon . getBlobName ( fileId ) ; const contentString = JSON . stringify ( combinedScanResults ?? this . getEmptyCombinedScanResults ( ) ) ; const condition = etag ? { ifMatchEtag : etag } : undefined ; const response = await this . blobStorageClient . uploadBlobContent ( DataProvidersCommon . combinedResultsBlobContainerName , filePath , contentString , condition ) ; if ( this . statusSuccessful ( response . statusCode ) ) { return { etag : response . etag } ; } if ( response . statusCode === CombinedScanResultsProvider . preconditionFailedStatusCode ) { return { error : { errorCode : 'etagMismatch' , } , } ; } return { error : { errorCode : 'httpStatusError' , data : `${ response . statusCode }` , } , } ; } public async readCombinedResults ( fileId ) { const downloadResponse = await this . readBlob ( fileId ) ; if ( downloadResponse . notFound ) { return { error : { errorCode : 'blobNotFound' } , } ; } let contentString ; try { contentString = ( await this . bodyParser . getRawBody ( downloadResponse . content as Readable ) ) . toString ( ) ; } catch ( error ) { return { error : { errorCode : 'streamError' , data : System . serializeError ( error ) , } , } ; } try { const content = JSON . parse ( contentString , ( key , value ) => { if ( key === 'violations' || key === 'passes' || key === 'incomplete' || key === 'inapplicable' ) { return new AxeResultsList ( value ) ; } return value ; } ) ; return { results : content , etag : downloadResponse . etag , } ; } catch ( error ) { return { error : { errorCode : 'jsonParseError' , data : System . serializeError ( error ) , } , } ; } } public getEmptyResponse ( ) { return { results : this . getEmptyCombinedScanResults ( ) , } ; } private getEmptyCombinedScanResults ( ) { return { urlCount : { failed : 0 , passed : 0 , total : 0 , } , axeResults : { urls : [ ] , violations : new AxeResultsList ( ) , passes : new AxeResultsList ( ) , incomplete : new AxeResultsList ( ) , inapplicable : new AxeResultsList ( ) , } as AxeCoreResults , } ; } private async readBlob ( fileId ) { return this . blobStorageClient . getBlobContent ( DataProvidersCommon . combinedResultsBlobContainerName , this . dataProvidersCommon . getBlobName ( fileId ) ) ; } private statusSuccessful ( statusCode ) { return statusCode >= 200 && statusCode < 300 ; } }