@@ -33,6 +33,7 @@ public typealias WordPressComRestApiError = WordPressComRestApiErrorCode
3333
3434public struct WordPressComRestApiEndpointError : Error {
3535 public var code : WordPressComRestApiErrorCode
36+ var response : HTTPURLResponse ?
3637
3738 public var apiErrorCode : String ?
3839 public var apiErrorMessage : String ?
@@ -47,6 +48,12 @@ extension WordPressComRestApiEndpointError: LocalizedError {
4748 }
4849}
4950
51+ extension WordPressComRestApiEndpointError : HTTPURLResponseProviding {
52+ var httpResponse : HTTPURLResponse ? {
53+ response
54+ }
55+ }
56+
5057public enum ResponseType {
5158 case json
5259 case data
@@ -69,6 +76,7 @@ open class WordPressComRestApi: NSObject {
6976 public typealias RequestEnqueuedBlock = ( _ taskID: NSNumber ) -> Void
7077 public typealias SuccessResponseBlock = ( _ responseObject: AnyObject , _ httpResponse: HTTPURLResponse ? ) -> Void
7178 public typealias FailureReponseBlock = ( _ error: NSError , _ httpResponse: HTTPURLResponse ? ) -> Void
79+ public typealias APIResult < T> = WordPressAPIResult < HTTPAPIResponse < T > , WordPressComRestApiEndpointError >
7280
7381 @objc public static let apiBaseURL : URL = URL ( string: " https://public-api.wordpress.com/ " ) !
7482
@@ -412,6 +420,21 @@ open class WordPressComRestApi: NSObject {
412420 return urlComponentsWithLocale? . url? . absoluteString
413421 }
414422
423+ private func requestBuilder( URLString: String ) throws -> HTTPRequestBuilder {
424+ guard let url = URL ( string: URLString, relativeTo: baseURL) else {
425+ throw URLError ( . badURL)
426+ }
427+
428+ var builder = HTTPRequestBuilder ( url: url)
429+
430+ if appendsPreferredLanguageLocale {
431+ let preferredLanguageIdentifier = WordPressComLanguageDatabase ( ) . deviceLanguage. slug
432+ builder = builder. query ( defaults: [ URLQueryItem ( name: localeKey, value: preferredLanguageIdentifier) ] )
433+ }
434+
435+ return builder
436+ }
437+
415438 private func applyLocaleIfNeeded( urlComponents: URLComponents , parameters: [ String : AnyObject ] ? = [ : ] , localeKey: String ) -> URLComponents ? {
416439 guard appendsPreferredLanguageLocale else {
417440 return urlComponents
@@ -459,6 +482,88 @@ open class WordPressComRestApi: NSObject {
459482 return URLSession ( configuration: configuration)
460483 } ( )
461484
485+ func perform(
486+ _ method: HTTPRequestBuilder . Method ,
487+ URLString: String ,
488+ parameters: [ String : AnyObject ] ? = nil ,
489+ fulfilling progress: Progress ? = nil
490+ ) async -> APIResult < AnyObject > {
491+ await perform ( method, URLString: URLString, parameters: parameters, fulfilling: progress) {
492+ try ( JSONSerialization . jsonObject ( with: $0) as AnyObject )
493+ }
494+ }
495+
496+ func perform< T: Decodable > (
497+ _ method: HTTPRequestBuilder . Method ,
498+ URLString: String ,
499+ parameters: [ String : AnyObject ] ? = nil ,
500+ fulfilling progress: Progress ? = nil ,
501+ jsonDecoder: JSONDecoder ? = nil ,
502+ type: T . Type = T . self
503+ ) async -> APIResult < T > {
504+ await perform ( method, URLString: URLString, parameters: parameters, fulfilling: progress) {
505+ let decoder = jsonDecoder ?? JSONDecoder ( )
506+ return try decoder. decode ( type, from: $0)
507+ }
508+ }
509+
510+ private func perform< T> (
511+ _ method: HTTPRequestBuilder . Method ,
512+ URLString: String ,
513+ parameters: [ String : AnyObject ] ? ,
514+ fulfilling progress: Progress ? ,
515+ decoder: @escaping ( Data ) throws -> T
516+ ) async -> APIResult < T > {
517+ var builder : HTTPRequestBuilder
518+ do {
519+ builder = try requestBuilder ( URLString: URLString)
520+ . method ( method)
521+ } catch {
522+ return . failure( . requestEncodingFailure( underlyingError: error) )
523+ }
524+
525+ if let parameters {
526+ if builder. method. allowsHTTPBody {
527+ builder = builder. body ( json: parameters as Any )
528+ } else {
529+ builder = builder. query ( parameters)
530+ }
531+ }
532+
533+ return await perform ( request: builder, fulfilling: progress, decoder: decoder)
534+ }
535+
536+ private func perform< T> (
537+ request: HTTPRequestBuilder ,
538+ fulfilling progress: Progress ? ,
539+ decoder: @escaping ( Data ) throws -> T
540+ ) async -> APIResult < T > {
541+ await self . urlSession
542+ . perform ( request: request, fulfilling: progress, errorType: WordPressComRestApiEndpointError . self)
543+ . mapSuccess { response -> HTTPAPIResponse < T > in
544+ let object = try decoder ( response. body)
545+
546+ return HTTPAPIResponse ( response: response. response, body: object)
547+ }
548+ . mapUnacceptableStatusCodeError { response, body in
549+ if let error = self . processError ( response: response, body: body, additionalUserInfo: nil ) {
550+ return error
551+ }
552+
553+ throw URLError ( . cannotParseResponse)
554+ }
555+ . mapError { error -> WordPressAPIError < WordPressComRestApiEndpointError > in
556+ switch error {
557+ case . requestEncodingFailure:
558+ return . endpointError( . init( code: . requestSerializationFailed) )
559+ case let . unparsableResponse( response, _, _) :
560+ return . endpointError( . init( code: . responseSerializationFailed, response: response) )
561+ default :
562+ return error
563+ }
564+ }
565+ }
566+
462567}
463568
464569// MARK: - FilePart
@@ -485,7 +590,7 @@ extension WordPressComRestApi {
485590 /// A custom error processor to handle error responses when status codes are betwen 400 and 500
486591 func processError( response: DataResponse < Any > , originalError: Error ) -> WordPressComRestApiEndpointError ? {
487592 if let afError = originalError as? AFError , case AFError . responseSerializationFailed( _) = afError {
488- return . init( code: . responseSerializationFailed)
593+ return . init( code: . responseSerializationFailed, response : response . response )
489594 }
490595
491596 guard let httpResponse = response. response, let data = response. data else {
@@ -505,16 +610,16 @@ extension WordPressComRestApi {
505610 guard let responseObject = try ? JSONSerialization . jsonObject ( with: data, options: . allowFragments) ,
506611 let responseDictionary = responseObject as? [ String : AnyObject ] else {
507612
508- if let error = checkForThrottleErrorIn ( data: data) {
613+ if let error = checkForThrottleErrorIn ( response : httpResponse , data: data) {
509614 return error
510615 }
511- return . init( code: . unknown)
616+ return . init( code: . unknown, response : httpResponse )
512617 }
513618
514619 // FIXME: A hack to support free WPCom sites and Rewind. Should be obsolote as soon as the backend
515620 // stops returning 412's for those sites.
516621 if httpResponse. statusCode == 412 , let code = responseDictionary [ " code " ] as? String , code == " no_connected_jetpack " {
517- return . init( code: . preconditionFailure)
622+ return . init( code: . preconditionFailure, response : httpResponse )
518623 }
519624
520625 var errorDictionary : AnyObject ? = responseDictionary as AnyObject ?
@@ -525,7 +630,7 @@ extension WordPressComRestApi {
525630 let errorCode = errorEntry [ " error " ] as? String ,
526631 let errorDescription = errorEntry [ " message " ] as? String
527632 else {
528- return . init( code: . unknown)
633+ return . init( code: . unknown, response : httpResponse )
529634 }
530635
531636 let errorsMap : [ String : WordPressComRestApiErrorCode ] = [
@@ -557,7 +662,7 @@ extension WordPressComRestApi {
557662 )
558663 }
559664
560- func checkForThrottleErrorIn( data: Data ) -> WordPressComRestApiEndpointError ? {
665+ func checkForThrottleErrorIn( response : HTTPURLResponse , data: Data ) -> WordPressComRestApiEndpointError ? {
561666 // This endpoint is throttled, so check if we've sent too many requests and fill that error in as
562667 // when too many requests occur the API just spits out an html page.
563668 guard let responseString = String ( data: data, encoding: . utf8) ,
0 commit comments