From 96ae82f62e13909ff80ecdd05583f9177e39b4da Mon Sep 17 00:00:00 2001
From: Gio Lodi <gio@mokacoding.com>
Date: Wed, 10 Apr 2024 08:04:54 +1000
Subject: [PATCH 01/18] Remove an `enum` builder in favor of default associated
 value

---
 Sources/WordPressKit/WordPressAPI/WordPressAPIError.swift | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/Sources/WordPressKit/WordPressAPI/WordPressAPIError.swift b/Sources/WordPressKit/WordPressAPI/WordPressAPIError.swift
index a4680b208..5a6c3d5f0 100644
--- a/Sources/WordPressKit/WordPressAPI/WordPressAPIError.swift
+++ b/Sources/WordPressKit/WordPressAPI/WordPressAPIError.swift
@@ -18,13 +18,10 @@ public enum WordPressAPIError<EndpointError>: Error where EndpointError: Localiz
     /// The API call returned an status code that's unacceptable to the endpoint.
     case unacceptableStatusCode(response: HTTPURLResponse, body: Data)
     /// The API call returned an HTTP response that WordPressKit can't parse. Receiving this error could be an indicator that there is an error response that's not handled properly by WordPressKit.
-    case unparsableResponse(response: HTTPURLResponse?, body: Data?, underlyingError: Error)
+    case unparsableResponse(response: HTTPURLResponse?, body: Data?, underlyingError: Error = URLError(.cannotParseResponse))
     /// Other error occured.
     case unknown(underlyingError: Error)
 
-    static func unparsableResponse(response: HTTPURLResponse?, body: Data?) -> Self {
-        return WordPressAPIError<EndpointError>.unparsableResponse(response: response, body: body, underlyingError: URLError(.cannotParseResponse))
-    }
 
     var response: HTTPURLResponse? {
         switch self {

From d17587a310bb41e15b5f0a666d7ae93b156f7242 Mon Sep 17 00:00:00 2001
From: Gio Lodi <gio@mokacoding.com>
Date: Wed, 10 Apr 2024 07:46:23 +1000
Subject: [PATCH 02/18] Remove `PostServiceRemoteExtended` and implementations

They will be moved into WordPress and Jetpack.

The reason for this is that they are only used in WordPress and Jetpack
and that they are Swift extensions of Objective-C types which we are
trying to isolate.

It's simpler to move these leaf types into the only consumer that uses
them than trying to re-architect them.
---
 .../Services/PostServiceRemoteExtended.swift  | 25 ------
 .../PostServiceRemoteREST+Extended.swift      | 67 ----------------
 .../PostServiceRemoteXMLRPC+Extended.swift    | 77 -------------------
 WordPressKit.xcodeproj/project.pbxproj        | 12 ---
 4 files changed, 181 deletions(-)
 delete mode 100644 Sources/WordPressKit/Services/PostServiceRemoteExtended.swift
 delete mode 100644 Sources/WordPressKit/Services/PostServiceRemoteREST+Extended.swift
 delete mode 100644 Sources/WordPressKit/Services/PostServiceRemoteXMLRPC+Extended.swift

diff --git a/Sources/WordPressKit/Services/PostServiceRemoteExtended.swift b/Sources/WordPressKit/Services/PostServiceRemoteExtended.swift
deleted file mode 100644
index fb7ed17ec..000000000
--- a/Sources/WordPressKit/Services/PostServiceRemoteExtended.swift
+++ /dev/null
@@ -1,25 +0,0 @@
-import Foundation
-
-public protocol PostServiceRemoteExtended: PostServiceRemote {
-    /// Creates a new post with the given parameters.
-    func createPost(with parameters: RemotePostCreateParameters) async throws -> RemotePost
-
-    /// Performs a partial update to the existing post.
-    ///
-    /// - throws: ``PostServiceRemoteUpdatePostError`` or oher underlying errors
-    /// (see ``WordPressAPIError``)
-    func patchPost(withID postID: Int, parameters: RemotePostUpdateParameters) async throws -> RemotePost
-
-    /// Permanently deletes a post with the given ID.
-    ///
-    /// - throws: ``PostServiceRemoteUpdatePostError`` or oher underlying errors
-    /// (see ``WordPressAPIError``)
-    func deletePost(withID postID: Int) async throws
-}
-
-public enum PostServiceRemoteUpdatePostError: Error {
-    /// 409 (Conflict)
-    case conflict
-    /// 404 (Not Found)
-    case notFound
-}
diff --git a/Sources/WordPressKit/Services/PostServiceRemoteREST+Extended.swift b/Sources/WordPressKit/Services/PostServiceRemoteREST+Extended.swift
deleted file mode 100644
index 6637a7850..000000000
--- a/Sources/WordPressKit/Services/PostServiceRemoteREST+Extended.swift
+++ /dev/null
@@ -1,67 +0,0 @@
-import Foundation
-
-extension PostServiceRemoteREST: PostServiceRemoteExtended {
-    public func createPost(with parameters: RemotePostCreateParameters) async throws -> RemotePost {
-        let path = self.path(forEndpoint: "sites/\(siteID)/posts/new?context=edit", withVersion: ._1_2)
-        let parameters = try makeParameters(from: RemotePostCreateParametersWordPressComEncoder(parameters: parameters))
-
-        let response = try await wordPressComRestApi.perform(.post, URLString: path, parameters: parameters).get()
-        return try await decodePost(from: response.body)
-    }
-
-    public func patchPost(withID postID: Int, parameters: RemotePostUpdateParameters) async throws -> RemotePost {
-        let path = self.path(forEndpoint: "sites/\(siteID)/posts/\(postID)?context=edit", withVersion: ._1_2)
-        let parameters = try makeParameters(from: RemotePostUpdateParametersWordPressComEncoder(parameters: parameters))
-
-        let result = await wordPressComRestApi.perform(.post, URLString: path, parameters: parameters)
-        switch result {
-        case .success(let response):
-            return try await decodePost(from: response.body)
-        case .failure(let error):
-            guard case .endpointError(let error) = error else {
-                throw error
-            }
-            switch error.apiErrorCode ?? "" {
-            case "unknown_post": throw PostServiceRemoteUpdatePostError.notFound
-            case "old-revision": throw PostServiceRemoteUpdatePostError.conflict
-            default: throw error
-            }
-        }
-    }
-
-    public func deletePost(withID postID: Int) async throws {
-        let path = self.path(forEndpoint: "sites/\(siteID)/posts/\(postID)/delete", withVersion: ._1_1)
-        let result = await wordPressComRestApi.perform(.post, URLString: path)
-        switch result {
-        case .success:
-            return
-        case .failure(let error):
-            guard case .endpointError(let error) = error else {
-                throw error
-            }
-            switch error.apiErrorCode ?? "" {
-            case "unknown_post": throw PostServiceRemoteUpdatePostError.notFound
-            default: throw error
-            }
-        }
-    }
-}
-
-// Decodes the post in the background.
-private func decodePost(from object: AnyObject) async throws -> RemotePost {
-    guard let dictionary = object as? [AnyHashable: Any] else {
-        throw WordPressAPIError<WordPressComRestApiEndpointError>.unparsableResponse(response: nil, body: nil)
-    }
-    return PostServiceRemoteREST.remotePost(fromJSONDictionary: dictionary)
-}
-
-private func makeParameters<T: Encodable>(from value: T) throws -> [String: AnyObject] {
-    let encoder = JSONEncoder()
-    encoder.dateEncodingStrategy = .formatted(.wordPressCom)
-    let data = try encoder.encode(value)
-    let object = try JSONSerialization.jsonObject(with: data)
-    guard let dictionary = object as? [String: AnyObject] else {
-        throw URLError(.unknown) // This should never happen
-    }
-    return dictionary
-}
diff --git a/Sources/WordPressKit/Services/PostServiceRemoteXMLRPC+Extended.swift b/Sources/WordPressKit/Services/PostServiceRemoteXMLRPC+Extended.swift
deleted file mode 100644
index b1bccf1a8..000000000
--- a/Sources/WordPressKit/Services/PostServiceRemoteXMLRPC+Extended.swift
+++ /dev/null
@@ -1,77 +0,0 @@
-import Foundation
-import wpxmlrpc
-
-extension PostServiceRemoteXMLRPC: PostServiceRemoteExtended {
-    public func createPost(with parameters: RemotePostCreateParameters) async throws -> RemotePost {
-        let dictionary = try makeParameters(from: RemotePostCreateParametersXMLRPCEncoder(parameters: parameters))
-        let parameters = xmlrpcArguments(withExtra: dictionary) as [AnyObject]
-        let response = try await api.call(method: "metaWeblog.newPost", parameters: parameters).get()
-        guard let postID = (response.body as? NSObject)?.numericValue() else {
-            throw URLError(.unknown) // Should never happen
-        }
-        return try await getPost(withID: postID)
-    }
-
-    public func patchPost(withID postID: Int, parameters: RemotePostUpdateParameters) async throws -> RemotePost {
-        let dictionary = try makeParameters(from: RemotePostUpdateParametersXMLRPCEncoder(parameters: parameters))
-        var parameters = xmlrpcArguments(withExtra: dictionary) as [AnyObject]
-        if parameters.count > 0 {
-            parameters[0] = postID as NSNumber
-        }
-        let result = await api.call(method: "metaWeblog.editPost", parameters: parameters)
-        switch result {
-        case .success:
-            return try await getPost(withID: postID as NSNumber)
-        case .failure(let error):
-            guard case .endpointError(let error) = error else {
-                throw error
-            }
-            switch error.code ?? 0 {
-            case 404: throw PostServiceRemoteUpdatePostError.notFound
-            case 409: throw PostServiceRemoteUpdatePostError.conflict
-            default: throw error
-            }
-        }
-    }
-
-    public func deletePost(withID postID: Int) async throws {
-        let parameters = xmlrpcArguments(withExtra: postID) as [AnyObject]
-        let result = await api.call(method: "wp.deletePost", parameters: parameters)
-        switch result {
-        case .success:
-            return
-        case .failure(let error):
-            guard case .endpointError(let error) = error else {
-                throw error
-            }
-            switch error.code ?? 0 {
-            case 404: throw PostServiceRemoteUpdatePostError.notFound
-            default: throw error
-            }
-        }
-    }
-
-    private func getPost(withID postID: NSNumber) async throws -> RemotePost {
-        try await withUnsafeThrowingContinuation { continuation in
-            getPostWithID(postID) { post in
-                guard let post else {
-                    return continuation.resume(throwing: URLError(.unknown)) // Should never happen
-                }
-                continuation.resume(returning: post)
-            } failure: { error in
-                continuation.resume(throwing: error ?? URLError(.unknown))
-            }
-        }
-    }
-}
-
-private func makeParameters<T: Encodable>(from value: T) throws -> [String: AnyObject] {
-    let encoder = PropertyListEncoder()
-    encoder.outputFormat = .xml
-    let data = try encoder.encode(value)
-    let object = try PropertyListSerialization.propertyList(from: data, format: nil)
-    guard let dictionary = object as? [String: AnyObject] else {
-        throw URLError(.unknown) // This should never happen
-    }
-    return dictionary
-}
diff --git a/WordPressKit.xcodeproj/project.pbxproj b/WordPressKit.xcodeproj/project.pbxproj
index 393460ce4..7a663384c 100644
--- a/WordPressKit.xcodeproj/project.pbxproj
+++ b/WordPressKit.xcodeproj/project.pbxproj
@@ -13,9 +13,6 @@
 		0152100C28EDA9E400DD6783 /* StatsAnnualAndMostPopularTimeInsightDecodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0152100B28EDA9E400DD6783 /* StatsAnnualAndMostPopularTimeInsightDecodingTests.swift */; };
 		0847B92C2A4442730044D32F /* IPLocationRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0847B92B2A4442730044D32F /* IPLocationRemote.swift */; };
 		08C7493E2A45EA11000DA0E2 /* IPLocationRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08C7493D2A45EA11000DA0E2 /* IPLocationRemoteTests.swift */; };
-		0C1C08412B9CD79900E52F8C /* PostServiceRemoteExtended.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C1C08402B9CD79900E52F8C /* PostServiceRemoteExtended.swift */; };
-		0C1C08432B9CD8D200E52F8C /* PostServiceRemoteREST+Extended.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C1C08422B9CD8D200E52F8C /* PostServiceRemoteREST+Extended.swift */; };
-		0C1C08452B9CDB0B00E52F8C /* PostServiceRemoteXMLRPC+Extended.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C1C08442B9CDB0B00E52F8C /* PostServiceRemoteXMLRPC+Extended.swift */; };
 		0C9CD7992B9A107E0045BE03 /* RemotePostParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C9CD7982B9A107E0045BE03 /* RemotePostParameters.swift */; };
 		0CB1905E2A2A5E83004D3E80 /* BlazeCampaign.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CB1905D2A2A5E83004D3E80 /* BlazeCampaign.swift */; };
 		0CB190612A2A6A13004D3E80 /* blaze-campaigns-search.json in Resources */ = {isa = PBXBuildFile; fileRef = 0CB1905F2A2A6943004D3E80 /* blaze-campaigns-search.json */; };
@@ -757,9 +754,6 @@
 		0152100B28EDA9E400DD6783 /* StatsAnnualAndMostPopularTimeInsightDecodingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatsAnnualAndMostPopularTimeInsightDecodingTests.swift; sourceTree = "<group>"; };
 		0847B92B2A4442730044D32F /* IPLocationRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPLocationRemote.swift; sourceTree = "<group>"; };
 		08C7493D2A45EA11000DA0E2 /* IPLocationRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPLocationRemoteTests.swift; sourceTree = "<group>"; };
-		0C1C08402B9CD79900E52F8C /* PostServiceRemoteExtended.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostServiceRemoteExtended.swift; sourceTree = "<group>"; };
-		0C1C08422B9CD8D200E52F8C /* PostServiceRemoteREST+Extended.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PostServiceRemoteREST+Extended.swift"; sourceTree = "<group>"; };
-		0C1C08442B9CDB0B00E52F8C /* PostServiceRemoteXMLRPC+Extended.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PostServiceRemoteXMLRPC+Extended.swift"; sourceTree = "<group>"; };
 		0C3A2A412A2E7BA500FD91D6 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = "<group>"; };
 		0C9CD7982B9A107E0045BE03 /* RemotePostParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemotePostParameters.swift; sourceTree = "<group>"; };
 		0CB1905D2A2A5E83004D3E80 /* BlazeCampaign.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazeCampaign.swift; sourceTree = "<group>"; };
@@ -1779,15 +1773,12 @@
 				E1BD95141FD5A2B800CD5CE3 /* PluginDirectoryServiceRemote.swift */,
 				E13EE1461F33258E00C15787 /* PluginServiceRemote.swift */,
 				740B23B21F17EC7300067A2A /* PostServiceRemote.h */,
-				0C1C08402B9CD79900E52F8C /* PostServiceRemoteExtended.swift */,
 				740B23BC1F17ECB500067A2A /* PostServiceRemoteOptions.h */,
 				740B23B31F17EC7300067A2A /* PostServiceRemoteREST.h */,
 				740B23B41F17EC7300067A2A /* PostServiceRemoteREST.m */,
-				0C1C08422B9CD8D200E52F8C /* PostServiceRemoteREST+Extended.swift */,
 				9AF4F2FB218331DC00570E4B /* PostServiceRemoteREST+Revisions.swift */,
 				740B23B51F17EC7300067A2A /* PostServiceRemoteXMLRPC.h */,
 				740B23B61F17EC7300067A2A /* PostServiceRemoteXMLRPC.m */,
-				0C1C08442B9CDB0B00E52F8C /* PostServiceRemoteXMLRPC+Extended.swift */,
 				F181EA0127184D3C00F26141 /* ProductServiceRemote.swift */,
 				74A44DCA1F13C533006CD8F4 /* PushAuthenticationServiceRemote.swift */,
 				C7A09A4F284104DB003096ED /* QR Login */,
@@ -3342,18 +3333,15 @@
 				436D56352118D85800CEAA33 /* WPCountry.swift in Sources */,
 				74A44DCB1F13C533006CD8F4 /* NotificationSettingsServiceRemote.swift in Sources */,
 				FAD1344525908F5F00A8FEB1 /* JetpackBackupServiceRemote.swift in Sources */,
-				0C1C08452B9CDB0B00E52F8C /* PostServiceRemoteXMLRPC+Extended.swift in Sources */,
 				F1BB7806240FB90B0030ADDC /* AtomicAuthenticationServiceRemote.swift in Sources */,
 				404057CE221C38130060250C /* StatsTopVideosTimeIntervalData.swift in Sources */,
 				7E0D64FF22D855700092AD10 /* EditorServiceRemote.swift in Sources */,
-				0C1C08412B9CD79900E52F8C /* PostServiceRemoteExtended.swift in Sources */,
 				9AF4F2FF2183346B00570E4B /* RemoteRevision.swift in Sources */,
 				FE6C673A2BB739950083ECAB /* Decodable+Dictionary.swift in Sources */,
 				17D936252475D8AB008B2205 /* RemoteHomepageType.swift in Sources */,
 				74BA04F41F06DC0A00ED5CD8 /* CommentServiceRemoteREST.m in Sources */,
 				74C473AC1EF2F75E009918F2 /* SiteManagementServiceRemote.swift in Sources */,
 				74585B971F0D54B400E7E667 /* RemoteDomain.swift in Sources */,
-				0C1C08432B9CD8D200E52F8C /* PostServiceRemoteREST+Extended.swift in Sources */,
 				3FFCC0492BAB98130051D229 /* DateFormatter+WordPressCom.swift in Sources */,
 				74A44DD01F13C64B006CD8F4 /* RemoteNotification.swift in Sources */,
 				E1D6B558200E473A00325669 /* TimeZoneServiceRemote.swift in Sources */,

From 58a63866fd064c2760cc4649880a0a3aed190c17 Mon Sep 17 00:00:00 2001
From: Gio Lodi <gio@mokacoding.com>
Date: Wed, 10 Apr 2024 08:03:53 +1000
Subject: [PATCH 03/18] Update access control to components called by removed
 types

---
 .../Models/RemotePostParameters.swift         | 32 ++++++++++++++-----
 .../DateFormatter+WordPressCom.swift          |  2 +-
 .../WordPressAPI/HTTPRequestBuilder.swift     |  4 +--
 .../WordPressAPI/WordPressComRestApi.swift    |  2 +-
 .../WordPressAPI/WordPressOrgXMLRPCApi.swift  |  2 +-
 5 files changed, 29 insertions(+), 13 deletions(-)

diff --git a/Sources/WordPressKit/Models/RemotePostParameters.swift b/Sources/WordPressKit/Models/RemotePostParameters.swift
index 81849b09a..f1abb0bba 100644
--- a/Sources/WordPressKit/Models/RemotePostParameters.swift
+++ b/Sources/WordPressKit/Models/RemotePostParameters.swift
@@ -177,10 +177,14 @@ private enum RemotePostWordPressComCodingKeys: String, CodingKey {
     static let postTags = "post_tag"
 }
 
-struct RemotePostCreateParametersWordPressComEncoder: Encodable {
+public struct RemotePostCreateParametersWordPressComEncoder: Encodable {
     let parameters: RemotePostCreateParameters
 
-    func encode(to encoder: Encoder) throws {
+    public init(parameters: RemotePostCreateParameters) {
+        self.parameters = parameters
+    }
+
+    public func encode(to encoder: Encoder) throws {
         var container = encoder.container(keyedBy: RemotePostWordPressComCodingKeys.self)
         try container.encodeIfPresent(parameters.type, forKey: .type)
         try container.encodeIfPresent(parameters.status, forKey: .status)
@@ -212,10 +216,14 @@ struct RemotePostCreateParametersWordPressComEncoder: Encodable {
     }
 }
 
-struct RemotePostUpdateParametersWordPressComEncoder: Encodable {
+public struct RemotePostUpdateParametersWordPressComEncoder: Encodable {
     let parameters: RemotePostUpdateParameters
 
-    func encode(to encoder: Encoder) throws {
+    public init(parameters: RemotePostUpdateParameters) {
+        self.parameters = parameters
+    }
+
+    public func encode(to encoder: Encoder) throws {
         var container = encoder.container(keyedBy: RemotePostWordPressComCodingKeys.self)
         try container.encodeIfPresent(parameters.ifNotModifiedSince, forKey: .ifNotModifiedSince)
 
@@ -274,10 +282,14 @@ private enum RemotePostXMLRPCCodingKeys: String, CodingKey {
     static let postTags = "post_tag"
 }
 
-struct RemotePostCreateParametersXMLRPCEncoder: Encodable {
+public struct RemotePostCreateParametersXMLRPCEncoder: Encodable {
     let parameters: RemotePostCreateParameters
 
-    func encode(to encoder: Encoder) throws {
+    public init(parameters: RemotePostCreateParameters) {
+        self.parameters = parameters
+    }
+
+    public func encode(to encoder: Encoder) throws {
         var container = encoder.container(keyedBy: RemotePostXMLRPCCodingKeys.self)
         try container.encode(parameters.type, forKey: .type)
         try container.encodeIfPresent(parameters.status, forKey: .postStatus)
@@ -309,10 +321,14 @@ struct RemotePostCreateParametersXMLRPCEncoder: Encodable {
     }
 }
 
-struct RemotePostUpdateParametersXMLRPCEncoder: Encodable {
+public struct RemotePostUpdateParametersXMLRPCEncoder: Encodable {
     let parameters: RemotePostUpdateParameters
 
-    func encode(to encoder: Encoder) throws {
+    public init(parameters: RemotePostUpdateParameters) {
+        self.parameters = parameters
+    }
+
+    public func encode(to encoder: Encoder) throws {
         var container = encoder.container(keyedBy: RemotePostXMLRPCCodingKeys.self)
         try container.encodeIfPresent(parameters.ifNotModifiedSince, forKey: .ifNotModifiedSince)
         try container.encodeIfPresent(parameters.status, forKey: .postStatus)
diff --git a/Sources/WordPressKit/WordPressAPI/DateFormatter+WordPressCom.swift b/Sources/WordPressKit/WordPressAPI/DateFormatter+WordPressCom.swift
index 1bb03baf0..a20a1775e 100644
--- a/Sources/WordPressKit/WordPressAPI/DateFormatter+WordPressCom.swift
+++ b/Sources/WordPressKit/WordPressAPI/DateFormatter+WordPressCom.swift
@@ -3,7 +3,7 @@ extension DateFormatter {
     /// A `DateFormatter` configured to manage dates compatible with the WordPress.com API.
     ///
     /// - SeeAlso: [https://developer.wordpress.com/docs/api/](https://developer.wordpress.com/docs/api/)
-    static let wordPressCom: DateFormatter = {
+    public static let wordPressCom: DateFormatter = {
         let formatter = DateFormatter()
         formatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"
         formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0) as TimeZone
diff --git a/Sources/WordPressKit/WordPressAPI/HTTPRequestBuilder.swift b/Sources/WordPressKit/WordPressAPI/HTTPRequestBuilder.swift
index c53cf9ca2..bb26a9072 100644
--- a/Sources/WordPressKit/WordPressAPI/HTTPRequestBuilder.swift
+++ b/Sources/WordPressKit/WordPressAPI/HTTPRequestBuilder.swift
@@ -5,8 +5,8 @@ import wpxmlrpc
 ///
 /// Calling this class's url related functions (the ones that changes path, query, etc) does not modify the
 /// original URL string. The URL will be perserved in the final result that's returned by the `build` function.
-final class HTTPRequestBuilder {
-    enum Method: String, CaseIterable {
+public final class HTTPRequestBuilder {
+    public enum Method: String, CaseIterable {
         case get = "GET"
         case post = "POST"
         case put = "PUT"
diff --git a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
index a0b9d6058..64e875add 100644
--- a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
+++ b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
@@ -360,7 +360,7 @@ open class WordPressComRestApi: NSObject {
         return configuration
     }
 
-    func perform(
+    public func perform(
         _ method: HTTPRequestBuilder.Method,
         URLString: String,
         parameters: [String: AnyObject]? = nil,
diff --git a/Sources/WordPressKit/WordPressAPI/WordPressOrgXMLRPCApi.swift b/Sources/WordPressKit/WordPressAPI/WordPressOrgXMLRPCApi.swift
index 2698c89f5..4bb825eb7 100644
--- a/Sources/WordPressKit/WordPressAPI/WordPressOrgXMLRPCApi.swift
+++ b/Sources/WordPressKit/WordPressAPI/WordPressOrgXMLRPCApi.swift
@@ -180,7 +180,7 @@ open class WordPressOrgXMLRPCApi: NSObject {
     /// - Parameters:
     ///   - streaming: set to `true` if there are large data (i.e. uploading files) in given `parameters`. `false` by default.
     /// - Returns: A `Result` type that contains the XMLRPC success or failure result.
-    func call(method: String, parameters: [AnyObject]?, fulfilling progress: Progress? = nil, streaming: Bool = false) async -> WordPressAPIResult<HTTPAPIResponse<AnyObject>, WordPressOrgXMLRPCApiFault> {
+    public func call(method: String, parameters: [AnyObject]?, fulfilling progress: Progress? = nil, streaming: Bool = false) async -> WordPressAPIResult<HTTPAPIResponse<AnyObject>, WordPressOrgXMLRPCApiFault> {
         let session = streaming ? uploadURLSession : urlSession
         let builder = HTTPRequestBuilder(url: endpoint)
             .method(.post)

From 19bbd29b298c50df88923849c2a2e531b8c68a6e Mon Sep 17 00:00:00 2001
From: Gio Lodi <gio@mokacoding.com>
Date: Wed, 10 Apr 2024 08:30:22 +1000
Subject: [PATCH 04/18] Move `upload(...` definition before `perform(...`

This is just a convenience refactor to make future diff clearer
---
 .../WordPressAPI/WordPressComRestApi.swift    | 65 +++++++++----------
 1 file changed, 32 insertions(+), 33 deletions(-)

diff --git a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
index 64e875add..5fe6d2883 100644
--- a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
+++ b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
@@ -360,6 +360,38 @@ open class WordPressComRestApi: NSObject {
         return configuration
     }
 
+    public func upload(
+        URLString: String,
+        parameters: [String: AnyObject]?,
+        fileParts: [FilePart],
+        requestEnqueued: RequestEnqueuedBlock? = nil,
+        fulfilling progress: Progress? = nil
+    ) async -> APIResult<AnyObject> {
+        let builder: HTTPRequestBuilder
+        do {
+            let form = try fileParts.map {
+                try MultipartFormField(fileAtPath: $0.url.path, name: $0.parameterName, filename: $0.fileName, mimeType: $0.mimeType)
+            }
+            builder = try requestBuilder(URLString: URLString)
+                .method(.post)
+                .body(form: form)
+        } catch {
+            return .failure(.requestEncodingFailure(underlyingError: error))
+        }
+
+        return await perform(
+            request: builder.query(parameters ?? [:]),
+            fulfilling: progress,
+            decoder: { try JSONSerialization.jsonObject(with: $0) as AnyObject },
+            taskCreated: { taskID in
+                DispatchQueue.main.async {
+                    requestEnqueued?(NSNumber(value: taskID))
+                }
+            },
+            session: uploadURLSession
+        )
+    }
+
     public func perform(
         _ method: HTTPRequestBuilder.Method,
         URLString: String,
@@ -443,39 +475,6 @@ open class WordPressComRestApi: NSObject {
                 }
             }
     }
-
-    public func upload(
-        URLString: String,
-        parameters: [String: AnyObject]?,
-        fileParts: [FilePart],
-        requestEnqueued: RequestEnqueuedBlock? = nil,
-        fulfilling progress: Progress? = nil
-    ) async -> APIResult<AnyObject> {
-        let builder: HTTPRequestBuilder
-        do {
-            let form = try fileParts.map {
-                try MultipartFormField(fileAtPath: $0.url.path, name: $0.parameterName, filename: $0.fileName, mimeType: $0.mimeType)
-            }
-            builder = try requestBuilder(URLString: URLString)
-                .method(.post)
-                .body(form: form)
-        } catch {
-            return .failure(.requestEncodingFailure(underlyingError: error))
-        }
-
-        return await perform(
-            request: builder.query(parameters ?? [:]),
-            fulfilling: progress,
-            decoder: { try JSONSerialization.jsonObject(with: $0) as AnyObject },
-            taskCreated: { taskID in
-                DispatchQueue.main.async {
-                    requestEnqueued?(NSNumber(value: taskID))
-                }
-            },
-            session: uploadURLSession
-        )
-    }
-
 }
 
 // MARK: - Error processing

From 61dbb4e64e8d6199f5cd449b9ea5b845aa31dc4b Mon Sep 17 00:00:00 2001
From: Gio Lodi <gio@mokacoding.com>
Date: Wed, 10 Apr 2024 08:31:38 +1000
Subject: [PATCH 05/18] Make a couple of properties used in `async` API
 `public`

---
 .../APIInterface/include/WordPressComRESTAPIInterfacing.h   | 6 ++++++
 Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift | 2 +-
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/Sources/APIInterface/include/WordPressComRESTAPIInterfacing.h b/Sources/APIInterface/include/WordPressComRESTAPIInterfacing.h
index 1f652fd6c..7901238d8 100644
--- a/Sources/APIInterface/include/WordPressComRESTAPIInterfacing.h
+++ b/Sources/APIInterface/include/WordPressComRESTAPIInterfacing.h
@@ -6,6 +6,12 @@
 
 @property (strong, nonatomic, readonly) NSURL * _Nonnull baseURL;
 
+/// The key with which to specify locale in the parameters of a request.
+@property (strong, nonatomic, readonly) NSString * _Nonnull localeKey;
+
+/// Whether the user's preferred language locale should be appended.
+@property (nonatomic, readonly) BOOL appendsPreferredLanguageLocale;
+
 /// - Note: `parameters` has `id` instead of the more common `NSObject *` as its value type so it will convert to `AnyObject` in Swift.
 ///         In Swift, it's simpler to work with `AnyObject` than with `NSObject`. For example `"abc" as AnyObject` over `"abc" as NSObject`.
 - (NSProgress * _Nullable)get:(NSString * _Nonnull)URLString
diff --git a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
index 5fe6d2883..7b81070a6 100644
--- a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
+++ b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
@@ -95,7 +95,7 @@ open class WordPressComRestApi: NSObject {
 
     private let backgroundUploads: Bool
 
-    private let localeKey: String
+    public let localeKey: String
 
     @objc public let baseURL: URL
 

From 5571b0381992bcda0084c33d743a49f43c24fd07 Mon Sep 17 00:00:00 2001
From: Gio Lodi <gio@mokacoding.com>
Date: Wed, 10 Apr 2024 08:41:22 +1000
Subject: [PATCH 06/18] Remove a stray double new line

---
 Sources/WordPressKit/WordPressAPI/WordPressAPIError.swift | 1 -
 1 file changed, 1 deletion(-)

diff --git a/Sources/WordPressKit/WordPressAPI/WordPressAPIError.swift b/Sources/WordPressKit/WordPressAPI/WordPressAPIError.swift
index 5a6c3d5f0..508710f15 100644
--- a/Sources/WordPressKit/WordPressAPI/WordPressAPIError.swift
+++ b/Sources/WordPressKit/WordPressAPI/WordPressAPIError.swift
@@ -22,7 +22,6 @@ public enum WordPressAPIError<EndpointError>: Error where EndpointError: Localiz
     /// Other error occured.
     case unknown(underlyingError: Error)
 
-
     var response: HTTPURLResponse? {
         switch self {
         case .requestEncodingFailure, .connection, .unknown:

From be9635feb6a3f82db6780280018a2f9a0ff00159 Mon Sep 17 00:00:00 2001
From: Gio Lodi <gio@mokacoding.com>
Date: Wed, 10 Apr 2024 08:41:38 +1000
Subject: [PATCH 07/18] Move `processError` to an extension on
 `WordPressComRESTAPIInterfacing`

---
 .../WordPressAPI/WordPressComRestApi.swift          | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
index 7b81070a6..3fb955efd 100644
--- a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
+++ b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
@@ -458,7 +458,7 @@ open class WordPressComRestApi: NSObject {
                 return HTTPAPIResponse(response: response.response, body: object)
             }
             .mapUnacceptableStatusCodeError { response, body in
-                if let error = self.processError(response: response, body: body, additionalUserInfo: nil) {
+                if let error = self.processError(response: response, body: body, additionalUserInfo: nil, invalidTokenHandler: invalidTokenHandler) {
                     return error
                 }
 
@@ -479,9 +479,14 @@ open class WordPressComRestApi: NSObject {
 
 // MARK: - Error processing
 
-extension WordPressComRestApi {
+extension WordPressComRESTAPIInterfacing {
 
-    func processError(response httpResponse: HTTPURLResponse, body data: Data, additionalUserInfo: [String: Any]?) -> WordPressComRestApiEndpointError? {
+    func processError(
+        response httpResponse: HTTPURLResponse,
+        body data: Data,
+        additionalUserInfo: [String: Any]?,
+        invalidTokenHandler: (() -> Void)?
+    ) -> WordPressComRestApiEndpointError? {
         // Not sure if it's intentional to include 500 status code, but the code seems to be there from the very beginning.
         // https://github.com/wordpress-mobile/WordPressKit-iOS/blob/1.0.1/WordPressKit/WordPressComRestApi.swift#L374
         guard (400...500).contains(httpResponse.statusCode) else {
@@ -527,7 +532,7 @@ extension WordPressComRestApi {
         if mappedError == .invalidToken {
             // Call `invalidTokenHandler in the main thread since it's typically used by the apps to present an authentication UI.
             DispatchQueue.main.async {
-                self.invalidTokenHandler?()
+                invalidTokenHandler?()
             }
         }
 

From 61f6a44a508ead4f1c3ad40410e2968d734f752a Mon Sep 17 00:00:00 2001
From: Gio Lodi <gio@mokacoding.com>
Date: Wed, 10 Apr 2024 08:42:53 +1000
Subject: [PATCH 08/18] Require `URLSession` in root `async` `perform` method

---
 Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
index 3fb955efd..7713d3550 100644
--- a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
+++ b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
@@ -440,7 +440,7 @@ open class WordPressComRestApi: NSObject {
             }
         }
 
-        return await perform(request: builder, fulfilling: progress, decoder: decoder)
+        return await perform(request: builder, fulfilling: progress, decoder: decoder, session: urlSession)
     }
 
     private func perform<T>(
@@ -448,9 +448,9 @@ open class WordPressComRestApi: NSObject {
         fulfilling progress: Progress?,
         decoder: @escaping (Data) throws -> T,
         taskCreated: ((Int) -> Void)? = nil,
-        session: URLSession? = nil
+        session: URLSession
     ) async -> APIResult<T> {
-        await (session ?? self.urlSession)
+        await session
             .perform(request: request, taskCreated: taskCreated, fulfilling: progress, errorType: WordPressComRestApiEndpointError.self)
             .mapSuccess { response -> HTTPAPIResponse<T> in
                 let object = try decoder(response.body)

From b03cc2d4cddcba4de96f46a26a06a4584cb420e8 Mon Sep 17 00:00:00 2001
From: Gio Lodi <gio@mokacoding.com>
Date: Wed, 10 Apr 2024 08:43:46 +1000
Subject: [PATCH 09/18] Move `session` argument before `taskCreated`

As per Swift API naming guidelines on arguments with default values
---
 .../WordPressKit/WordPressAPI/WordPressComRestApi.swift   | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
index 7713d3550..7b00a9084 100644
--- a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
+++ b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
@@ -383,12 +383,12 @@ open class WordPressComRestApi: NSObject {
             request: builder.query(parameters ?? [:]),
             fulfilling: progress,
             decoder: { try JSONSerialization.jsonObject(with: $0) as AnyObject },
+            session: uploadURLSession,
             taskCreated: { taskID in
                 DispatchQueue.main.async {
                     requestEnqueued?(NSNumber(value: taskID))
                 }
-            },
-            session: uploadURLSession
+            }
         )
     }
 
@@ -447,8 +447,8 @@ open class WordPressComRestApi: NSObject {
         request: HTTPRequestBuilder,
         fulfilling progress: Progress?,
         decoder: @escaping (Data) throws -> T,
-        taskCreated: ((Int) -> Void)? = nil,
-        session: URLSession
+        session: URLSession,
+        taskCreated: ((Int) -> Void)? = nil
     ) async -> APIResult<T> {
         await session
             .perform(request: request, taskCreated: taskCreated, fulfilling: progress, errorType: WordPressComRestApiEndpointError.self)

From a5edf0deb3a0935aacca3024623ff587fde4bf81 Mon Sep 17 00:00:00 2001
From: Gio Lodi <gio@mokacoding.com>
Date: Wed, 10 Apr 2024 08:47:25 +1000
Subject: [PATCH 10/18] Require `invalidTokenHandler` in `perform`

---
 .../WordPressAPI/WordPressComRestApi.swift             | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
index 7b00a9084..0157e6d62 100644
--- a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
+++ b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
@@ -384,6 +384,7 @@ open class WordPressComRestApi: NSObject {
             fulfilling: progress,
             decoder: { try JSONSerialization.jsonObject(with: $0) as AnyObject },
             session: uploadURLSession,
+            invalidTokenHandler: invalidTokenHandler,
             taskCreated: { taskID in
                 DispatchQueue.main.async {
                     requestEnqueued?(NSNumber(value: taskID))
@@ -440,7 +441,13 @@ open class WordPressComRestApi: NSObject {
             }
         }
 
-        return await perform(request: builder, fulfilling: progress, decoder: decoder, session: urlSession)
+        return await perform(
+            request: builder,
+            fulfilling: progress,
+            decoder: decoder,
+            session: urlSession,
+            invalidTokenHandler: invalidTokenHandler
+        )
     }
 
     private func perform<T>(
@@ -448,6 +455,7 @@ open class WordPressComRestApi: NSObject {
         fulfilling progress: Progress?,
         decoder: @escaping (Data) throws -> T,
         session: URLSession,
+        invalidTokenHandler: (() -> Void)?,
         taskCreated: ((Int) -> Void)? = nil
     ) async -> APIResult<T> {
         await session

From 68e32b870c8270caa1175a861a6c07fde8165f88 Mon Sep 17 00:00:00 2001
From: Gio Lodi <gio@mokacoding.com>
Date: Wed, 10 Apr 2024 08:50:27 +1000
Subject: [PATCH 11/18] Move root `perform` definition to
 `WordPressComRESTAPIInterfacing`

---
 .../WordPressKit/WordPressAPI/WordPressComRestApi.swift    | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
index 0157e6d62..d4172bf96 100644
--- a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
+++ b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
@@ -449,15 +449,18 @@ open class WordPressComRestApi: NSObject {
             invalidTokenHandler: invalidTokenHandler
         )
     }
+}
 
-    private func perform<T>(
+extension WordPressComRESTAPIInterfacing {
+
+    func perform<T>(
         request: HTTPRequestBuilder,
         fulfilling progress: Progress?,
         decoder: @escaping (Data) throws -> T,
         session: URLSession,
         invalidTokenHandler: (() -> Void)?,
         taskCreated: ((Int) -> Void)? = nil
-    ) async -> APIResult<T> {
+    ) async -> WordPressAPIResult<HTTPAPIResponse<T>, WordPressComRestApiEndpointError> {
         await session
             .perform(request: request, taskCreated: taskCreated, fulfilling: progress, errorType: WordPressComRestApiEndpointError.self)
             .mapSuccess { response -> HTTPAPIResponse<T> in

From 16f2af7629478920639ef339fde997221dd84e22 Mon Sep 17 00:00:00 2001
From: Gio Lodi <gio@mokacoding.com>
Date: Wed, 10 Apr 2024 09:05:12 +1000
Subject: [PATCH 12/18] Extract logic to create builder with locale to
 extension

---
 .../WordPressAPI/WordPressComRestApi.swift    | 36 ++++++++++++++-----
 1 file changed, 27 insertions(+), 9 deletions(-)

diff --git a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
index d4172bf96..d3a165cf9 100644
--- a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
+++ b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
@@ -307,18 +307,14 @@ open class WordPressComRestApi: NSObject {
     }
 
     private func requestBuilder(URLString: String) throws -> HTTPRequestBuilder {
-        guard let url = URL(string: URLString, relativeTo: baseURL) else {
-            throw URLError(.badURL)
-        }
-
-        var builder = HTTPRequestBuilder(url: url)
-
+        let locale: (String, String)?
         if appendsPreferredLanguageLocale {
-            let preferredLanguageIdentifier = WordPressComLanguageDatabase().deviceLanguage.slug
-            builder = builder.query(defaults: [URLQueryItem(name: localeKey, value: preferredLanguageIdentifier)])
+            locale = (localeKey, WordPressComLanguageDatabase().deviceLanguage.slug)
+        } else {
+            locale = nil
         }
 
-        return builder
+        return try HTTPRequestBuilder.with(URLString: URLString, relativeTo: baseURL, appendingLocale: locale)
     }
 
     @objc public func temporaryFileURL(withExtension fileExtension: String) -> URL {
@@ -579,6 +575,28 @@ extension WordPressComRESTAPIInterfacing {
         )
     }
 }
+
+extension HTTPRequestBuilder {
+
+    static func with(
+        URLString: String,
+        relativeTo baseURL: URL,
+        appendingLocale locale: (key: String, value: String)?
+    ) throws -> HTTPRequestBuilder {
+        guard let url = URL(string: URLString, relativeTo: baseURL) else {
+            throw URLError(.badURL)
+        }
+
+        let builder = Self.init(url: url)
+
+        guard let locale else {
+            return builder
+        }
+
+        return builder.query(defaults: [URLQueryItem(name: locale.key, value: locale.value)])
+    }
+
+}
 // MARK: - Anonymous API support
 
 extension WordPressComRestApi {

From 63ac625b7a5e91d5aa176118e1d9e8ee991e4163 Mon Sep 17 00:00:00 2001
From: Gio Lodi <gio@mokacoding.com>
Date: Wed, 10 Apr 2024 09:16:16 +1000
Subject: [PATCH 13/18] Add new `localeValue` property to
 `WordPressComRESTAPIInterfacing`

---
 .../include/WordPressComRESTAPIInterfacing.h           | 10 ++++++++--
 .../WordPressAPI/WordPressComRestApi.swift             |  4 ++++
 2 files changed, 12 insertions(+), 2 deletions(-)

diff --git a/Sources/APIInterface/include/WordPressComRESTAPIInterfacing.h b/Sources/APIInterface/include/WordPressComRESTAPIInterfacing.h
index 7901238d8..35abe211f 100644
--- a/Sources/APIInterface/include/WordPressComRESTAPIInterfacing.h
+++ b/Sources/APIInterface/include/WordPressComRESTAPIInterfacing.h
@@ -6,11 +6,17 @@
 
 @property (strong, nonatomic, readonly) NSURL * _Nonnull baseURL;
 
+/// Whether the user's preferred language locale should be appended to the request.
+/// Should default to `true`.
+///
+/// - SeeAlso: `localeKey` and `localeValue` to configure the locale appendend to the request.
+@property (nonatomic, readonly) BOOL appendsPreferredLanguageLocale;
+
 /// The key with which to specify locale in the parameters of a request.
 @property (strong, nonatomic, readonly) NSString * _Nonnull localeKey;
 
-/// Whether the user's preferred language locale should be appended.
-@property (nonatomic, readonly) BOOL appendsPreferredLanguageLocale;
+/// The value with which to specify locale in the parameters of a request.
+@property (strong, nonatomic, readonly) NSString * _Nonnull localeValue;
 
 /// - Note: `parameters` has `id` instead of the more common `NSObject *` as its value type so it will convert to `AnyObject` in Swift.
 ///         In Swift, it's simpler to work with `AnyObject` than with `NSObject`. For example `"abc" as AnyObject` over `"abc" as NSObject`.
diff --git a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
index d3a165cf9..1bb2976ff 100644
--- a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
+++ b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
@@ -97,6 +97,10 @@ open class WordPressComRestApi: NSObject {
 
     public let localeKey: String
 
+    public var localeValue: String {
+        WordPressComLanguageDatabase().deviceLanguage.slug
+    }
+
     @objc public let baseURL: URL
 
     private var invalidTokenHandler: (() -> Void)?

From b09ee316f6d049dd92c678c4c837ec812c851b30 Mon Sep 17 00:00:00 2001
From: Gio Lodi <gio@mokacoding.com>
Date: Wed, 10 Apr 2024 09:16:43 +1000
Subject: [PATCH 14/18] Move request builder to
 `WordPressComRESTAPIInterfacing` extension

---
 .../WordPressAPI/WordPressComRestApi.swift    | 25 +++++++++----------
 1 file changed, 12 insertions(+), 13 deletions(-)

diff --git a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
index 1bb2976ff..579b632b8 100644
--- a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
+++ b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
@@ -310,17 +310,6 @@ open class WordPressComRestApi: NSObject {
         return "\(String(describing: oAuthToken)),\(String(describing: userAgent))".hashValue
     }
 
-    private func requestBuilder(URLString: String) throws -> HTTPRequestBuilder {
-        let locale: (String, String)?
-        if appendsPreferredLanguageLocale {
-            locale = (localeKey, WordPressComLanguageDatabase().deviceLanguage.slug)
-        } else {
-            locale = nil
-        }
-
-        return try HTTPRequestBuilder.with(URLString: URLString, relativeTo: baseURL, appendingLocale: locale)
-    }
-
     @objc public func temporaryFileURL(withExtension fileExtension: String) -> URL {
         assert(!fileExtension.isEmpty, "file Extension cannot be empty")
         let fileName = "\(ProcessInfo.processInfo.globallyUniqueString)_file.\(fileExtension)"
@@ -427,8 +416,7 @@ open class WordPressComRestApi: NSObject {
     ) async -> APIResult<T> {
         var builder: HTTPRequestBuilder
         do {
-            builder = try requestBuilder(URLString: URLString)
-                .method(method)
+            builder = try requestBuilder(URLString: URLString).method(method)
         } catch {
             return .failure(.requestEncodingFailure(underlyingError: error))
         }
@@ -486,6 +474,17 @@ extension WordPressComRESTAPIInterfacing {
                 }
             }
     }
+
+    func requestBuilder(URLString: String) throws -> HTTPRequestBuilder {
+        let locale: (String, String)?
+        if appendsPreferredLanguageLocale {
+            locale = (localeKey, localeValue)
+        } else {
+            locale = nil
+        }
+
+        return try HTTPRequestBuilder.with(URLString: URLString, relativeTo: baseURL, appendingLocale: locale)
+    }
 }
 
 // MARK: - Error processing

From 72c8f7bd0792d1c6bb53f421d9cbca31843e760d Mon Sep 17 00:00:00 2001
From: Gio Lodi <gio@mokacoding.com>
Date: Wed, 10 Apr 2024 09:20:35 +1000
Subject: [PATCH 15/18] Redefine `APIResult` typealias in the context of
 `WPComRESTAPIInterfacing`

---
 Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
index 579b632b8..3287a9567 100644
--- a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
+++ b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
@@ -441,6 +441,8 @@ open class WordPressComRestApi: NSObject {
 
 extension WordPressComRESTAPIInterfacing {
 
+    public typealias APIResult<T> = WordPressAPIResult<HTTPAPIResponse<T>, WordPressComRestApiEndpointError>
+
     func perform<T>(
         request: HTTPRequestBuilder,
         fulfilling progress: Progress?,
@@ -448,7 +450,7 @@ extension WordPressComRESTAPIInterfacing {
         session: URLSession,
         invalidTokenHandler: (() -> Void)?,
         taskCreated: ((Int) -> Void)? = nil
-    ) async -> WordPressAPIResult<HTTPAPIResponse<T>, WordPressComRestApiEndpointError> {
+    ) async -> APIResult<T> {
         await session
             .perform(request: request, taskCreated: taskCreated, fulfilling: progress, errorType: WordPressComRestApiEndpointError.self)
             .mapSuccess { response -> HTTPAPIResponse<T> in

From ed6a6349a5ba6803b2f3235747d710a4031c4677 Mon Sep 17 00:00:00 2001
From: Gio Lodi <gio@mokacoding.com>
Date: Wed, 10 Apr 2024 09:39:38 +1000
Subject: [PATCH 16/18] Move `urlSession` and `invalidTokenHandler` to API
 interface protocol

---
 .../APIInterface/include/WordPressComRESTAPIInterfacing.h  | 4 ++++
 .../WordPressKit/WordPressAPI/WordPressComRestApi.swift    | 7 ++-----
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/Sources/APIInterface/include/WordPressComRESTAPIInterfacing.h b/Sources/APIInterface/include/WordPressComRESTAPIInterfacing.h
index 35abe211f..5590ba14d 100644
--- a/Sources/APIInterface/include/WordPressComRESTAPIInterfacing.h
+++ b/Sources/APIInterface/include/WordPressComRESTAPIInterfacing.h
@@ -18,6 +18,10 @@
 /// The value with which to specify locale in the parameters of a request.
 @property (strong, nonatomic, readonly) NSString * _Nonnull localeValue;
 
+@property (strong, nonatomic, readonly) NSURLSession * _Nonnull urlSession;
+
+@property (strong, nonatomic, readonly) void (^ _Nullable invalidTokenHandler)(void);
+
 /// - Note: `parameters` has `id` instead of the more common `NSObject *` as its value type so it will convert to `AnyObject` in Swift.
 ///         In Swift, it's simpler to work with `AnyObject` than with `NSObject`. For example `"abc" as AnyObject` over `"abc" as NSObject`.
 - (NSProgress * _Nullable)get:(NSString * _Nonnull)URLString
diff --git a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
index 3287a9567..d5ae17b41 100644
--- a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
+++ b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
@@ -103,7 +103,7 @@ open class WordPressComRestApi: NSObject {
 
     @objc public let baseURL: URL
 
-    private var invalidTokenHandler: (() -> Void)?
+    public var invalidTokenHandler: (() -> Void)?
 
     /**
      Configure whether or not the user's preferred language locale should be appended. Defaults to true.
@@ -177,9 +177,6 @@ open class WordPressComRestApi: NSObject {
         }
     }
 
-    @objc func setInvalidTokenHandler(_ handler: @escaping () -> Void) {
-        invalidTokenHandler = handler
-    }
 
     // MARK: Network requests
 
@@ -319,7 +316,7 @@ open class WordPressComRestApi: NSObject {
 
     // MARK: - Async
 
-    private lazy var urlSession: URLSession = {
+    public lazy var urlSession: URLSession = {
         URLSession(configuration: sessionConfiguration(background: false))
     }()
 

From 55d8e8fa55153c22475d6b5befb321d14b1f3b6d Mon Sep 17 00:00:00 2001
From: Gio Lodi <gio@mokacoding.com>
Date: Wed, 10 Apr 2024 09:41:01 +1000
Subject: [PATCH 17/18] Move a couple more `perform` types to API protocol

---
 .../WordPressAPI/WordPressComRestApi.swift    | 38 ++++++++++---------
 1 file changed, 20 insertions(+), 18 deletions(-)

diff --git a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
index d5ae17b41..c75407efc 100644
--- a/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
+++ b/Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift
@@ -379,17 +379,6 @@ open class WordPressComRestApi: NSObject {
         )
     }
 
-    public func perform(
-        _ method: HTTPRequestBuilder.Method,
-        URLString: String,
-        parameters: [String: AnyObject]? = nil,
-        fulfilling progress: Progress? = nil
-    ) async -> APIResult<AnyObject> {
-        await perform(method, URLString: URLString, parameters: parameters, fulfilling: progress) {
-            try (JSONSerialization.jsonObject(with: $0) as AnyObject)
-        }
-    }
-
     func perform<T: Decodable>(
         _ method: HTTPRequestBuilder.Method,
         URLString: String,
@@ -403,8 +392,25 @@ open class WordPressComRestApi: NSObject {
             return try decoder.decode(type, from: $0)
         }
     }
+}
+
+extension WordPressComRESTAPIInterfacing {
+
+    public typealias APIResult<T> = WordPressAPIResult<HTTPAPIResponse<T>, WordPressComRestApiEndpointError>
+
+    public func perform(
+        _ method: HTTPRequestBuilder.Method,
+        URLString: String,
+        parameters: [String: AnyObject]? = nil,
+        fulfilling progress: Progress? = nil
+    ) async -> APIResult<AnyObject> {
+        await perform(method, URLString: URLString, parameters: parameters, fulfilling: progress) {
+            try (JSONSerialization.jsonObject(with: $0) as AnyObject)
+        }
+    }
 
-    private func perform<T>(
+    // FIXME: This was private. It became public during the extraction. Consider whether to make it privated once done.
+    public func perform<T>(
         _ method: HTTPRequestBuilder.Method,
         URLString: String,
         parameters: [String: AnyObject]?,
@@ -434,13 +440,9 @@ open class WordPressComRestApi: NSObject {
             invalidTokenHandler: invalidTokenHandler
         )
     }
-}
-
-extension WordPressComRESTAPIInterfacing {
-
-    public typealias APIResult<T> = WordPressAPIResult<HTTPAPIResponse<T>, WordPressComRestApiEndpointError>
 
-    func perform<T>(
+    // FIXME: This was private. It became public during the extraction. Consider whether to make it privated once done.
+    public func perform<T>(
         request: HTTPRequestBuilder,
         fulfilling progress: Progress?,
         decoder: @escaping (Data) throws -> T,

From f9a15a518cf2124739c6ad5b4c85f7f7f9efda2d Mon Sep 17 00:00:00 2001
From: Gio Lodi <gio@mokacoding.com>
Date: Wed, 10 Apr 2024 09:56:44 +1000
Subject: [PATCH 18/18] Remove a couple of unused variable assignments

---
 Sources/WordPressKit/Models/RemotePostParameters.swift | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Sources/WordPressKit/Models/RemotePostParameters.swift b/Sources/WordPressKit/Models/RemotePostParameters.swift
index f1abb0bba..ea5f66859 100644
--- a/Sources/WordPressKit/Models/RemotePostParameters.swift
+++ b/Sources/WordPressKit/Models/RemotePostParameters.swift
@@ -236,7 +236,7 @@ public struct RemotePostUpdateParametersWordPressComEncoder: Encodable {
         try container.encodeIfPresent(parameters.excerpt, forKey: .excerpt)
         try container.encodeIfPresent(parameters.slug, forKey: .slug)
         if let value = parameters.featuredImageID {
-            if let featuredImageID = value {
+            if value != nil {
                 try container.encode(parameters.featuredImageID, forKey: .featuredImageID)
             } else {
                 // Passing `null` doesn't work.
@@ -340,7 +340,7 @@ public struct RemotePostUpdateParametersXMLRPCEncoder: Encodable {
         try container.encodeIfPresent(parameters.excerpt, forKey: .excerpt)
         try container.encodeIfPresent(parameters.slug, forKey: .slug)
         if let value = parameters.featuredImageID {
-            if let featuredImageID = value {
+            if value != nil {
                 try container.encode(parameters.featuredImageID, forKey: .featuredImageID)
             } else {
                 // Passing `null` doesn't work.