From 73102b7ad29d2a0291bca5f8ab71b00ca8e77b24 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Fri, 16 Feb 2024 10:57:04 +1300 Subject: [PATCH 1/7] Decouple PluginDirectoryServiceRemote from Alamofire --- .../PluginDirectoryServiceRemote.swift | 53 ++++++++++--------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/WordPressKit/PluginDirectoryServiceRemote.swift b/WordPressKit/PluginDirectoryServiceRemote.swift index b4bb1a4a1..0ae57ad56 100644 --- a/WordPressKit/PluginDirectoryServiceRemote.swift +++ b/WordPressKit/PluginDirectoryServiceRemote.swift @@ -47,7 +47,7 @@ public enum PluginDirectoryFeedType: Hashable { } } -public struct PluginDirectoryGetInformationEndpoint: Endpoint { +public struct PluginDirectoryGetInformationEndpoint { public enum Error: Swift.Error { case pluginNotFound } @@ -57,20 +57,18 @@ public struct PluginDirectoryGetInformationEndpoint: Endpoint { self.slug = slug } - public func buildRequest() throws -> URLRequest { - let url = PluginDirectoryRemoteConstants.getInformationEndpoint - .appendingPathComponent(slug) - .appendingPathExtension("json") - let request = URLRequest(url: url) - let encodedRequest = try URLEncoding.default.encode(request, with: ["fields": "icons,banners"]) - return encodedRequest + func buildRequest() throws -> URLRequest { + try HTTPRequestBuilder(url: PluginDirectoryRemoteConstants.getInformationEndpoint) + .append(percentEncodedPath: "\(slug).json") + .query(name: "fields", value: "icons,banners") + .build() } - public func parseResponse(data: Data) throws -> PluginDirectoryEntry { + func parseResponse(data: Data) throws -> PluginDirectoryEntry { return try PluginDirectoryRemoteConstants.jsonDecoder.decode(PluginDirectoryEntry.self, from: data) } - public func validate(request: URLRequest?, response: HTTPURLResponse, data: Data?) throws { + func validate(response: HTTPURLResponse, data: Data?) throws { // api.wordpress.org has an odd way of responding to plugin info requests for // plugins not in the directory: it will return `null` with an HTTP 200 OK. // This turns that case into a `.pluginNotFound` error. @@ -83,7 +81,7 @@ public struct PluginDirectoryGetInformationEndpoint: Endpoint { } } -public struct PluginDirectoryFeedEndpoint: Endpoint { +public struct PluginDirectoryFeedEndpoint { public enum Error: Swift.Error { case genericError } @@ -91,12 +89,12 @@ public struct PluginDirectoryFeedEndpoint: Endpoint { let feedType: PluginDirectoryFeedType let pageNumber: Int - public init(feedType: PluginDirectoryFeedType) { + init(feedType: PluginDirectoryFeedType) { self.feedType = feedType self.pageNumber = 1 } - public func buildRequest() throws -> URLRequest { + func buildRequest() throws -> URLRequest { var parameters: [String: Any] = ["action": "query_plugins", "request[per_page]": PluginDirectoryRemoteConstants.pluginsPerPage, "request[fields][icons]": 1, @@ -113,17 +111,16 @@ public struct PluginDirectoryFeedEndpoint: Endpoint { } - let request = URLRequest(url: PluginDirectoryRemoteConstants.feedEndpoint) - let encodedRequest = try URLEncoding.default.encode(request, with: parameters) - - return encodedRequest + return try HTTPRequestBuilder(url: PluginDirectoryRemoteConstants.feedEndpoint) + .query(parameters) + .build() } - public func parseResponse(data: Data) throws -> PluginDirectoryFeedPage { + func parseResponse(data: Data) throws -> PluginDirectoryFeedPage { return try PluginDirectoryRemoteConstants.jsonDecoder.decode(PluginDirectoryFeedPage.self, from: data) } - public func validate(request: URLRequest?, response: HTTPURLResponse, data: Data?) throws { + func validate(response: HTTPURLResponse, data: Data?) throws { if response.statusCode != 200 { throw Error.genericError} } } @@ -132,13 +129,19 @@ public struct PluginDirectoryServiceRemote { public init() {} - public func getPluginFeed(_ feedType: PluginDirectoryFeedType, - pageNumber: Int = 1, - completion: @escaping (Result) -> Void) { - PluginDirectoryFeedEndpoint(feedType: feedType).request(completion: completion) + public func getPluginFeed(_ feedType: PluginDirectoryFeedType, pageNumber: Int = 1) async throws -> PluginDirectoryFeedPage { + let endpoint = PluginDirectoryFeedEndpoint(feedType: feedType) + let (data, response) = try await URLSession.shared.data(for: endpoint.buildRequest()) + let httpResponse = response as! HTTPURLResponse + try endpoint.validate(response: httpResponse, data: data) + return try endpoint.parseResponse(data: data) } - public func getPluginInformation(slug: String, completion: @escaping (Result) -> Void) { - PluginDirectoryGetInformationEndpoint(slug: slug).request(completion: completion) + public func getPluginInformation(slug: String) async throws -> PluginDirectoryEntry { + let endpoint = PluginDirectoryGetInformationEndpoint(slug: slug) + let (data, response) = try await URLSession.shared.data(for: endpoint.buildRequest()) + let httpResponse = response as! HTTPURLResponse + try endpoint.validate(response: httpResponse, data: data) + return try endpoint.parseResponse(data: data) } } From 56c84ec7b604302bdd6896005ab4febf7e360c19 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Fri, 16 Feb 2024 10:58:10 +1300 Subject: [PATCH 2/7] Remove unused Endpoint.swift --- WordPressKit.xcodeproj/project.pbxproj | 4 -- WordPressKit/Endpoint.swift | 55 -------------------------- 2 files changed, 59 deletions(-) delete mode 100644 WordPressKit/Endpoint.swift diff --git a/WordPressKit.xcodeproj/project.pbxproj b/WordPressKit.xcodeproj/project.pbxproj index a32440826..da0b35d6a 100644 --- a/WordPressKit.xcodeproj/project.pbxproj +++ b/WordPressKit.xcodeproj/project.pbxproj @@ -603,7 +603,6 @@ E14694031F344F71004052C8 /* site-plugins-error.json in Resources */ = {isa = PBXBuildFile; fileRef = E14694021F344F71004052C8 /* site-plugins-error.json */; }; E1787DB0200E564B004CB3AF /* timezones.json in Resources */ = {isa = PBXBuildFile; fileRef = E1787DAF200E564B004CB3AF /* timezones.json */; }; E1787DB2200E5690004CB3AF /* TimeZoneServiceRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1787DB1200E5690004CB3AF /* TimeZoneServiceRemoteTests.swift */; }; - E182BF6A1FD961810001D850 /* Endpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = E182BF691FD961810001D850 /* Endpoint.swift */; }; E194CB731FBDEF6500B0A8B8 /* PluginState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E194CB721FBDEF6400B0A8B8 /* PluginState.swift */; }; E1A6605F1FD694ED00BAC339 /* PluginDirectoryEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A6605E1FD694ED00BAC339 /* PluginDirectoryEntry.swift */; }; E1BD95151FD5A2B800CD5CE3 /* PluginDirectoryServiceRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1BD95141FD5A2B800CD5CE3 /* PluginDirectoryServiceRemote.swift */; }; @@ -1331,7 +1330,6 @@ E14694021F344F71004052C8 /* site-plugins-error.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "site-plugins-error.json"; sourceTree = ""; }; E1787DAF200E564B004CB3AF /* timezones.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = timezones.json; sourceTree = ""; }; E1787DB1200E5690004CB3AF /* TimeZoneServiceRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeZoneServiceRemoteTests.swift; sourceTree = ""; }; - E182BF691FD961810001D850 /* Endpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Endpoint.swift; sourceTree = ""; }; E194CB721FBDEF6400B0A8B8 /* PluginState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PluginState.swift; sourceTree = ""; }; E1A6605E1FD694ED00BAC339 /* PluginDirectoryEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginDirectoryEntry.swift; sourceTree = ""; }; E1BD95141FD5A2B800CD5CE3 /* PluginDirectoryServiceRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginDirectoryServiceRemote.swift; sourceTree = ""; }; @@ -1984,7 +1982,6 @@ 74BA04F11F06DC0A00ED5CD8 /* CommentServiceRemoteXMLRPC.m */, 8BB5F62027A99A2000B2FFAF /* DashboardServiceRemote.swift */, 7E0D64FE22D855700092AD10 /* EditorServiceRemote.swift */, - E182BF691FD961810001D850 /* Endpoint.swift */, F9E56DF724EB125600916770 /* FeatureFlagRemote.swift */, 74650F711F0EA1A700188EDB /* GravatarServiceRemote.swift */, 1769DEA924729AFF00F42EFC /* HomepageSettingsServiceRemote.swift */, @@ -3459,7 +3456,6 @@ F1BB7806240FB90B0030ADDC /* AtomicAuthenticationServiceRemote.swift in Sources */, 404057CE221C38130060250C /* StatsTopVideosTimeIntervalData.swift in Sources */, 7E0D64FF22D855700092AD10 /* EditorServiceRemote.swift in Sources */, - E182BF6A1FD961810001D850 /* Endpoint.swift in Sources */, 9AF4F2FF2183346B00570E4B /* RemoteRevision.swift in Sources */, 17D936252475D8AB008B2205 /* RemoteHomepageType.swift in Sources */, 74BA04F41F06DC0A00ED5CD8 /* CommentServiceRemoteREST.m in Sources */, diff --git a/WordPressKit/Endpoint.swift b/WordPressKit/Endpoint.swift deleted file mode 100644 index 192e310dc..000000000 --- a/WordPressKit/Endpoint.swift +++ /dev/null @@ -1,55 +0,0 @@ -import Foundation -import Alamofire - -/// Represents a specific type of Network request. -/// -/// This protocol provides the building blocks to define network requests -/// independent of the network library used. It has 3 responsibilities: -/// -/// - Creating a URLRequest -/// - Validating the response -/// - Parsing response data into the expected result -/// -/// Validation is an optional step that allows inspection of the URLResponse -/// object. If an endpoint doesn’t need custom validation, it can have an empty -/// implementation. -/// -public protocol Endpoint { - associatedtype Output - func buildRequest() throws -> URLRequest - func parseResponse(data: Data) throws -> Output - - /// Validates a response. - /// - /// If the endpoint doesn't need validation, implement this as an empty method. - /// Otherwise, inspect the arguments and throw an error if necessary. - func validate(request: URLRequest?, response: HTTPURLResponse, data: Data?) throws -} - -extension Endpoint { - func request(completion: @escaping (Result) -> Void) { - do { - let request = try buildRequest() - - Alamofire - .request(request) - .validate() - .validate({ (request, response, data) in - do { - try self.validate(request: request, response: response, data: data) - return .success - } catch { - return .failure(error) - } - }) - .responseData(queue: DispatchQueue.global(qos: .utility), completionHandler: { (response) in - let result = response.result.flatMap(self.parseResponse(data:)) - DispatchQueue.main.async { - completion(result) - } - }) - } catch { - completion(.failure(error)) - } - } -} From a69bb606d06979e0736c6dbd66349c6efef1fc9f Mon Sep 17 00:00:00 2001 From: Tony Li Date: Fri, 16 Feb 2024 10:58:28 +1300 Subject: [PATCH 3/7] Update unit tests --- WordPressKitTests/PluginDirectoryTests.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/WordPressKitTests/PluginDirectoryTests.swift b/WordPressKitTests/PluginDirectoryTests.swift index 57288fe13..05c8f64a2 100644 --- a/WordPressKitTests/PluginDirectoryTests.swift +++ b/WordPressKitTests/PluginDirectoryTests.swift @@ -57,7 +57,7 @@ class PluginDirectoryTests: XCTestCase { do { let request = try endpoint.buildRequest() let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: "1.1", headerFields: nil)! - XCTAssertNoThrow(try endpoint.validate(request: request, response: response, data: data)) + XCTAssertNoThrow(try endpoint.validate(response: response, data: data)) } catch { XCTFail(error.localizedDescription) } @@ -70,7 +70,7 @@ class PluginDirectoryTests: XCTestCase { let request = try! endpoint.buildRequest() let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: "1.1", headerFields: nil)! - XCTAssertThrowsError(try endpoint.validate(request: request, response: response, data: "null".data(using: .utf8))) + XCTAssertThrowsError(try endpoint.validate(response: response, data: "null".data(using: .utf8))) } func testValidatePluginDirectoryFeedResponseSucceeds() throws { @@ -79,7 +79,7 @@ class PluginDirectoryTests: XCTestCase { let request = try endpoint.buildRequest() let response = HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: "1.1", headerFields: nil)! - XCTAssertNoThrow(try endpoint.validate(request: request, response: response, data: "null".data(using: .utf8))) + XCTAssertNoThrow(try endpoint.validate(response: response, data: "null".data(using: .utf8))) } func testValidatePluginDirectoryFeedResponseFails() { @@ -88,7 +88,7 @@ class PluginDirectoryTests: XCTestCase { let request = try! endpoint.buildRequest() let response = HTTPURLResponse(url: request.url!, statusCode: 403, httpVersion: "1.1", headerFields: nil)! - XCTAssertThrowsError(try endpoint.validate(request: request, response: response, data: "null".data(using: .utf8))) + XCTAssertThrowsError(try endpoint.validate(response: response, data: "null".data(using: .utf8))) } func testNewDirectoryFeedRequest() { From b4e53b6123c70e7c1db9e6c1f6195aa43b844738 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Fri, 16 Feb 2024 11:06:20 +1300 Subject: [PATCH 4/7] Add a couple of unit tests for PluginDirectoryServiceRemote --- WordPressKitTests/PluginDirectoryTests.swift | 21 ++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/WordPressKitTests/PluginDirectoryTests.swift b/WordPressKitTests/PluginDirectoryTests.swift index 05c8f64a2..13b9f1a8a 100644 --- a/WordPressKitTests/PluginDirectoryTests.swift +++ b/WordPressKitTests/PluginDirectoryTests.swift @@ -1,4 +1,5 @@ import XCTest +import OHHTTPStubs @testable import WordPressKit class PluginDirectoryTests: XCTestCase { @@ -51,6 +52,26 @@ class PluginDirectoryTests: XCTestCase { } } + func testGetPluginInformation() async throws { + let data = try MockPluginDirectoryProvider.getPluginDirectoryMockData(with: "plugin-directory-rename-xml-rpc", sender: type(of: self)) + stub(condition: isHost("api.wordpress.org")) { _ in + HTTPStubsResponse(data: data, statusCode: 200, headers: ["Content-Type": "application/json"]) + } + + let plugin = try await PluginDirectoryServiceRemote().getPluginInformation(slug: "rename-xml-rpc") + XCTAssertEqual(plugin.name, "Rename XMLRPC") + } + + func testGetDirectoryFeed() async throws { + let data = try MockPluginDirectoryProvider.getPluginDirectoryMockData(with: "plugin-directory-popular", sender: type(of: self)) + stub(condition: isHost("api.wordpress.org")) { _ in + HTTPStubsResponse(data: data, statusCode: 200, headers: ["Content-Type": "application/json"]) + } + + let feed = try await PluginDirectoryServiceRemote().getPluginFeed(.popular) + XCTAssertEqual(feed.plugins.first?.name, "Contact Form 7") + } + func testValidateResponseFound() { let data = try! MockPluginDirectoryProvider.getPluginDirectoryMockData(with: "plugin-directory-rename-xml-rpc", sender: type(of: self)) let endpoint = PluginDirectoryGetInformationEndpoint(slug: "jetpack") From 2fd641c459ffaa1e61a08cde7655c0ddca5e1a0d Mon Sep 17 00:00:00 2001 From: Tony Li Date: Fri, 16 Feb 2024 11:15:03 +1300 Subject: [PATCH 5/7] Add a changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2178275fc..7d0e721d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ _None._ ### Breaking Changes -_None._ +- Decouple `PluginDirectoryServiceRemote` from Alamofire. [#725] ### New Features From 0342a84c872b127cf24984bb4d39a40d00c5d645 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Fri, 16 Feb 2024 13:16:50 +1300 Subject: [PATCH 6/7] Remove 'import Alamofire' --- WordPressKit/PluginDirectoryServiceRemote.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/WordPressKit/PluginDirectoryServiceRemote.swift b/WordPressKit/PluginDirectoryServiceRemote.swift index 0ae57ad56..5dd800b78 100644 --- a/WordPressKit/PluginDirectoryServiceRemote.swift +++ b/WordPressKit/PluginDirectoryServiceRemote.swift @@ -1,5 +1,4 @@ import Foundation -import Alamofire private struct PluginDirectoryRemoteConstants { static let dateFormatter: DateFormatter = { From dce379835de69e333a9748a481ab380c069fbd41 Mon Sep 17 00:00:00 2001 From: Tony Li Date: Fri, 16 Feb 2024 21:36:10 +1300 Subject: [PATCH 7/7] Add another changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3aa5f57c1..9903cc1e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ _None._ - Rewrite `WordPressOrgRestApi` to support self hosted sites and WordPress.com sites. [#724] - Decouple `PluginDirectoryServiceRemote` from Alamofire. [#725] +- Remove `Endpoint`. [#725] ### New Features