diff --git a/CHANGELOG.md b/CHANGELOG.md index dbc43ab7..60243521 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,24 @@ _None._ --> +## Unreleased + +### Breaking Changes + +- The Objective-C-visible `WordPressOrgXMLRPCError` `enum` has been renamed to `WordPressOrgXMLRPCErrorCode`, `WordPressOrgXMLRPCError.Code` in Swift [#790] + +### New Features + +_None._ + +### Bug Fixes + +_None._ + +### Internal Changes + +_None._ + ## 17.0.0 ### Breaking Changes diff --git a/Sources/APIInterface/include/WordPressOrgXMLRPCApiErrorDomain.h b/Sources/APIInterface/include/WordPressOrgXMLRPCApiErrorDomain.h new file mode 100644 index 00000000..1b33b7d7 --- /dev/null +++ b/Sources/APIInterface/include/WordPressOrgXMLRPCApiErrorDomain.h @@ -0,0 +1,9 @@ +#import + +/// Error domain of `NSError` instances that are converted from `WordPressOrgXMLRPCApiError` +/// and `WordPressAPIError` instances. +/// +/// This matches the compiler generated value and is used to ensure consistent error domain across error types and SPM or Framework build modes. +/// +/// See `extension WordPressComRestApiEndpointError: CustomNSError` in CoreAPI package for context. +static NSString *const _Nonnull WordPressOrgXMLRPCApiErrorDomain = @"WordPressKit.WordPressOrgXMLRPCApiError"; diff --git a/Sources/CoreAPI/WordPressOrgXMLRPCApi.swift b/Sources/CoreAPI/WordPressOrgXMLRPCApi.swift index 2698c89f..c43e0c76 100644 --- a/Sources/CoreAPI/WordPressOrgXMLRPCApi.swift +++ b/Sources/CoreAPI/WordPressOrgXMLRPCApi.swift @@ -288,38 +288,6 @@ private class SessionDelegate: NSObject, URLSessionDelegate { } } } - -/// Error constants for the WordPress XML-RPC API -@objc public enum WordPressOrgXMLRPCApiError: Int, Error { - /// 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 public let code: Int? @@ -347,7 +315,13 @@ private extension WordPressAPIResult, 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) { @@ -361,7 +335,13 @@ private extension WordPressAPIResult, 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 { @@ -391,7 +371,7 @@ private extension WordPressAPIError where EndpointError == WordPressOrgXMLRPCApi /// Convert to NSError for backwards compatiblity. /// /// Some Objective-C code in the WordPress app checks domain of the errors returned by `WordPressOrgXMLRPCApi`, - /// which can be WordPressOrgXMLRPCApiError or WPXMLRPCFaultErrorDomain. + /// which can be `WordPressOrgXMLRPCApiError` or `WPXMLRPCFaultErrorDomain`. /// /// Swift code should avoid dealing with NSError instances. Instead, they should use the strongly typed /// `WordPressAPIError`. @@ -413,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): @@ -428,5 +408,4 @@ private extension WordPressAPIError where EndpointError == WordPressOrgXMLRPCApi return WordPressOrgXMLRPCApi.convertError(error, data: data, statusCode: statusCode) } - } diff --git a/Sources/CoreAPI/WordPressOrgXMLRPCApiError+NSErrorBridge.swift b/Sources/CoreAPI/WordPressOrgXMLRPCApiError+NSErrorBridge.swift new file mode 100644 index 00000000..f1c26d0a --- /dev/null +++ b/Sources/CoreAPI/WordPressOrgXMLRPCApiError+NSErrorBridge.swift @@ -0,0 +1,14 @@ +import Foundation +#if SWIFT_PACKAGE +import APIInterface +#endif + +/// See `extension WordPressComRestApiEndpointError: CustomNSError` for documentation and rationale. +extension WordPressOrgXMLRPCApiError: CustomNSError { + + public static let errorDomain = WordPressOrgXMLRPCApiErrorDomain + + public var errorCode: Int { code.rawValue } + + public var errorUserInfo: [String: Any] { [:] } +} diff --git a/Sources/CoreAPI/WordPressOrgXMLRPCApiError.swift b/Sources/CoreAPI/WordPressOrgXMLRPCApiError.swift new file mode 100644 index 00000000..946b4a37 --- /dev/null +++ b/Sources/CoreAPI/WordPressOrgXMLRPCApiError.swift @@ -0,0 +1,40 @@ +import Foundation + +public struct WordPressOrgXMLRPCApiError: Error { + + /// Error constants for the WordPress XML-RPC API + @objc public enum Code: 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 + } + + let code: Code +} + +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.") + } + } +} diff --git a/Sources/WordPressKit/WordPressKit.h b/Sources/WordPressKit/WordPressKit.h index 482fdb30..35b8b22b 100644 --- a/Sources/WordPressKit/WordPressKit.h +++ b/Sources/WordPressKit/WordPressKit.h @@ -11,6 +11,7 @@ FOUNDATION_EXPORT const unsigned char WordPressKitVersionString[]; #import #import #import +#import #import #import diff --git a/Tests/CoreAPITests/WordPressComRestApiTests+Error.swift b/Tests/CoreAPITests/WordPressComRestApiTests+Error.swift index f122bbec..fdb5a2e0 100644 --- a/Tests/CoreAPITests/WordPressComRestApiTests+Error.swift +++ b/Tests/CoreAPITests/WordPressComRestApiTests+Error.swift @@ -21,5 +21,4 @@ class WordPressComRestApiErrorTests: XCTestCase { func testErrorDomain() { XCTAssertEqual(WordPressComRestApiErrorDomain, WordPressComRestApiEndpointError.errorDomain) } - } diff --git a/Tests/CoreAPITests/WordPressOrgXMLRPCApiErrorTests.swift b/Tests/CoreAPITests/WordPressOrgXMLRPCApiErrorTests.swift new file mode 100644 index 00000000..8a6d0f9a --- /dev/null +++ b/Tests/CoreAPITests/WordPressOrgXMLRPCApiErrorTests.swift @@ -0,0 +1,25 @@ +#if SWIFT_PACKAGE +import APIInterface +@testable import CoreAPI +#else +@testable import WordPressKit +#endif +import XCTest + +class WordPressOrgXMLRPCApiErrorTests: XCTestCase { + + func testNSErrorBridging() throws { + for error in WordPressOrgXMLRPCApiError.Code.allCases { + let xmlRPCError = try XCTUnwrap(WordPressOrgXMLRPCApiError(code: error)) + let apiError = WordPressAPIError.endpointError(xmlRPCError) + let newNSError = apiError as NSError + + XCTAssertEqual(newNSError.domain, "WordPressKit.WordPressOrgXMLRPCApiError") + XCTAssertEqual(newNSError.code, error.rawValue) + } + } + + func testErrorDomain() { + XCTAssertEqual(WordPressOrgXMLRPCApiErrorDomain, WordPressOrgXMLRPCApiError.errorDomain) + } +} diff --git a/Tests/CoreAPITests/WordPressOrgXMLRPCApiTests.swift b/Tests/CoreAPITests/WordPressOrgXMLRPCApiTests.swift index b198c5d0..e687e21e 100644 --- a/Tests/CoreAPITests/WordPressOrgXMLRPCApiTests.swift +++ b/Tests/CoreAPITests/WordPressOrgXMLRPCApiTests.swift @@ -2,6 +2,7 @@ import XCTest import OHHTTPStubs import wpxmlrpc #if SWIFT_PACKAGE +import APIInterface @testable import CoreAPI import OHHTTPStubsSwift #else @@ -63,13 +64,8 @@ class WordPressOrgXMLRPCApiTests: XCTestCase { }, failure: { (error, _) in expect.fulfill() - // When building for SPM, the compiler doesn't generate the domain constant. -#if SWIFT_PACKAGE - XCTAssertEqual(error.domain, "CoreAPI.WordPressOrgXMLRPCApiError") -#else XCTAssertEqual(error.domain, WordPressOrgXMLRPCApiErrorDomain) -#endif - XCTAssertEqual(error.code, WordPressOrgXMLRPCApiError.httpErrorStatusCode.rawValue) + XCTAssertEqual(error.code, WordPressOrgXMLRPCApiError.Code.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]) @@ -96,12 +92,7 @@ class WordPressOrgXMLRPCApiTests: XCTestCase { expect.fulfill() XCTAssertFalse(error is WordPressOrgXMLRPCApiError) - // When building for SPM, the compiler doesn't generate the domain constant. -#if SWIFT_PACKAGE - XCTAssertEqual(error.domain, "CoreAPI.WordPressOrgXMLRPCApiError") -#else XCTAssertEqual(error.domain, WordPressOrgXMLRPCApiErrorDomain) -#endif XCTAssertEqual(error.code, 403) XCTAssertEqual(error.localizedFailureReason, "An HTTP error code 403 was returned.") XCTAssertNotNil(error.userInfo[WordPressOrgXMLRPCApi.WordPressOrgXMLRPCApiErrorKeyData as String]) @@ -128,15 +119,10 @@ class WordPressOrgXMLRPCApiTests: XCTestCase { failure: { (error, _) in expect.fulfill() - XCTAssertTrue(error is WordPressOrgXMLRPCApiError) - // When building for SPM, the compiler doesn't generate the domain constant. -#if SWIFT_PACKAGE - XCTAssertEqual(error.domain, "CoreAPI.WordPressOrgXMLRPCApiError") -#else XCTAssertEqual(error.domain, WordPressOrgXMLRPCApiErrorDomain) -#endif - 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]) } diff --git a/WordPressKit.xcodeproj/project.pbxproj b/WordPressKit.xcodeproj/project.pbxproj index 6b7eca31..056ddf9b 100644 --- a/WordPressKit.xcodeproj/project.pbxproj +++ b/WordPressKit.xcodeproj/project.pbxproj @@ -64,9 +64,13 @@ 32FC20CE255DCC6100CD0A7B /* JetpackScanThreat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32FC20CD255DCC6100CD0A7B /* JetpackScanThreat.swift */; }; 3F3195AD266FF94B00397EE7 /* ZendeskMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F3195AC266FF94B00397EE7 /* ZendeskMetadata.swift */; }; 3F3C9E9C289A3E31009A1357 /* TestCollector+Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F3C9E9B289A3E31009A1357 /* TestCollector+Constants.swift */; }; + 3F6128132BCB31660063810D /* WordPressOrgXMLRPCApiError+NSErrorBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F6128122BCB31660063810D /* WordPressOrgXMLRPCApiError+NSErrorBridge.swift */; }; + 3F6128162BCB320B0063810D /* WordPressOrgXMLRPCApiErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F6128142BCB31DB0063810D /* WordPressOrgXMLRPCApiErrorTests.swift */; }; 3F758FD324F6C68200BBA2FC /* AnnouncementServiceRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F758FD224F6C68200BBA2FC /* AnnouncementServiceRemote.swift */; }; 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 */; }; @@ -812,9 +816,13 @@ 32FC20CD255DCC6100CD0A7B /* JetpackScanThreat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JetpackScanThreat.swift; sourceTree = ""; }; 3F3195AC266FF94B00397EE7 /* ZendeskMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZendeskMetadata.swift; sourceTree = ""; }; 3F3C9E9B289A3E31009A1357 /* TestCollector+Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TestCollector+Constants.swift"; sourceTree = ""; }; + 3F6128122BCB31660063810D /* WordPressOrgXMLRPCApiError+NSErrorBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WordPressOrgXMLRPCApiError+NSErrorBridge.swift"; sourceTree = ""; }; + 3F6128142BCB31DB0063810D /* WordPressOrgXMLRPCApiErrorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordPressOrgXMLRPCApiErrorTests.swift; sourceTree = ""; }; 3F758FD224F6C68200BBA2FC /* AnnouncementServiceRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnnouncementServiceRemote.swift; sourceTree = ""; }; 3F8308A629EE683500354497 /* ActivityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityTests.swift; sourceTree = ""; }; 3FA4258E2BCCFDA6007539BF /* WordPressComRestApiErrorDomain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WordPressComRestApiErrorDomain.h; sourceTree = ""; }; + 3FA425A62BCF7E7C007539BF /* WordPressOrgXMLRPCApiErrorDomain.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WordPressOrgXMLRPCApiErrorDomain.h; sourceTree = ""; }; + 3FA425A82BCF8721007539BF /* WordPressOrgXMLRPCApiError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordPressOrgXMLRPCApiError.swift; sourceTree = ""; }; 3FB8642D288813E9003A86BE /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UnitTests.xctestplan; sourceTree = ""; }; 3FD634E32BC3A55F00CEDF5E /* WordPressOrgXMLRPCValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WordPressOrgXMLRPCValidator.swift; sourceTree = ""; }; 3FD634E42BC3A55F00CEDF5E /* Date+WordPressCom.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date+WordPressCom.swift"; sourceTree = ""; }; @@ -1580,6 +1588,7 @@ 4A05E79B2B2FDC6100C25E3B /* WordPressOrgAPITests.swift */, FFA4D4A82423B10A00BF5180 /* WordPressOrgRestApiTests.swift */, 74B335DB1F06F4180053A184 /* WordPressOrgXMLRPCApiTests.swift */, + 3F6128142BCB31DB0063810D /* WordPressOrgXMLRPCApiErrorTests.swift */, 46ABD0DF262EED3D00C7FF24 /* WordPressOrgXMLRPCValidatorTests.swift */, ); path = CoreAPITests; @@ -2095,6 +2104,7 @@ children = ( 3FE2E94E2BB29A1B002CA2E1 /* FilePart.h */, 3FA4258E2BCCFDA6007539BF /* WordPressComRestApiErrorDomain.h */, + 3FA425A62BCF7E7C007539BF /* WordPressOrgXMLRPCApiErrorDomain.h */, 3FFCC0552BABC78B0051D229 /* WordPressComRESTAPIInterfacing.h */, 3FE2E9662BBEB8D2002CA2E1 /* WordPressComRESTAPIVersion.h */, 3FD635032BC3F03200CEDF5E /* WordPressComRESTAPIVersionedPathBuilder.h */, @@ -2135,6 +2145,8 @@ 3FE2E97A2BC3A332002CA2E1 /* WordPressComRestApi.swift */, 4A05E7992B2FDC3200C25E3B /* WordPressOrgRestApi.swift */, 93BD27791EE73944002BB00B /* WordPressOrgXMLRPCApi.swift */, + 3FA425A82BCF8721007539BF /* WordPressOrgXMLRPCApiError.swift */, + 3F6128122BCB31660063810D /* WordPressOrgXMLRPCApiError+NSErrorBridge.swift */, 3FD634E32BC3A55F00CEDF5E /* WordPressOrgXMLRPCValidator.swift */, 93BD277B1EE73944002BB00B /* WordPressRSDParser.swift */, ); @@ -2716,6 +2728,7 @@ 9311A6881F22625A00704AC9 /* TaxonomyServiceRemoteREST.h in Headers */, 93188D1E1F2262BF0028ED4D /* RemotePostTag.h in Headers */, 9311A6871F22625A00704AC9 /* TaxonomyServiceRemote.h in Headers */, + 3FA425A72BCF7EDC007539BF /* WordPressOrgXMLRPCApiErrorDomain.h in Headers */, 9309994D1F1657C600F006A1 /* ThemeServiceRemote.h in Headers */, B5969E1C20A49AC4005E9DF1 /* NSString+MD5.h in Headers */, 740B23C41F17EE8000067A2A /* RemotePost.h in Headers */, @@ -3304,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 */, @@ -3381,6 +3395,7 @@ 74BA04F61F06DC0A00ED5CD8 /* CommentServiceRemoteXMLRPC.m in Sources */, 40819778221F00E600A298E4 /* StatsSummaryTimeIntervalData.swift in Sources */, 7430C9A81F1927180051B8E6 /* ReaderTopicServiceRemote.m in Sources */, + 3F6128132BCB31660063810D /* WordPressOrgXMLRPCApiError+NSErrorBridge.swift in Sources */, 17CE77F120C6EB41001DEA5A /* ReaderFeed.swift in Sources */, 93C674F21EE8351E00BFAF05 /* NSMutableDictionary+Helpers.m in Sources */, 4624222D2548BA0F002B8A12 /* RemoteSiteDesign.swift in Sources */, @@ -3516,6 +3531,7 @@ 1DC837C229B9F04F009DCD4B /* RemoteVideoPressVideoTests.swift in Sources */, 3FFCC04D2BABA6980051D229 /* NSDate+WordPressComTests.swift in Sources */, FAD1345125909DEA00A8FEB1 /* JetpackBackupServiceRemoteTests.swift in Sources */, + 3F6128162BCB320B0063810D /* WordPressOrgXMLRPCApiErrorTests.swift in Sources */, 8B2F4BE924ABC9DC0056C08A /* ReaderPostServiceRemote+CardsTests.swift in Sources */, FEF87FEF2BB7343700A1D2C1 /* ReaderTopicServiceRemoteTests.swift in Sources */, 40F9880C221ACEEE00B7B369 /* StatsRemoteV2Tests.swift in Sources */,