Skip to content
This repository was archived by the owner on Sep 15, 2025. It is now read-only.

Commit 8f55cb1

Browse files
committed
Merge branch 'trunk' into multipart-form-request-builder
2 parents dbb756d + e9afb31 commit 8f55cb1

22 files changed

+314
-134
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ _None._
3434

3535
### Breaking Changes
3636

37-
_None._
37+
- `WordPressComRestApiError` is renamed to `WordPressRestApiErrorCode`, and no longer conforms to `Swift.Error`. [#696]
3838

3939
### New Features
4040

WordPressKit.xcodeproj/project.pbxproj

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,10 @@
155155
4A1DEF44293051BC00322608 /* LoggingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A1DEF43293051BC00322608 /* LoggingTests.swift */; };
156156
4A1DEF46293051C600322608 /* LoggingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A1DEF45293051C600322608 /* LoggingTests.m */; };
157157
4A40F6552B2A5A1A0015DA77 /* WordPressAPIErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A40F6542B2A5A1A0015DA77 /* WordPressAPIErrorTests.swift */; };
158+
4A57A6812B549144008D0660 /* WordPressComRestApiTests+Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A57A6802B549144008D0660 /* WordPressComRestApiTests+Error.swift */; };
159+
4A57A6832B54A326008D0660 /* WordPressAPIError+NSErrorBrdige.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A57A6822B54A326008D0660 /* WordPressAPIError+NSErrorBrdige.swift */; };
160+
4A57A6872B54C68C008D0660 /* Constants.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A57A6852B54C68C008D0660 /* Constants.h */; settings = {ATTRIBUTES = (Public, ); }; };
161+
4A57A6882B54C68C008D0660 /* Constants.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A57A6862B54C68C008D0660 /* Constants.m */; };
158162
4A5BC1A82B59DE6600C7D037 /* Either.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A5BC1A72B59DE6600C7D037 /* Either.swift */; };
159163
4A68E3CD29404181004AC3DC /* RemoteBlog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A68E3CC29404181004AC3DC /* RemoteBlog.swift */; };
160164
4A68E3CF29404289004AC3DC /* RemoteBlogOptionsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A68E3CE29404289004AC3DC /* RemoteBlogOptionsHelper.swift */; };
@@ -866,6 +870,10 @@
866870
4A1DEF43293051BC00322608 /* LoggingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggingTests.swift; sourceTree = "<group>"; };
867871
4A1DEF45293051C600322608 /* LoggingTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LoggingTests.m; sourceTree = "<group>"; };
868872
4A40F6542B2A5A1A0015DA77 /* WordPressAPIErrorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordPressAPIErrorTests.swift; sourceTree = "<group>"; };
873+
4A57A6802B549144008D0660 /* WordPressComRestApiTests+Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WordPressComRestApiTests+Error.swift"; sourceTree = "<group>"; };
874+
4A57A6822B54A326008D0660 /* WordPressAPIError+NSErrorBrdige.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WordPressAPIError+NSErrorBrdige.swift"; sourceTree = "<group>"; };
875+
4A57A6852B54C68C008D0660 /* Constants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Constants.h; sourceTree = "<group>"; };
876+
4A57A6862B54C68C008D0660 /* Constants.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Constants.m; sourceTree = "<group>"; };
869877
4A5BC1A72B59DE6600C7D037 /* Either.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Either.swift; sourceTree = "<group>"; };
870878
4A68E3CC29404181004AC3DC /* RemoteBlog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteBlog.swift; sourceTree = "<group>"; };
871879
4A68E3CE29404289004AC3DC /* RemoteBlogOptionsHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteBlogOptionsHelper.swift; sourceTree = "<group>"; };
@@ -1848,6 +1856,7 @@
18481856
FFE247A620C891D1002DF3A2 /* WordPressComOAuthClientTests.swift */,
18491857
74B335D91F06F3D60053A184 /* WordPressComRestApiTests.swift */,
18501858
73B3DAD521FBB20D00B2CF18 /* WordPressComRestApiTests+Locale.swift */,
1859+
4A57A6802B549144008D0660 /* WordPressComRestApiTests+Error.swift */,
18511860
FFA4D4A82423B10A00BF5180 /* WordPressOrgRestApiTests.swift */,
18521861
74B335DB1F06F4180053A184 /* WordPressOrgXMLRPCApiTests.swift */,
18531862
740B23D51F17F7C100067A2A /* XMLRPCTestable.swift */,
@@ -2467,6 +2476,7 @@
24672476
4A11239D2B1926D1004690CF /* HTTPClient.swift */,
24682477
4A11239B2B1926B7004690CF /* HTTPRequestBuilder.swift */,
24692478
4A1123992B19269A004690CF /* WordPressAPIError.swift */,
2479+
4A57A6822B54A326008D0660 /* WordPressAPIError+NSErrorBrdige.swift */,
24702480
);
24712481
name = WordPressAPI;
24722482
sourceTree = "<group>";
@@ -2487,6 +2497,8 @@
24872497
4AE278432B2FAF6200E4D9B1 /* HTTPProtocolHelpers.swift */,
24882498
3F391E192B50F3EB007975C4 /* Result+Callback.swift */,
24892499
4A5BC1A72B59DE6600C7D037 /* Either.swift */,
2500+
4A57A6852B54C68C008D0660 /* Constants.h */,
2501+
4A57A6862B54C68C008D0660 /* Constants.m */,
24902502
);
24912503
name = Utility;
24922504
sourceTree = "<group>";
@@ -2770,6 +2782,7 @@
27702782
740B23C41F17EE8000067A2A /* RemotePost.h in Headers */,
27712783
740B23C21F17EE8000067A2A /* RemotePostCategory.h in Headers */,
27722784
B5A4822F20AC6C1A009D95F6 /* WPKitLogging.h in Headers */,
2785+
4A57A6872B54C68C008D0660 /* Constants.h in Headers */,
27732786
9309995B1F16616A00F006A1 /* RemoteTheme.h in Headers */,
27742787
1A4F98672279A87D00D86E8E /* WPKit-Swift.h in Headers */,
27752788
93F50A371F226B9300B5BEBA /* WordPressComServiceRemote.h in Headers */,
@@ -3337,6 +3350,7 @@
33373350
93BD277E1EE73944002BB00B /* NSDate+WordPressJSON.m in Sources */,
33383351
8B2F4BED24ABCAEF0056C08A /* Decodable+Dictionary.swift in Sources */,
33393352
E194CB731FBDEF6500B0A8B8 /* PluginState.swift in Sources */,
3353+
4A57A6882B54C68C008D0660 /* Constants.m in Sources */,
33403354
404057D6221C92660060250C /* StatsTopClicksTimeIntervalData.swift in Sources */,
33413355
9AF4F2FC218331DC00570E4B /* PostServiceRemoteREST+Revisions.swift in Sources */,
33423356
F4B0F4732ACAF498003ABC61 /* DomainsServiceRemote+AllDomains.swift in Sources */,
@@ -3434,6 +3448,7 @@
34343448
436D563C2118E18D00CEAA33 /* WPState.swift in Sources */,
34353449
439A44DA2107C93000795ED7 /* RemotePlan_ApiVersion1_3.swift in Sources */,
34363450
93BD27811EE73944002BB00B /* WordPressOrgXMLRPCApi.swift in Sources */,
3451+
4A57A6832B54A326008D0660 /* WordPressAPIError+NSErrorBrdige.swift in Sources */,
34373452
439A44D62107C66A00795ED7 /* JSONDecoderExtension.swift in Sources */,
34383453
B5A4822B20AC6C0B009D95F6 /* WPKitLogging.swift in Sources */,
34393454
B5A4822E20AC6C1A009D95F6 /* WPKitLogging.m in Sources */,
@@ -3561,6 +3576,7 @@
35613576
FFE247A720C891D1002DF3A2 /* WordPressComOAuthClientTests.swift in Sources */,
35623577
93AB06041EE8838400EF8764 /* RemoteTestCase.swift in Sources */,
35633578
4A40F6552B2A5A1A0015DA77 /* WordPressAPIErrorTests.swift in Sources */,
3579+
4A57A6812B549144008D0660 /* WordPressComRestApiTests+Error.swift in Sources */,
35643580
BA2A78FA24A486D300BB6F53 /* SitePluginTests.swift in Sources */,
35653581
465F88A7263B371D00F4C950 /* BlockEditorSettingsServiceRemoteTests.swift in Sources */,
35663582
93BD27411EE73311002BB00B /* AccountServiceRemoteRESTTests.swift in Sources */,

WordPressKit/ActivityServiceRemote.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ open class ActivityServiceRemote: ServiceRemoteWordPressComREST {
154154
}, failure: { error, _ in
155155
// FIXME: A hack to support free WPCom sites and Rewind. Should be obsolote as soon as the backend
156156
// stops returning 412's for those sites.
157-
if let error = error as? WordPressComRestApiError, error == WordPressComRestApiError.preconditionFailure {
157+
if error.domain == WordPressComRestApiEndpointError.errorDomain, error.code == WordPressComRestApiErrorCode.preconditionFailure.rawValue {
158158
let status = RewindStatus(state: .unavailable)
159159
success(status)
160160
return

WordPressKit/Constants.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#import <Foundation/Foundation.h>
2+
3+
NS_ASSUME_NONNULL_BEGIN
4+
5+
FOUNDATION_EXTERN NSString *WordPressComRestApiErrorDomain;
6+
7+
NS_ASSUME_NONNULL_END

WordPressKit/Constants.m

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#import "Constants.h"
2+
3+
/// Error domain of `NSError` instances that are converted from `WordPressComRestApiEndpointError`
4+
/// and `WordPressAPIError<WordPressComRestApiEndpointError>` instances.
5+
///
6+
/// See `extension WordPressComRestApiEndpointError: CustomNSError` for context.
7+
NSString *WordPressComRestApiErrorDomain = @"WordPressKit.WordPressComRestApiError";

WordPressKit/MediaServiceRemoteREST.m

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ - (NSError *)processMediaUploadErrors:(NSArray *)errorList {
244244
errorMessage = errorInfo[@"message"];
245245
}
246246
NSDictionary *errorDictionary = @{NSLocalizedDescriptionKey: errorMessage};
247-
error = [NSError errorWithDomain:WordPressComRestApiErrorDomain code:WordPressComRestApiErrorUploadFailed userInfo:errorDictionary];
247+
error = [NSError errorWithDomain:WordPressComRestApiErrorDomain code:WordPressComRestApiErrorCodeUploadFailed userInfo:errorDictionary];
248248
}
249249
return error;
250250
}
@@ -297,7 +297,7 @@ - (void)deleteMedia:(RemoteMedia *)media
297297
} else {
298298
if (failure) {
299299
NSError *error = [NSError errorWithDomain:WordPressComRestApiErrorDomain
300-
code:WordPressComRestApiErrorUnknown
300+
code:WordPressComRestApiErrorCodeUnknown
301301
userInfo:nil];
302302
failure(error);
303303
}
@@ -370,7 +370,7 @@ -(void)getVideoPressToken:(NSString *)videoPressID
370370
} else {
371371
if (failure) {
372372
NSError *error = [NSError errorWithDomain:WordPressComRestApiErrorDomain
373-
code:WordPressComRestApiErrorUnknown
373+
code:WordPressComRestApiErrorCodeUnknown
374374
userInfo:nil];
375375
failure(error);
376376
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import Foundation
2+
3+
/// Custom `NSError` bridge implementation.
4+
///
5+
/// The implementation ensures `NSError` instances that are casted from `WordPressAPIError<EndpointError>.endpointError`
6+
/// are the same as those instances that are casted directly from the underlying `EndpointError` instances.
7+
///
8+
/// In theory, we should not need to implement this bridging, because we should never cast errors to `NSError`
9+
/// instances in any error handling code. But since there are still Objective-C callers, providing this custom bridging
10+
/// implementation comes in handy for those cases. See the `WordPressComRestApiEndpointError` extension below.
11+
extension WordPressAPIError: CustomNSError {
12+
13+
public static var errorDomain: String {
14+
(EndpointError.self as? CustomNSError.Type)?.errorDomain
15+
?? String(describing: Self.self)
16+
}
17+
18+
public var errorCode: Int {
19+
switch self {
20+
case let .endpointError(endpointError):
21+
return (endpointError as NSError).code
22+
// Use negative values for other cases to reduce chances of collision with `EndpointError`.
23+
case .requestEncodingFailure:
24+
return -100000
25+
case .connection:
26+
return -100001
27+
case .unacceptableStatusCode:
28+
return -100002
29+
case .unparsableResponse:
30+
return -100003
31+
case .unknown:
32+
return -100004
33+
}
34+
}
35+
36+
public var errorUserInfo: [String: Any] {
37+
switch self {
38+
case let .endpointError(endpointError):
39+
return (endpointError as NSError).userInfo
40+
case .requestEncodingFailure, .connection, .unacceptableStatusCode, .unparsableResponse,
41+
.unknown:
42+
return [:]
43+
}
44+
45+
}
46+
47+
}
48+
49+
// MARK: - Bridge WordPressComRestApiEndpointError to NSError
50+
51+
/// A custom NSError bridge implementation to ensure `NSError` instances converted from `WordPressComRestApiEndpointError`
52+
/// are the same as the ones converted from their underlying error (the `code: WordPressComRestApiError` property in
53+
/// `WordPressComRestApiEndpointError`).
54+
///
55+
/// Along with `WordPressAPIError`'s conformance to `CustomNSError`, the three `NSError` instances below have the
56+
/// same domain and code.
57+
///
58+
/// ```
59+
/// let error: WordPressComRestApiError = // ...
60+
/// let newError: WordPressComRestApiEndpointError = .init(code: error)
61+
/// let apiError: WordPressAPIError<WordPressComRestApiEndpointError> = .endpointError(newError)
62+
///
63+
/// // Following `NSError` instance have the same domain and code.
64+
/// let errorNSError = error as NSError
65+
/// let newErrorNSError = newError as NSError
66+
/// let apiErrorNSError = apiError as NSError
67+
/// ```
68+
///
69+
/// ## Why implementing this custom NSError brdige?
70+
///
71+
/// `WordPressComRestApi` returns `NSError` instances to their callers. Since `WordPressComRestApi` is used in many
72+
/// Objective-C file, we can't change the API to return an `Error` type that's Swift-only (i.e. `WordPressAPIError`).
73+
/// If the day where there are no Objective-C callers finally comes, we definitely should stop returning `NSError` and
74+
/// start using a concrete error type instead. But for now, we have to provide backwards compatiblity to those
75+
/// Objective-C code while using `WordPressAPIError` internally in `WordPressComRestApi`.
76+
///
77+
/// The `NSError` instances returned by `WordPressComRestApi` is one of the following:
78+
/// - `WordPressComRestApiError` enum cases that are directly converted to `NSError`
79+
/// - `NSError` instances that have domain and code from `WordPressComRestApiError`, with additional `userInfo` (error
80+
/// code, message, etc).
81+
/// - Error instances returned by Alamofire 4: `AFError`, or maybe other errors.
82+
///
83+
/// Alamofire will be removed from this library, there is no point (also not possible) in providing backwards
84+
/// compatiblity to `AFError`. That means, we need to make sure the `NSError` instances that are converted from
85+
/// `WordPressAPIError` have the same error domain and code as the underlying `WordPressComRestApiError` enum.
86+
/// And in cases where additional user info was provided, they need to be carried over to the `NSError` instances.
87+
extension WordPressComRestApiEndpointError: CustomNSError {
88+
89+
// This value is the same as the `WordPressComRestApiErrorDomain` constant generated by Swift compiler.
90+
public static let errorDomain = "WordPressKit.WordPressComRestApiError"
91+
92+
public var errorCode: Int {
93+
code.rawValue
94+
}
95+
96+
public var errorUserInfo: [String: Any] {
97+
var userInfo = additionalUserInfo ?? [:]
98+
99+
if let code = apiErrorCode {
100+
userInfo[WordPressComRestApi.ErrorKeyErrorCode] = code
101+
}
102+
if let message = apiErrorMessage {
103+
userInfo[WordPressComRestApi.ErrorKeyErrorMessage] = message
104+
userInfo[NSLocalizedDescriptionKey] = message
105+
}
106+
if let data = apiErrorData {
107+
userInfo[WordPressComRestApi.ErrorKeyErrorData] = data
108+
}
109+
110+
return userInfo
111+
112+
}
113+
114+
}

0 commit comments

Comments
 (0)