import { BatchType } from "../../common/batch/BatchOperation" ; import BatchRequest from "../../common/batch/BatchRequest" ; import { HttpMethod } from "../../table/generated/IRequest" ; import { BatchSerialization } from "../../common/batch/BatchSerialization" ; import TableBatchOperation from "../batch/TableBatchOperation" ; import * as Models from "../generated/artifacts/models" ; import TableBatchUtils from "./TableBatchUtils" ; import StorageError from "../errors/StorageError" ; import { truncatedISO8061Date } from "../../common/utils/utils" ; export class extends BatchSerialization export class TableBatchSerialization extends BatchSerialization { param { string } batchRequestsString return { * } memberof public deserializeBatchRequest ( batchRequestsString ) { this . extractBatchBoundary ( batchRequestsString ) ; this . extractChangeSetBoundary ( batchRequestsString ) ; this . extractLineEndings ( batchRequestsString ) ; const contentTypeHeaderString = this . extractRequestHeaderString ( batchRequestsString , "(\\n)+(([c,C])+(ontent-)+([t,T])+(ype)+)+(?=:)+" ) ; const contentTransferEncodingString = this . extractRequestHeaderString ( batchRequestsString , "(\\n)+(([c,C])+(ontent-)+([t,T])+(ransfer-)+([e,E])+(ncoding))+(?=:)+" ) ; const HTTP_LINE_ENDING = this . lineEnding ; const subRequestPrefix = `--${ this . changesetBoundary }${ HTTP_LINE_ENDING }${ contentTypeHeaderString }: application/http${ HTTP_LINE_ENDING }${ contentTransferEncodingString }: binary` ; const splitBody = batchRequestsString . split ( subRequestPrefix ) ; let subRequests ; if ( splitBody . length > 1 ) { subRequests = splitBody . slice ( 1 , splitBody . length ) ; } else { subRequests = splitBody ; } const batchOperations = subRequests . map ( ( subRequest ) => { let requestType = [ ] ; requestType = subRequest . match ( "(GET|PATCH|POST|PUT|MERGE|INSERT|DELETE)" ) ; if ( requestType === null || requestType . length < 2 ) { throw new Error ( `Couldn't extract verb from sub-Request:\n ${ subRequest }` ) ; } const fullRequestURI = subRequest . match ( /((http+s?)(\S)+)/ ) ; if ( fullRequestURI === null || fullRequestURI . length < 3 ) { throw new Error ( `Couldn't extract full request URL from sub-Request:\n ${ subRequest }` ) ; } const path = this . extractPath ( fullRequestURI [ 1 ] ) ; if ( path === null || path . length < 2 ) { throw new Error ( `Couldn't extract path from URL in sub-Request:\n ${ subRequest }` ) ; } const jsonOperationBody = subRequest . match ( /{+.+}+/ ) ; if ( subRequests . length > 1 && null !== requestType && requestType [ 0 ] !== "DELETE" && ( jsonOperationBody === null || jsonOperationBody . length < 1 ) ) { throw new Error ( `Couldn't extract path from sub-Request:\n ${ subRequest }` ) ; } let headers ; let jsonBody ; let subStringStart ; let subStringEnd ; subStringStart = subRequest . indexOf ( fullRequestURI [ 1 ] ) ; subStringStart += fullRequestURI [ 1 ] . length + 1 ; if ( jsonOperationBody != null ) { subStringEnd = subRequest . indexOf ( jsonOperationBody [ 0 ] ) ; jsonBody = jsonOperationBody [ 0 ] ; } else { subStringEnd = subRequest . length - 4 ; jsonBody = "" ; } headers = subRequest . substring ( subStringStart , subStringEnd ) ; const operation = new TableBatchOperation ( BatchType . table , headers ) ; if ( null !== requestType ) { operation . httpMethod = requestType [ 0 ] as HttpMethod ; } operation . path = path [ 1 ] ; operation . uri = fullRequestURI [ 0 ] ; operation . jsonRequestBody = jsonBody ; return operation ; } ) ; return batchOperations ; } param { BatchRequest } request param { Models . TableInsertEntityResponse } response return { * } memberof public serializeTableInsertEntityBatchResponse ( request , response ) { let serializedResponses = "" ; serializedResponses = this . SetContentTypeAndEncoding ( serializedResponses ) ; serializedResponses = this . serializeHttpStatusCode ( serializedResponses , response . statusCode ) ; if ( request . contentID !== undefined ) { serializedResponses += "Content-ID: " + request . contentID . toString ( ) + "\r\n" ; } serializedResponses = this . SerializeNoSniffNoCache ( serializedResponses ) ; serializedResponses = this . serializePreferenceApplied ( request , serializedResponses ) ; serializedResponses = this . serializeDataServiceVersion ( serializedResponses , request ) ; serializedResponses += "Location: " + this . SerializeEntityPath ( serializedResponses , request ) ; serializedResponses += "DataServiceId: " + this . SerializeEntityPath ( serializedResponses , request ) ; if ( null !== response . eTag && undefined !== response . eTag ) { serializedResponses += "ETag: " + response . eTag ; } return serializedResponses ; } param { BatchRequest } request param { Models . TableDeleteEntityResponse } response return { * } memberof public serializeTableDeleteEntityBatchResponse ( request , response ) { let serializedResponses = "" ; serializedResponses = this . SetContentTypeAndEncoding ( serializedResponses ) ; serializedResponses = this . serializeHttpStatusCode ( serializedResponses , response . statusCode ) ; serializedResponses = this . SerializeNoSniffNoCache ( serializedResponses ) ; serializedResponses = this . serializeDataServiceVersion ( serializedResponses , request ) ; return serializedResponses ; } param { BatchRequest } request param { Models . TableUpdateEntityResponse } response return { * } memberof public serializeTableUpdateEntityBatchResponse ( request , response ) { let serializedResponses = "" ; serializedResponses = this . SetContentTypeAndEncoding ( serializedResponses ) ; serializedResponses = this . serializeHttpStatusCode ( serializedResponses , response . statusCode ) ; if ( request . contentID ) { serializedResponses += "Content-ID: " + request . contentID . toString ( ) + "\r\n" ; } serializedResponses = this . SerializeNoSniffNoCache ( serializedResponses ) ; serializedResponses = this . serializePreferenceApplied ( request , serializedResponses ) ; serializedResponses = this . serializeDataServiceVersion ( serializedResponses , request ) ; if ( null !== response . eTag && undefined !== response . eTag ) { serializedResponses += "ETag: " + response . eTag ; } return serializedResponses ; } private param { BatchRequest } request param { string } serializedResponses return { * } memberof private serializePreferenceApplied ( request , serializedResponses ) { if ( request . getHeader ( "Preference-Applied" ) ) { serializedResponses += "Preference-Applied: " + request . getHeader ( "Preference-Applied" ) + "\r\n" ; } return serializedResponses ; } param { BatchRequest } request param { Models . TableMergeEntityResponse } response return { * } memberof public serializeTablMergeEntityBatchResponse ( request , response ) { let serializedResponses = "" ; serializedResponses = this . SetContentTypeAndEncoding ( serializedResponses ) ; serializedResponses = this . serializeHttpStatusCode ( serializedResponses , response . statusCode ) ; serializedResponses = this . SerializeNoSniffNoCache ( serializedResponses ) ; if ( request . contentID ) { serializedResponses += "Content-ID: " + request . contentID . toString ( ) + "\r\n" ; } serializedResponses = this . serializeDataServiceVersion ( serializedResponses , request ) ; if ( null !== response . eTag && undefined !== response . eTag ) { serializedResponses += "ETag: " + response . eTag ; } return serializedResponses ; } param { BatchRequest } request param { Models . TableQueryEntitiesWithPartitionAndRowKeyResponse } response return { * } memberof public async serializeTableQueryEntityWithPartitionAndRowKeyBatchResponse ( request , response ) { let serializedResponses = "" ; serializedResponses = this . SetContentTypeAndEncoding ( serializedResponses ) ; serializedResponses = this . serializeHttpStatusCode ( serializedResponses , response . statusCode ) ; serializedResponses = this . serializeDataServiceVersion ( serializedResponses , request ) ; serializedResponses += "Content-Type: " ; serializedResponses += request . params . queryOptions ?. format ; serializedResponses += ";streaming=true;charset=utf-8\r\n" ; serializedResponses = this . SerializeNoSniffNoCache ( serializedResponses ) ; if ( response . eTag ) { serializedResponses += "ETag: " + response . eTag ; } serializedResponses += "\r\n" ; if ( response . body != null ) { try { serializedResponses += await TableBatchUtils . StreamToString ( response . body ) ; } catch { throw new Error ( "failed to deserialize body" ) ; } } serializedResponses += "\r\n" ; return serializedResponses ; } param { BatchRequest } request param { Models . TableQueryEntitiesResponse } response return { * } memberof public async serializeTableQueryEntityBatchResponse ( request , response ) { let serializedResponses = "" ; serializedResponses = this . SetContentTypeAndEncoding ( serializedResponses ) ; serializedResponses = this . serializeHttpStatusCode ( serializedResponses , response . statusCode ) ; serializedResponses = this . serializeDataServiceVersion ( serializedResponses , request ) ; serializedResponses += "Content-Type: " ; serializedResponses += request . params . queryOptions ?. format ; serializedResponses += ";streaming=true;charset=utf-8\r\n" ; serializedResponses = this . SerializeNoSniffNoCache ( serializedResponses ) ; serializedResponses += "\r\n" ; if ( response . body != null ) { try { serializedResponses += await TableBatchUtils . StreamToString ( response . body ) ; } catch { throw new Error ( "failed to deserialize body" ) ; } } serializedResponses += "\r\n" ; return serializedResponses ; } private param { string } serializedResponses return { * } memberof private SetContentTypeAndEncoding ( serializedResponses ) { serializedResponses += "\r\nContent-Type: application/http\r\n" ; serializedResponses += "Content-Transfer-Encoding: binary\r\n" ; serializedResponses += "\r\n" ; return serializedResponses ; } private param { string } serializedResponses return { * } memberof private SerializeNoSniffNoCache ( serializedResponses ) { serializedResponses = this . SerializeXContentTypeOptions ( serializedResponses ) ; serializedResponses += "Cache-Control: no-cache\r\n" ; return serializedResponses ; } private SerializeXContentTypeOptions ( serializedResponses ) { serializedResponses += "X-Content-Type-Options: nosniff\r\n" ; return serializedResponses ; } private param { number } statusCode return { * } memberof private GetStatusMessageString ( statusCode ) { switch ( statusCode ) { case 200 : return "OK" ; case 201 : return "Created" ; case 204 : return "No Content" ; case 404 : return "Not Found" ; case 400 : return "Bad Request" ; default : return "STATUS_CODE_NOT_IMPLEMENTED" ; } } private param { string } batchRequestsString param { string } regExPattern return { * } memberof private extractRequestHeaderString ( batchRequestsString , regExPattern ) { const headerStringMatches = batchRequestsString . match ( regExPattern ) ; if ( headerStringMatches == null ) { throw StorageError ; } return headerStringMatches [ 2 ] ; } private param { string } serializedResponses param { * } response return { * } memberof private serializeHttpStatusCode ( serializedResponses , statusCode ) { serializedResponses += "HTTP/1.1 " + statusCode . toString ( ) + " " + this . GetStatusMessageString ( statusCode ) + "\r\n" ; return serializedResponses ; } private param { string } serializedResponses param { BatchRequest } request return { string } memberof private SerializeEntityPath ( serializedResponses , request ) { let parenthesesPosition = request . getUrl ( ) . indexOf ( "(" ) ; parenthesesPosition -- ; if ( parenthesesPosition < 0 ) { parenthesesPosition = request . getUrl ( ) . length ; } const trimmedUrl = request . getUrl ( ) . substring ( 0 , parenthesesPosition ) ; let entityPath = trimmedUrl + "(PartitionKey='" ; entityPath += request . params . tableEntityProperties ! . PartitionKey ; entityPath += "'," ; entityPath += "RowKey='" ; entityPath += request . params . tableEntityProperties ! . RowKey ; entityPath += "')\r\n" ; return entityPath ; } private param { BatchRequest } request param { string } serializedResponses return { * } memberof private serializeDataServiceVersion ( serializedResponses , request ) { if ( undefined !== request && undefined !== request . params && request . params . dataServiceVersion ) { serializedResponses += "DataServiceVersion: " + request . params . dataServiceVersion + ";\r\n" ; } else { serializedResponses += "DataServiceVersion: 3.0;\r\n" ; } return serializedResponses ; } private param { * } err return { * } memberof public serializeError ( err , contentID , request ) { let errorReponse = "" ; const odataError = err as StorageError ; errorReponse = this . serializeHttpStatusCode ( errorReponse , 400 ) ; errorReponse += "Content-ID: " + contentID + "\r\n" ; errorReponse = this . serializeDataServiceVersion ( errorReponse , request ) ; errorReponse += "Content-Type: application/json;odata=minimalmetadata;charset=utf-8\r\n" ; errorReponse += "\r\n" ; errorReponse += odataError . body + "\r\n" ; return errorReponse ; } param { string } odataErrorString param { ( string | undefined ) } requestId return { * } memberof public serializeGeneralRequestError ( odataErrorString , requestId ) { const changesetBoundary = this . changesetBoundary . replace ( "changeset" , "changesetresponse" ) ; let errorReponse = "" ; errorReponse += changesetBoundary + "\r\n" ; errorReponse = this . serializeHttpStatusCode ( errorReponse , 400 ) ; errorReponse = this . SerializeXContentTypeOptions ( errorReponse ) ; errorReponse = this . serializeDataServiceVersion ( errorReponse , undefined ) ; errorReponse += "Content-Type: application/json;odata=minimalmetadata;charset=utf-8\r\n" ; errorReponse += "\r\n" ; let requestIdResponseString = "" ; if ( requestId !== undefined ) { requestIdResponseString = `RequestId:${ requestId }\\n` ; } const date = truncatedISO8061Date ( new Date ( ) , true ) ; errorReponse += `{\"odata.error\":{\"code\":\"InvalidInput\",\"message\":{\"lang\":\"en-US\",\"value\":\"${ odataErrorString }\\n${ requestIdResponseString }Time:${ date }\"}}}\r\n` ; return errorReponse ; } }