import { ITelemetryProperties } from "@fluidframework/common-definitions" ; import { DriverErrorType } from "@fluidframework/driver-definitions" ; import { IFluidErrorBase , TelemetryLogger } from "@fluidframework/telemetry-utils" ; import { AuthorizationError , createGenericNetworkError , isOnline , RetryableError , NonRetryableError , OnlineStatus , } from "@fluidframework/driver-utils" ; import { OdspErrorType , OdspError , IOdspError } from "@fluidframework/odsp-driver-definitions" ; import { parseAuthErrorClaims } from "./parseAuthErrorClaims" ; import { parseAuthErrorTenant } from "./parseAuthErrorTenant" ; export const fetchIncorrectResponse = 712 ; export const OdspServiceReadOnlyErrorCode = "serviceReadOnly" ; export function getSPOAndGraphRequestIdsFromResponse ( headers ) { interface LoggingHeader { headerName : string ; logName : string ; } const headersToLog = [ { headerName : "sprequestguid" , logName : "sprequestguid" } , { headerName : "request-id" , logName : "requestId" } , { headerName : "client-request-id" , logName : "clientRequestId" } , { headerName : "x-msedge-ref" , logName : "xMsedgeRef" } , { headerName : "X-Fluid-Retries" , logName : "serverRetries" } , ] ; const additionalProps = { sprequestduration : TelemetryLogger . numberFromString ( headers . get ( "sprequestduration" ) ) , contentsize : TelemetryLogger . numberFromString ( headers . get ( "content-length" ) ) } ; headersToLog . forEach ( ( header ) => { const headerValue = headers . get ( header . headerName ) ; if ( headerValue !== undefined && headerValue !== null ) { additionalProps [ header . logName ] = headerValue ; } } ) ; return additionalProps ; } export interface IFacetCodes { facetCodes ? : string [ ] ; } /** Empirically-based model of error response inner error from ODSP */ export interface OdspErrorResponseInnerError { code ? : string ; innerError ? : OdspErrorResponseInnerError ; } /** Empirically-based model of error responses from ODSP */ export interface OdspErrorResponse { error : OdspErrorResponseInnerError & { message : string ; } ; } /** Empirically-based type guard for error responses from ODSP */ function isOdspErrorResponse ( x ) { const error = x ?. error ; return typeof ( error ?. message ) === "string" && ( error ?. code === undefined || typeof ( error ?. code ) === "string" ) ; } export function tryParseErrorResponse ( response ) { try { if ( response !== undefined ) { const parsed = JSON . parse ( response ) ; if ( isOdspErrorResponse ( parsed ) ) { return { success : true , errorResponse : parsed } ; } } } catch ( e ) { } return { success : false } ; } export function parseFacetCodes ( errorResponse ) { const stack = [ ] ; let error = errorResponse . error ; while ( typeof error === "object" && error !== null ) { if ( error . code !== undefined ) { stack . unshift ( error . code ) ; } error = error . innerError ; } return stack ; } export function createOdspNetworkError ( fluidErrorCode , errorMessage , statusCode , retryAfterSeconds ? , response ? , responseText ? , props = { } ) { let error ; const parseResult = tryParseErrorResponse ( responseText ) ; let facetCodes ; let innerMostErrorCode ; if ( parseResult . success ) { props . response = responseText ; const errorResponse = parseResult . errorResponse ; facetCodes = parseFacetCodes ( errorResponse ) ; if ( facetCodes !== undefined ) { innerMostErrorCode = facetCodes [ 0 ] ; props . innerMostErrorCode = innerMostErrorCode ; } } switch ( statusCode ) { case 400 : error = new NonRetryableError ( fluidErrorCode , errorMessage , DriverErrorType . genericNetworkError , { statusCode } ) ; break ; case 401 : case 403 : if ( innerMostErrorCode === OdspServiceReadOnlyErrorCode ) { error = new RetryableError ( fluidErrorCode , errorMessage , OdspErrorType . serviceReadOnly ) ; } else { const claims = response ?. headers ? parseAuthErrorClaims ( response . headers ) : undefined ; const tenantId = response ?. headers ? parseAuthErrorTenant ( response . headers ) : undefined ; error = new AuthorizationError ( fluidErrorCode , errorMessage , claims , tenantId , { statusCode } ) ; } break ; case 404 : error = new NonRetryableError ( fluidErrorCode , errorMessage , DriverErrorType . fileNotFoundOrAccessDeniedError , { statusCode } ) ; break ; case 406 : error = new NonRetryableError ( fluidErrorCode , errorMessage , DriverErrorType . unsupportedClientProtocolVersion , { statusCode } ) ; break ; case 410 : error = new NonRetryableError ( fluidErrorCode , errorMessage , OdspErrorType . cannotCatchUp , { statusCode } ) ; break ; case 409 : error = new NonRetryableError ( fluidErrorCode , errorMessage , DriverErrorType . fileOverwrittenInStorage , { statusCode } ) ; break ; case 412 : error = new NonRetryableError ( fluidErrorCode , errorMessage , DriverErrorType . genericNetworkError , { statusCode } ) ; break ; case 413 : error = new NonRetryableError ( fluidErrorCode , errorMessage , OdspErrorType . snapshotTooBig , { statusCode } ) ; break ; case 414 : error = new NonRetryableError ( fluidErrorCode , errorMessage , OdspErrorType . invalidFileNameError , { statusCode } ) ; break ; case 500 : error = new RetryableError ( fluidErrorCode , errorMessage , DriverErrorType . genericNetworkError , { statusCode } ) ; break ; case 501 : error = new NonRetryableError ( fluidErrorCode , errorMessage , OdspErrorType . fluidNotEnabled , { statusCode } ) ; break ; case 507 : error = new NonRetryableError ( fluidErrorCode , errorMessage , OdspErrorType . outOfStorageError , { statusCode } ) ; break ; case fetchIncorrectResponse : error = new NonRetryableError ( fluidErrorCode , errorMessage , DriverErrorType . incorrectServerResponse ) ; break ; default : const retryAfterMs = retryAfterSeconds !== undefined ? retryAfterSeconds * 1000 : undefined ; error = createGenericNetworkError ( fluidErrorCode , errorMessage , true , retryAfterMs , { statusCode } ) ; break ; } enrichOdspError ( error , response , facetCodes , props ) ; return error ; } export function enrichOdspError ( error , response ? , facetCodes ? , props = { } ) { error . online = OnlineStatus [ isOnline ( ) ] ; if ( facetCodes !== undefined ) { error . facetCodes = facetCodes ; } if ( response ) { props . responseType = response . type ; if ( response . headers ) { const headers = getSPOAndGraphRequestIdsFromResponse ( response . headers ) ; for ( const key of Object . keys ( headers ) ) { props [ key ] = headers [ key ] ; } ( error as IOdspError ) . serverEpoch = response . headers . get ( "x-fluid-epoch" ) ?? undefined ; } } error . addTelemetryProperties ( props ) ; return error ; } /**
 * Throws network error - an object with a bunch of network related properties
 */ export function throwOdspNetworkError ( fluidErrorCode , statusCode , response , responseText ? , props ? ) { const networkError = createOdspNetworkError ( fluidErrorCode , response . statusText !== "" ? `${ fluidErrorCode } (${ response . statusText })` : fluidErrorCode , statusCode , numberFromHeader ( response . headers . get ( "retry-after" ) ) , response , responseText , props ) ; networkError . addTelemetryProperties ( { odspError : true , storageServiceError : true } ) ; throw networkError ; } function numberFromHeader ( header ) { if ( header === null ) { return undefined ; } const n = Number ( header ) ; if ( Number . isNaN ( n ) ) { return undefined ; } return n ; }