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

Commit dfb2134

Browse files
authored
Rewrite WordPressOrgRestApi (#724)
2 parents ec0e24f + c44bb13 commit dfb2134

23 files changed

+992
-737
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+
- Rewrite `WordPressOrgRestApi` to support self hosted sites and WordPress.com sites. [#724]
3838

3939
### New Features
4040

WordPressKit.xcodeproj/project.pbxproj

Lines changed: 24 additions & 20 deletions
Large diffs are not rendered by default.

WordPressKit/Authenticator.swift

Lines changed: 0 additions & 153 deletions
This file was deleted.
Lines changed: 20 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import Foundation
22

33
public class BlockEditorSettingsServiceRemote {
4-
let remoteAPI: WordPressRestApi
5-
public init(remoteAPI: WordPressRestApi) {
4+
let remoteAPI: WordPressOrgRestApi
5+
public init(remoteAPI: WordPressOrgRestApi) {
66
self.remoteAPI = remoteAPI
77
}
88
}
@@ -11,30 +11,15 @@ public class BlockEditorSettingsServiceRemote {
1111
public extension BlockEditorSettingsServiceRemote {
1212
typealias EditorThemeCompletionHandler = (Swift.Result<RemoteEditorTheme?, Error>) -> Void
1313

14-
func fetchTheme(forSiteID siteID: Int?, _ completion: @escaping EditorThemeCompletionHandler) {
14+
func fetchTheme(completion: @escaping EditorThemeCompletionHandler) {
1515
let requestPath = "/wp/v2/themes"
16-
let parameters: [String: AnyObject] = ["status": "active" as AnyObject]
17-
let modifiedPath = remoteAPI.requestPath(fromOrgPath: requestPath, with: siteID)
18-
remoteAPI.GET(modifiedPath, parameters: parameters) { [weak self] (result, _) in
19-
guard let `self` = self else { return }
20-
switch result {
21-
case .success(let response):
22-
self.processEditorThemeResponse(response) { editorTheme in
23-
completion(.success(editorTheme))
24-
}
25-
case .failure(let error):
26-
completion(.failure(error))
27-
}
28-
}
29-
}
30-
31-
private func processEditorThemeResponse(_ response: Any, completion: (_ editorTheme: RemoteEditorTheme?) -> Void) {
32-
guard let responseData = try? JSONSerialization.data(withJSONObject: response, options: []),
33-
let editorThemes = try? JSONDecoder().decode([RemoteEditorTheme].self, from: responseData) else {
34-
completion(nil)
35-
return
16+
let parameters = ["status": "active"]
17+
Task { @MainActor in
18+
let result = await self.remoteAPI.get(path: requestPath, parameters: parameters, type: [RemoteEditorTheme].self)
19+
.map { $0.first }
20+
.mapError { error -> Error in error }
21+
completion(result)
3622
}
37-
completion(editorThemes.first)
3823
}
3924

4025
}
@@ -43,30 +28,18 @@ public extension BlockEditorSettingsServiceRemote {
4328
public extension BlockEditorSettingsServiceRemote {
4429
typealias BlockEditorSettingsCompletionHandler = (Swift.Result<RemoteBlockEditorSettings?, Error>) -> Void
4530

46-
func fetchBlockEditorSettings(forSiteID siteID: Int?, _ completion: @escaping BlockEditorSettingsCompletionHandler) {
47-
let requestPath = "/wp-block-editor/v1/settings"
48-
let parameters: [String: AnyObject] = ["context": "mobile" as AnyObject]
49-
let modifiedPath = remoteAPI.requestPath(fromOrgPath: requestPath, with: siteID)
50-
51-
remoteAPI.GET(modifiedPath, parameters: parameters) { [weak self] (result, _) in
52-
guard let `self` = self else { return }
53-
switch result {
54-
case .success(let response):
55-
self.processBlockEditorSettingsResponse(response) { blockEditorSettings in
56-
completion(.success(blockEditorSettings))
31+
func fetchBlockEditorSettings(completion: @escaping BlockEditorSettingsCompletionHandler) {
32+
Task { @MainActor in
33+
let result = await self.remoteAPI.get(path: "/wp-block-editor/v1/settings", parameters: ["context": "mobile"], type: RemoteBlockEditorSettings.self)
34+
.map { settings -> RemoteBlockEditorSettings? in settings }
35+
.flatMapError { original in
36+
if case let .unparsableResponse(response, _, underlyingError) = original, response?.statusCode == 200, underlyingError is DecodingError {
37+
return .success(nil)
38+
}
39+
return .failure(original)
5740
}
58-
case .failure(let error):
59-
completion(.failure(error))
60-
}
61-
}
62-
}
63-
64-
private func processBlockEditorSettingsResponse(_ response: Any, completion: (_ editorTheme: RemoteBlockEditorSettings?) -> Void) {
65-
guard let responseData = try? JSONSerialization.data(withJSONObject: response, options: []),
66-
let blockEditorSettings = try? JSONDecoder().decode(RemoteBlockEditorSettings.self, from: responseData) else {
67-
completion(nil)
68-
return
41+
.mapError { error -> Error in error }
42+
completion(result)
6943
}
70-
completion(blockEditorSettings)
7144
}
7245
}

WordPressKit/HTTPClient.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,11 @@ extension WordPressAPIResult {
162162
}
163163

164164
func decodeSuccess<NewSuccess: Decodable, E: LocalizedError>(
165-
_ decoder: JSONDecoder = JSONDecoder()
165+
_ decoder: JSONDecoder = JSONDecoder(),
166+
type: NewSuccess.Type = NewSuccess.self
166167
) -> WordPressAPIResult<NewSuccess, E> where Success == HTTPAPIResponse<Data>, Failure == WordPressAPIError<E> {
167168
mapSuccess {
168-
try decoder.decode(NewSuccess.self, from: $0.body)
169+
try decoder.decode(type, from: $0.body)
169170
}
170171
}
171172

WordPressKit/HTTPProtocolHelpers.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,22 @@ extension HTTPURLResponse {
5757
return Self.value(ofParameter: parameterName, inHeaderValue: headerValue, stripQuotes: stripQuotes)
5858
}
5959

60+
func value(forHTTPHeaderField field: String, withoutParameters: Bool) -> String? {
61+
guard withoutParameters else {
62+
return value(forHTTPHeaderField: field)
63+
}
64+
65+
guard let headerValue = value(forHTTPHeaderField: field) else {
66+
return nil
67+
}
68+
69+
guard let firstSemicolon = headerValue.firstIndex(of: ";") else {
70+
return headerValue
71+
}
72+
73+
return String(headerValue[headerValue.startIndex..<firstSemicolon])
74+
}
75+
6076
static func value(ofParameter parameterName: String, inHeaderValue headerValue: String, stripQuotes: Bool = true) -> String? {
6177
// Find location of '<parameter>=' string in the header.
6278
guard let location = headerValue.range(of: parameterName + "=", options: .caseInsensitive) else {

WordPressKit/HTTPRequestBuilder.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,23 @@ final class HTTPRequestBuilder {
5151
return self
5252
}
5353

54+
/// Append path and query to the original URL.
55+
///
56+
/// Some may call API client using a string that contains path and query, like `api.get("post?id=1")`.
57+
/// This function can be used to support those use cases.
58+
func appendURLString(_ string: String) -> Self {
59+
let urlString = Self.join("https://w.org", string)
60+
guard let url = URL(string: urlString),
61+
let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true)
62+
else {
63+
assertionFailure("Illegal URL string: \(string)")
64+
return self
65+
}
66+
67+
return append(percentEncodedPath: urlComponents.percentEncodedPath)
68+
.append(query: urlComponents.queryItems ?? [])
69+
}
70+
5471
func header(name: String, value: String?) -> Self {
5572
headers[name] = value
5673
return self

0 commit comments

Comments
 (0)