Skip to content

Commit

Permalink
Wrap .org XML RPC error enum in an Error-conforming struct
Browse files Browse the repository at this point in the history
Jumping through this hoop was necessary to avoid the Swift compiler
automatically generating an error domain for the `Error` and making it
impossible to successfully redefine the domain at the `CustomNSError`
conformance site.
  • Loading branch information
mokagio committed Apr 17, 2024
1 parent f6f0c1c commit ac15e9a
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 47 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,24 @@ _None._
-->

## Unreleased

### Breaking Changes

- The Objective-C-visible `WordPressOrgXMLRPCError` `enum` has been renamed to `WordPressOrgXMLRPCErrorCode` [#790]

### New Features

_None._

### Bug Fixes

_None._

### Internal Changes

_None._

## 17.0.0

### Breaking Changes
Expand Down
55 changes: 15 additions & 40 deletions Sources/CoreAPI/WordPressOrgXMLRPCApi.swift
Original file line number Diff line number Diff line change
Expand Up @@ -288,43 +288,6 @@ private class SessionDelegate: NSObject, URLSessionDelegate {
}
}
}

// Hack to avoid the Swift compiler generating a domain constant for us that would then conflict with the one we define in APIInterface.
// The automatic generation occurs, as far as I can tell, because of @objc and Error, neither of which we can remove.
// Reminder: We define our own constant for the domain so that we can conform WordPressOrgXMLRPCApiError to CustomNSError.
typealias WordPressOrgXMLRPCApiError = WordPressOrgXMLRPCAPIError

/// Error constants for the WordPress XML-RPC API
@objc public enum WordPressOrgXMLRPCAPIError: Int, Error, CaseIterable {
/// An error HTTP status code was returned.
case httpErrorStatusCode
/// The serialization of the request failed.
case requestSerializationFailed
/// The serialization of the response failed.
case responseSerializationFailed
/// An unknown error occurred.
case unknown
}

extension WordPressOrgXMLRPCApiError: LocalizedError {
public var errorDescription: String? {
return NSLocalizedString("There was a problem communicating with the site.", comment: "A general error message shown to the user when there was an API communication failure.")
}

public var failureReason: String? {
switch self {
case .httpErrorStatusCode:
return NSLocalizedString("An HTTP error code was returned.", comment: "A failure reason for when an error HTTP status code was returned from the site.")
case .requestSerializationFailed:
return NSLocalizedString("The serialization of the request failed.", comment: "A failure reason for when the request couldn't be serialized.")
case .responseSerializationFailed:
return NSLocalizedString("The serialization of the response failed.", comment: "A failure reason for when the response couldn't be serialized.")
case .unknown:
return NSLocalizedString("An unknown error occurred.", comment: "A failure reason for when the error that occured wasn't able to be determined.")
}
}
}

public struct WordPressOrgXMLRPCApiFault: LocalizedError, HTTPURLResponseProviding {
public var response: HTTPAPIResponse<Data>
public let code: Int?
Expand Down Expand Up @@ -352,7 +315,13 @@ private extension WordPressAPIResult<HTTPAPIResponse<Data>, WordPressOrgXMLRPCAp
// https://github.com/wordpress-mobile/WordPressKit-iOS/blob/11.0.0/WordPressKit/WordPressOrgXMLRPCApi.swift#L265
flatMap { response in
guard let contentType = response.response.allHeaderFields["Content-Type"] as? String else {
return .failure(.unparsableResponse(response: response.response, body: response.body, underlyingError: WordPressOrgXMLRPCApiError.unknown))
return .failure(
.unparsableResponse(
response: response.response,
body: response.body,
underlyingError: WordPressOrgXMLRPCApiError(code: .unknown)
)
)
}

if (400..<600).contains(response.response.statusCode) {
Expand All @@ -366,7 +335,13 @@ private extension WordPressAPIResult<HTTPAPIResponse<Data>, WordPressOrgXMLRPCAp
}

guard contentType.hasPrefix("application/xml") || contentType.hasPrefix("text/xml") else {
return .failure(.unparsableResponse(response: response.response, body: response.body, underlyingError: WordPressOrgXMLRPCApiError.unknown))
return .failure(
.unparsableResponse(
response: response.response,
body: response.body,
underlyingError: WordPressOrgXMLRPCApiError(code: .unknown)
)
)
}

guard let decoder = WPXMLRPCDecoder(data: response.body) else {
Expand Down Expand Up @@ -418,7 +393,7 @@ private extension WordPressAPIError where EndpointError == WordPressOrgXMLRPCApi
data = fault.response.body
statusCode = nil
case let .unacceptableStatusCode(response, body):
error = WordPressOrgXMLRPCApiError.httpErrorStatusCode as NSError
error = WordPressOrgXMLRPCApiError(code: .httpErrorStatusCode) as NSError
data = body
statusCode = response.statusCode
case let .unparsableResponse(_, body, underlyingError):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ extension WordPressOrgXMLRPCApiError: CustomNSError {

public static let errorDomain = WordPressOrgXMLRPCApiErrorDomain

public var errorCode: Int { self.rawValue }
public var errorCode: Int { code.rawValue }

public var errorUserInfo: [String: Any] { [:] }
}
39 changes: 39 additions & 0 deletions Sources/CoreAPI/WordPressOrgXMLRPCApiError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import Foundation

/// Error constants for the WordPress XML-RPC API
@objc public enum WordPressOrgXMLRPCApiErrorCode: Int, CaseIterable {
/// An error HTTP status code was returned.
case httpErrorStatusCode
/// The serialization of the request failed.
case requestSerializationFailed
/// The serialization of the response failed.
case responseSerializationFailed
/// An unknown error occurred.
case unknown
}

public struct WordPressOrgXMLRPCApiError: Error {
let code: WordPressOrgXMLRPCApiErrorCode
}

extension WordPressOrgXMLRPCApiError: LocalizedError {
public var errorDescription: String? {
return NSLocalizedString(
"There was a problem communicating with the site.",
comment: "A general error message shown to the user when there was an API communication failure."
)
}

public var failureReason: String? {
switch code {
case .httpErrorStatusCode:
return NSLocalizedString("An HTTP error code was returned.", comment: "A failure reason for when an error HTTP status code was returned from the site.")
case .requestSerializationFailed:
return NSLocalizedString("The serialization of the request failed.", comment: "A failure reason for when the request couldn't be serialized.")
case .responseSerializationFailed:
return NSLocalizedString("The serialization of the response failed.", comment: "A failure reason for when the response couldn't be serialized.")
case .unknown:
return NSLocalizedString("An unknown error occurred.", comment: "A failure reason for when the error that occured wasn't able to be determined.")
}
}
}
4 changes: 2 additions & 2 deletions Tests/CoreAPITests/WordPressOrgXMLRPCApiErrorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import XCTest
class WordPressOrgXMLRPCApiErrorTests: XCTestCase {

func testNSErrorBridging() throws {
for error in WordPressOrgXMLRPCApiError.allCases {
let xmlRPCError = try XCTUnwrap(WordPressOrgXMLRPCApiError(rawValue: error.rawValue))
for error in WordPressOrgXMLRPCApiErrorCode.allCases {
let xmlRPCError = try XCTUnwrap(WordPressOrgXMLRPCApiError(code: error))
let apiError = WordPressAPIError.endpointError(xmlRPCError)
let newNSError = apiError as NSError

Expand Down
8 changes: 4 additions & 4 deletions Tests/CoreAPITests/WordPressOrgXMLRPCApiTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class WordPressOrgXMLRPCApiTests: XCTestCase {
failure: { (error, _) in
expect.fulfill()
XCTAssertEqual(error.domain, WordPressOrgXMLRPCApiErrorDomain)
XCTAssertEqual(error.code, WordPressOrgXMLRPCApiError.httpErrorStatusCode.rawValue)
XCTAssertEqual(error.code, WordPressOrgXMLRPCApiErrorCode.httpErrorStatusCode.rawValue)
XCTAssertEqual(error.localizedFailureReason, "An HTTP error code 404 was returned.")
XCTAssertNotNil(error.userInfo[WordPressOrgXMLRPCApi.WordPressOrgXMLRPCApiErrorKeyData as String])
XCTAssertNotNil(error.userInfo[WordPressOrgXMLRPCApi.WordPressOrgXMLRPCApiErrorKeyStatusCode as String])
Expand Down Expand Up @@ -119,10 +119,10 @@ class WordPressOrgXMLRPCApiTests: XCTestCase {
failure: { (error, _) in
expect.fulfill()

XCTAssertTrue(error is WordPressOrgXMLRPCApiError)
XCTAssertEqual(error.domain, WordPressOrgXMLRPCApiErrorDomain)
XCTAssertEqual(error.code, WordPressOrgXMLRPCApiError.unknown.rawValue)
XCTAssertEqual(error.localizedFailureReason, WordPressOrgXMLRPCApiError.unknown.failureReason)
let errorUnknonw = WordPressOrgXMLRPCApiError(code: .unknown)
XCTAssertEqual(error.code, errorUnknonw.code.rawValue)
XCTAssertEqual(error.localizedFailureReason, errorUnknonw.failureReason)
XCTAssertNotNil(error.userInfo[WordPressOrgXMLRPCApi.WordPressOrgXMLRPCApiErrorKeyData as String])
XCTAssertNil(error.userInfo[WordPressOrgXMLRPCApi.WordPressOrgXMLRPCApiErrorKeyStatusCode as String])
}
Expand Down
4 changes: 4 additions & 0 deletions WordPressKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
3F8308A729EE683500354497 /* ActivityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F8308A629EE683500354497 /* ActivityTests.swift */; };
3FA4258F2BCCFDA6007539BF /* WordPressComRestApiErrorDomain.h in Headers */ = {isa = PBXBuildFile; fileRef = 3FA4258E2BCCFDA6007539BF /* WordPressComRestApiErrorDomain.h */; settings = {ATTRIBUTES = (Public, ); }; };
3FA425A72BCF7EDC007539BF /* WordPressOrgXMLRPCApiErrorDomain.h in Headers */ = {isa = PBXBuildFile; fileRef = 3FA425A62BCF7E7C007539BF /* WordPressOrgXMLRPCApiErrorDomain.h */; settings = {ATTRIBUTES = (Public, ); }; };
3FA425A92BCF8721007539BF /* WordPressOrgXMLRPCApiError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FA425A82BCF8721007539BF /* WordPressOrgXMLRPCApiError.swift */; };
3FB8642C2888089F003A86BE /* BuildkiteTestCollector in Frameworks */ = {isa = PBXBuildFile; productRef = 3FB8642B2888089F003A86BE /* BuildkiteTestCollector */; };
3FD634E52BC3A55F00CEDF5E /* WordPressOrgXMLRPCValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FD634E32BC3A55F00CEDF5E /* WordPressOrgXMLRPCValidator.swift */; };
3FD634E62BC3A55F00CEDF5E /* Date+WordPressCom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FD634E42BC3A55F00CEDF5E /* Date+WordPressCom.swift */; };
Expand Down Expand Up @@ -821,6 +822,7 @@
3F8308A629EE683500354497 /* ActivityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityTests.swift; sourceTree = "<group>"; };
3FA4258E2BCCFDA6007539BF /* WordPressComRestApiErrorDomain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WordPressComRestApiErrorDomain.h; sourceTree = "<group>"; };
3FA425A62BCF7E7C007539BF /* WordPressOrgXMLRPCApiErrorDomain.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WordPressOrgXMLRPCApiErrorDomain.h; sourceTree = "<group>"; };
3FA425A82BCF8721007539BF /* WordPressOrgXMLRPCApiError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordPressOrgXMLRPCApiError.swift; sourceTree = "<group>"; };
3FB8642D288813E9003A86BE /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UnitTests.xctestplan; sourceTree = "<group>"; };
3FD634E32BC3A55F00CEDF5E /* WordPressOrgXMLRPCValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WordPressOrgXMLRPCValidator.swift; sourceTree = "<group>"; };
3FD634E42BC3A55F00CEDF5E /* Date+WordPressCom.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+WordPressCom.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2143,6 +2145,7 @@
3FE2E97A2BC3A332002CA2E1 /* WordPressComRestApi.swift */,
4A05E7992B2FDC3200C25E3B /* WordPressOrgRestApi.swift */,
93BD27791EE73944002BB00B /* WordPressOrgXMLRPCApi.swift */,
3FA425A82BCF8721007539BF /* WordPressOrgXMLRPCApiError.swift */,
3F6128122BCB31660063810D /* WordPressOrgXMLRPCApiError+NSErrorBridge.swift */,
3FD634E32BC3A55F00CEDF5E /* WordPressOrgXMLRPCValidator.swift */,
93BD277B1EE73944002BB00B /* WordPressRSDParser.swift */,
Expand Down Expand Up @@ -3314,6 +3317,7 @@
9AB6D647218705E90008F274 /* RemoteDiff.swift in Sources */,
93BD277C1EE73944002BB00B /* HTTPAuthenticationAlertController.swift in Sources */,
7433BC011EFC4505002D9E92 /* PlanServiceRemote.swift in Sources */,
3FA425A92BCF8721007539BF /* WordPressOrgXMLRPCApiError.swift in Sources */,
4041405E220F9EF500CF7C5B /* StatsDotComFollowersInsight.swift in Sources */,
74650F721F0EA1A700188EDB /* GravatarServiceRemote.swift in Sources */,
B5969E1D20A49AC4005E9DF1 /* NSString+MD5.m in Sources */,
Expand Down

0 comments on commit ac15e9a

Please sign in to comment.