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

Commit 01af35a

Browse files
authored
Add multipart POST method to WordPressComRESTAPIInterfacing (#765)
2 parents 77e2e58 + d7d0bf7 commit 01af35a

File tree

10 files changed

+96
-34
lines changed

10 files changed

+96
-34
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ _None._
3535
### Breaking Changes
3636

3737
- Reworked the `NSDate` RFC3339 / WordPress.com JSON conversions API [#759]
38+
- Changed `FilePart` `filename` property to `fileName` [#765]
3839

3940
### New Features
4041

Sources/WordPressKit/Services/MediaServiceRemoteREST.m

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,11 @@ - (void)uploadMedia:(NSArray *)mediaItems
139139
if (remoteMedia.postID != nil && [remoteMedia.postID compare:@(0)] == NSOrderedDescending) {
140140
parameters[@"attrs[0][parent_id]"] = remoteMedia.postID;
141141
}
142-
FilePart *filePart = [[FilePart alloc] initWithParameterName:@"media[]" url:remoteMedia.localURL filename:filename mimeType:type];
142+
FilePart *filePart = [[FilePart alloc] initWithParameterName:@"media[]" url:remoteMedia.localURL fileName:filename mimeType:type];
143143
[fileParts addObject:filePart];
144144
}
145145

146-
[self.wordPressComRestApi multipartPOST:requestUrl
146+
[self.wordPressComRESTAPI multipartPOST:requestUrl
147147
parameters:parameters
148148
fileParts:fileParts
149149
requestEnqueued:^(NSNumber *taskID) {
@@ -203,8 +203,8 @@ - (void)uploadMedia:(RemoteMedia *)media
203203
}
204204
return;
205205
}
206-
FilePart *filePart = [[FilePart alloc] initWithParameterName:@"media[]" url:media.localURL filename:filename mimeType:type];
207-
__block NSProgress *localProgress = [self.wordPressComRestApi multipartPOST:requestUrl
206+
FilePart *filePart = [[FilePart alloc] initWithParameterName:@"media[]" url:media.localURL fileName:filename mimeType:type];
207+
__block NSProgress *localProgress = [self.wordPressComRESTAPI multipartPOST:requestUrl
208208
parameters:parameters
209209
fileParts:@[filePart]
210210
requestEnqueued:nil

Sources/WordPressKit/Services/PostServiceRemoteREST.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,8 @@ - (void)createPost:(RemotePost *)post
164164
parameters[@"content"] = post.content;
165165
parameters[@"title"] = post.title;
166166
parameters[@"status"] = post.status;
167-
FilePart *filePart = [[FilePart alloc] initWithParameterName:@"media[]" url:media.localURL filename:filename mimeType:type];
168-
[self.wordPressComRestApi multipartPOST:requestUrl
167+
FilePart *filePart = [[FilePart alloc] initWithParameterName:@"media[]" url:media.localURL fileName:filename mimeType:type];
168+
[self.wordPressComRESTAPI multipartPOST:requestUrl
169169
parameters:parameters
170170
fileParts:@[filePart]
171171
requestEnqueued:^(NSNumber *taskID) {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#import <Foundation/Foundation.h>
2+
3+
/// Represents the infomartion needed to encode a file on a multipart form request.
4+
@interface FilePart: NSObject
5+
6+
@property (strong, nonatomic) NSString * _Nonnull parameterName;
7+
@property (strong, nonatomic) NSURL * _Nonnull url;
8+
@property (strong, nonatomic) NSString * _Nonnull fileName;
9+
@property (strong, nonatomic) NSString * _Nonnull mimeType;
10+
11+
- (instancetype _Nonnull)initWithParameterName:(NSString * _Nonnull)parameterName
12+
url:(NSURL * _Nonnull)url
13+
fileName:(NSString * _Nonnull)fileName
14+
mimeType:(NSString * _Nonnull)mimeType;
15+
16+
@end
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#import "FilePart.h"
2+
3+
@implementation FilePart
4+
5+
- (instancetype)initWithParameterName:(NSString *)parameterName
6+
url:(NSURL *)url
7+
fileName:(NSString *)fileName
8+
mimeType:(NSString *)mimeType
9+
{
10+
self = [super init];
11+
self.parameterName = parameterName;
12+
self.url = url;
13+
self.fileName = fileName;
14+
self.mimeType = mimeType;
15+
return self;
16+
}
17+
18+
@end

Sources/WordPressKit/WordPressAPI/WordPressComRESTAPIInterfacing.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
@import Foundation;
22

3+
@class FilePart;
4+
35
@protocol WordPressComRESTAPIInterfacing
46

57
@property (strong, nonatomic, readonly) NSURL * _Nonnull baseURL;
@@ -14,4 +16,11 @@
1416
success:(void (^ _Nonnull)(id _Nonnull, NSHTTPURLResponse * _Nullable))success
1517
failure:(void (^ _Nonnull)(NSError * _Nonnull, NSHTTPURLResponse * _Nullable))failure;
1618

19+
- (NSProgress * _Nullable)multipartPOST:(NSString * _Nonnull)URLString
20+
parameters:(NSDictionary<NSString *, NSObject *> * _Nullable)parameters
21+
fileParts:(NSArray<FilePart *> * _Nonnull)fileParts
22+
requestEnqueued:(void (^ _Nullable)(NSNumber * _Nonnull))requestEnqueue
23+
success:(void (^ _Nonnull)(id _Nonnull, NSHTTPURLResponse * _Nullable))success
24+
failure:(void (^ _Nonnull)(NSError * _Nonnull, NSHTTPURLResponse * _Nullable))failure;
25+
1726
@end

Sources/WordPressKit/WordPressAPI/WordPressComRestApi.swift

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -268,16 +268,18 @@ open class WordPressComRestApi: NSObject {
268268
- parameter success: callback to be called on successful request
269269
- parameter failure: callback to be called on failed request
270270

271-
- returns: a NSProgress object that can be used to track the progress of the upload and to cancel the upload. If the method
271+
- returns: a `Progerss` object that can be used to track the progress of the upload and to cancel the upload. If the method
272272
returns nil it's because something happened on the request serialization and the network request was not started, but the failure callback
273273
will be invoked with the error specificing the serialization issues.
274274
*/
275-
@objc @discardableResult open func multipartPOST(_ URLString: String,
276-
parameters: [String: AnyObject]?,
277-
fileParts: [FilePart],
278-
requestEnqueued: RequestEnqueuedBlock? = nil,
279-
success: @escaping SuccessResponseBlock,
280-
failure: @escaping FailureReponseBlock) -> Progress? {
275+
@nonobjc @discardableResult open func multipartPOST(
276+
_ URLString: String,
277+
parameters: [String: AnyObject]?,
278+
fileParts: [FilePart],
279+
requestEnqueued: RequestEnqueuedBlock? = nil,
280+
success: @escaping SuccessResponseBlock,
281+
failure: @escaping FailureReponseBlock
282+
) -> Progress? {
281283
let progress = Progress.discreteProgress(totalUnitCount: 100)
282284

283285
Task { @MainActor in
@@ -452,7 +454,7 @@ open class WordPressComRestApi: NSObject {
452454
let builder: HTTPRequestBuilder
453455
do {
454456
let form = try fileParts.map {
455-
try MultipartFormField(fileAtPath: $0.url.path, name: $0.parameterName, filename: $0.filename, mimeType: $0.mimeType)
457+
try MultipartFormField(fileAtPath: $0.url.path, name: $0.parameterName, filename: $0.fileName, mimeType: $0.mimeType)
456458
}
457459
builder = try requestBuilder(URLString: URLString)
458460
.method(.post)
@@ -476,23 +478,6 @@ open class WordPressComRestApi: NSObject {
476478

477479
}
478480

479-
// MARK: - FilePart
480-
481-
/// FilePart represents the infomartion needed to encode a file on a multipart form request
482-
public final class FilePart: NSObject {
483-
@objc let parameterName: String
484-
@objc let url: URL
485-
@objc let filename: String
486-
@objc let mimeType: String
487-
488-
@objc public init(parameterName: String, url: URL, filename: String, mimeType: String) {
489-
self.parameterName = parameterName
490-
self.url = url
491-
self.filename = filename
492-
self.mimeType = mimeType
493-
}
494-
}
495-
496481
// MARK: - Error processing
497482

498483
extension WordPressComRestApi {
@@ -622,7 +607,6 @@ extension WordPressAPIError<WordPressComRestApiEndpointError> {
622607
}
623608

624609
extension WordPressComRestApi: WordPressComRESTAPIInterfacing {
625-
626610
// A note on the naming: Even if defined as `GET` in Objective-C, then method gets converted to Swift as `get`.
627611
//
628612
// Also, there is no Objective-C direct equivalent of `AnyObject`, which here is used in `parameters: [String: AnyObject]?`.
@@ -646,4 +630,28 @@ extension WordPressComRestApi: WordPressComRESTAPIInterfacing {
646630
) -> Progress? {
647631
POST(URLString, parameters: parameters, success: success, failure: failure)
648632
}
633+
634+
public func multipartPOST(
635+
_ URLString: String,
636+
parameters: [String: NSObject]?,
637+
fileParts: [FilePart],
638+
// Notice this does not require @escaping because it is Optional.
639+
//
640+
// Annotate with @escaping, and the compiler will fail with:
641+
// > Closure is already escaping in optional type argument
642+
//
643+
// It is necessary to explicitly set this as Optional because of the _Nullable parameter requirement in the Objective-C protocol.
644+
requestEnqueued: ((NSNumber) -> Void)?,
645+
success: @escaping (Any, HTTPURLResponse?) -> Void,
646+
failure: @escaping (any Error, HTTPURLResponse?) -> Void
647+
) -> Progress? {
648+
multipartPOST(
649+
URLString,
650+
parameters: parameters,
651+
fileParts: fileParts,
652+
requestEnqueued: requestEnqueued,
653+
success: success as SuccessResponseBlock,
654+
failure: failure as FailureReponseBlock
655+
)
656+
}
649657
}

Sources/WordPressKit/WordPressKit.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ FOUNDATION_EXPORT double WordPressKitVersionNumber;
66
//! Project version string for WordPressKit.
77
FOUNDATION_EXPORT const unsigned char WordPressKitVersionString[];
88

9+
#import <WordPressKit/FilePart.h>
910
#import <WordPressKit/WordPressComRESTAPIInterfacing.h>
11+
1012
#import <WordPressKit/ServiceRemoteWordPressComREST.h>
1113
#import <WordPressKit/ServiceRemoteWordPressXMLRPC.h>
1214
#import <WordPressKit/SiteServiceRemoteWordPressComREST.h>

Tests/WordPressKitTests/Tests/WordPressComRestApiTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ class WordPressComRestApiTests: XCTestCase {
295295

296296
let expect = self.expectation(description: "One callback should be invoked")
297297
let api = WordPressComRestApi(oAuthToken: "fakeToken")
298-
let filePart = FilePart(parameterName: "file", url: URL(fileURLWithPath: "/a.txt") as URL, filename: "a.txt", mimeType: "image/jpeg")
298+
let filePart = FilePart(parameterName: "file", url: URL(fileURLWithPath: "/a.txt") as URL, fileName: "a.txt", mimeType: "image/jpeg")
299299
api.multipartPOST(wordPressMediaNewEndpointPath, parameters: nil, fileParts: [filePart], success: { (_: AnyObject, _: HTTPURLResponse?) in
300300
expect.fulfill()
301301
XCTFail("This call should fail")
@@ -319,7 +319,7 @@ class WordPressComRestApiTests: XCTestCase {
319319
let mediaURL = URL(fileURLWithPath: mediaPath)
320320
let expect = self.expectation(description: "One callback should be invoked")
321321
let api = WordPressComRestApi(oAuthToken: "fakeToken")
322-
let filePart = FilePart(parameterName: "media[]", url: mediaURL as URL, filename: "test-image.jpg", mimeType: "image/jpeg")
322+
let filePart = FilePart(parameterName: "media[]", url: mediaURL as URL, fileName: "test-image.jpg", mimeType: "image/jpeg")
323323
let progress1 = api.multipartPOST(wordPressMediaNewEndpointPath, parameters: nil, fileParts: [filePart], success: { (_: AnyObject, _: HTTPURLResponse?) in
324324
XCTFail("This call should fail")
325325
}, failure: { (error, _) in

WordPressKit.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@
7171
3F758FD324F6C68200BBA2FC /* AnnouncementServiceRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F758FD224F6C68200BBA2FC /* AnnouncementServiceRemote.swift */; };
7272
3F8308A729EE683500354497 /* ActivityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F8308A629EE683500354497 /* ActivityTests.swift */; };
7373
3FB8642C2888089F003A86BE /* BuildkiteTestCollector in Frameworks */ = {isa = PBXBuildFile; productRef = 3FB8642B2888089F003A86BE /* BuildkiteTestCollector */; };
74+
3FE2E94F2BB29A1B002CA2E1 /* FilePart.m in Sources */ = {isa = PBXBuildFile; fileRef = 3FE2E94D2BB29A1B002CA2E1 /* FilePart.m */; };
75+
3FE2E9502BB29A1B002CA2E1 /* FilePart.h in Headers */ = {isa = PBXBuildFile; fileRef = 3FE2E94E2BB29A1B002CA2E1 /* FilePart.h */; settings = {ATTRIBUTES = (Public, ); }; };
7476
3FFCC0412BA995290051D229 /* Date+WordPressComTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFCC0402BA995290051D229 /* Date+WordPressComTests.swift */; };
7577
3FFCC0472BAA6EF40051D229 /* NSDate+WordPressCom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFCC0462BAA6EF40051D229 /* NSDate+WordPressCom.swift */; };
7678
3FFCC0492BAB98130051D229 /* DateFormatter+WordPressCom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFCC0482BAB98130051D229 /* DateFormatter+WordPressCom.swift */; };
@@ -803,6 +805,8 @@
803805
3F758FD224F6C68200BBA2FC /* AnnouncementServiceRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnnouncementServiceRemote.swift; sourceTree = "<group>"; };
804806
3F8308A629EE683500354497 /* ActivityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityTests.swift; sourceTree = "<group>"; };
805807
3FB8642D288813E9003A86BE /* UnitTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = UnitTests.xctestplan; sourceTree = "<group>"; };
808+
3FE2E94D2BB29A1B002CA2E1 /* FilePart.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FilePart.m; sourceTree = "<group>"; };
809+
3FE2E94E2BB29A1B002CA2E1 /* FilePart.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FilePart.h; sourceTree = "<group>"; };
806810
3FFCC0402BA995290051D229 /* Date+WordPressComTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+WordPressComTests.swift"; sourceTree = "<group>"; };
807811
3FFCC0462BAA6EF40051D229 /* NSDate+WordPressCom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSDate+WordPressCom.swift"; sourceTree = "<group>"; };
808812
3FFCC0482BAB98130051D229 /* DateFormatter+WordPressCom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+WordPressCom.swift"; sourceTree = "<group>"; };
@@ -1534,6 +1538,8 @@
15341538
3FE2E9352BB10D92002CA2E1 /* WordPressAPI */ = {
15351539
isa = PBXGroup;
15361540
children = (
1541+
3FE2E94E2BB29A1B002CA2E1 /* FilePart.h */,
1542+
3FE2E94D2BB29A1B002CA2E1 /* FilePart.m */,
15371543
3FFCC0552BABC78B0051D229 /* WordPressComRESTAPIInterfacing.h */,
15381544
4A05E7952B2FCB6400C25E3B /* NonceRetrieval.swift */,
15391545
4A05E7992B2FDC3200C25E3B /* WordPressOrgRestApi.swift */,
@@ -2579,6 +2585,7 @@
25792585
7430C9B11F1927C50051B8E6 /* RemoteReaderPost.h in Headers */,
25802586
7430C9A51F1927180051B8E6 /* ReaderSiteServiceRemote.h in Headers */,
25812587
7430C9A31F1927180051B8E6 /* ReaderPostServiceRemote.h in Headers */,
2588+
3FE2E9502BB29A1B002CA2E1 /* FilePart.h in Headers */,
25822589
742362D61F10250600BD0A7F /* MenusServiceRemote.h in Headers */,
25832590
740B23BA1F17EC7300067A2A /* PostServiceRemoteXMLRPC.h in Headers */,
25842591
740B23BD1F17ECB500067A2A /* PostServiceRemoteOptions.h in Headers */,
@@ -3291,6 +3298,7 @@
32913298
82FFBF501F45EFD100F4573F /* RemoteBlogJetpackSettings.swift in Sources */,
32923299
74650F741F0EA1E200188EDB /* RemoteGravatarProfile.swift in Sources */,
32933300
40E7FEB4221063480032834E /* StatsTodayInsight.swift in Sources */,
3301+
3FE2E94F2BB29A1B002CA2E1 /* FilePart.m in Sources */,
32943302
F41D98EA2B48602B004EC050 /* SessionDetails.swift in Sources */,
32953303
436D563C2118E18D00CEAA33 /* WPState.swift in Sources */,
32963304
439A44DA2107C93000795ED7 /* RemotePlan_ApiVersion1_3.swift in Sources */,

0 commit comments

Comments
 (0)