Skip to content

Commit 7fd4b84

Browse files
authored
Use SiteInfo to create WpService (#1239)
* Use SiteInfo to create WpService * Update the Swift wrapper * Update the Kotlin wrapper * Add change logs
1 parent 41b3f4c commit 7fd4b84

18 files changed

Lines changed: 220 additions & 207 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2828
- **BREAKING:** Renamed `AnyJson` to `WpAdditionalFields`; the type is now constructible from foreign bindings and exposes typed value accessors
2929
- **BREAKING:** `PostMeta` is now a `uniffi::Object` wrapping the raw meta payload instead of a typed record with a single `footnotes` field; consumers must use the `footnotes()` / `with_footnotes()` accessors and arbitrary meta keys are reachable via `value_for_key` / `with_value`
3030
- **BREAKING:** `PostCreateParams::meta`, `PostUpdateParams::meta`, and `SparseAnyPost::meta` are now `Option<Arc<PostMeta>>` instead of `Option<PostMeta>`
31+
- **BREAKING:** [Replace `WpService.selfHosted` and `WpService.wordpressCom` with a single `WpService.new(siteInfo:)` constructor](https://github.com/Automattic/wordpress-rs/pull/1239). `SiteInfo.SelfHosted` now carries `ParsedUrl` values for `site_url` and `api_root` instead of `String`, and the `wordpress_com_site_api_root` helper has been removed — construct `SiteInfo.WordPressCom` directly.
32+
- **BREAKING:** [Swift `WordPressAPI` initializers now accept a `SiteInfo`](https://github.com/Automattic/wordpress-rs/pull/1239) instead of `apiRootUrl`/`apiUrlResolver`, and the `siteUrl` parameter is now a `ParsedUrl` rather than a `String`.
3133
- Reformat `CHANGELOG.md` to follow [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and enforce changelog updates on PRs via a Buildkite check that also surfaces the failure as a GitHub PR comment (using the shared `comment_on_pr` helper from `a8c-ci-toolkit`)
3234
- Release documentation
3335

native/kotlin/api/kotlin/src/integrationTest/kotlin/IntegrationTestHelpers.kt

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import uniffi.wp_api.WpApiClientDelegate
77
import uniffi.wp_api.WpApiMiddlewarePipeline
88
import uniffi.wp_api.WpAuthenticationProvider
99
import uniffi.wp_api.WpErrorCode
10+
import uniffi.wp_api.ParsedUrl
1011
import uniffi.wp_mobile.MockPostService
12+
import uniffi.wp_mobile.SiteInfo
1113
import uniffi.wp_mobile.WpService
1214
import rs.wordpress.cache.kotlin.WordPressApiCache
1315

@@ -51,10 +53,15 @@ fun createTestServiceContext(): TestServiceContext {
5153
// Extract site URL by removing /wp-json suffix
5254
val siteUrl = apiRootUrl.removeSuffix("/wp-json")
5355

56+
val parsedSiteUrl = ParsedUrl.parse(siteUrl)
57+
val parsedApiRoot = ParsedUrl.parse(apiRootUrl)
58+
5459
// Create self-hosted service
55-
val service = WpService.selfHosted(
56-
siteUrl = siteUrl,
57-
apiRoot = apiRootUrl,
60+
val service = WpService(
61+
siteInfo = SiteInfo.SelfHosted(
62+
siteUrl = parsedSiteUrl,
63+
apiRoot = parsedApiRoot
64+
),
5865
delegate = WpApiClientDelegate(
5966
authProvider,
6067
requestExecutor = WpRequestExecutor(emptyList(), NetworkAvailabilityProvider { true }),
@@ -64,11 +71,12 @@ fun createTestServiceContext(): TestServiceContext {
6471
cache = wordPressApiCache.cache
6572
)
6673

67-
// Create mock post service with shared cache
74+
// Create mock post service with shared cache.
75+
// Use the parsed URL strings to ensure URL normalization matches the WpService's DbSite.
6876
val mockPostService = MockPostService(
6977
wordPressApiCache.cache,
70-
siteUrl,
71-
apiRootUrl
78+
parsedSiteUrl.url(),
79+
parsedApiRoot.url()
7280
)
7381

7482
return TestServiceContext(service, mockPostService)

native/kotlin/example/composeApp/src/androidMain/kotlin/rs/wordpress/example/ui/welcome/WelcomeActivity.kt

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import uniffi.wp_api.parseAuthorizationUrl
3232
import uniffi.wp_api.wordpressComOauth2Configuration
3333
import uniffi.wp_mobile.Account
3434
import uniffi.wp_mobile.AccountRepository
35-
import uniffi.wp_mobile.wordpressComSiteApiRoot
3635

3736
class WelcomeActivity : ComponentActivity() {
3837
private val accountRepository: AccountRepository by inject()
@@ -212,16 +211,14 @@ class WelcomeActivity : ComponentActivity() {
212211
val tokenResponse = tokenResult.response.data
213212
val blogId = tokenResponse.blogId
214213
?: throw IllegalStateException("Expected blog_id in site-specific token response")
215-
val siteUrl = discoveredSiteHost
216-
?: tokenResponse.blogUrl
217-
?: "WordPress.com"
218214
accountRepository.store(
219-
Account.SelfHostedSite(
215+
Account.WpCom(
220216
id = 0uL,
221-
domain = siteUrl,
222-
username = siteUrl,
223-
password = tokenResponse.accessToken,
224-
siteApiRoot = wordpressComSiteApiRoot(blogId)
217+
username = discoveredSiteHost
218+
?: tokenResponse.blogUrl
219+
?: "WordPress.com",
220+
token = tokenResponse.accessToken,
221+
siteApiRoot = blogId.toString()
225222
)
226223
)
227224
siteSpecificOAuthState = null

native/kotlin/example/composeApp/src/commonMain/kotlin/rs/wordpress/example/shared/App.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,17 @@ fun App(authenticationEnabled: Boolean, authenticateSite: (String, onSuccess: ()
8585
navController.navigate("site")
8686
}
8787
is Account.WpCom -> {
88-
currentWpComClient = createWpComApiClient(account)
89-
navController.navigate("wpcom_site")
88+
if (account.siteApiRoot.isNotEmpty()) {
89+
// Site-specific WP.com account with a blog ID
90+
currentWpService = createWpService(account, cache)
91+
val apiClient = createWpApiClient(account)
92+
currentApiClient = apiClient
93+
currentSiteViewModel = SiteViewModel(apiClient)
94+
navController.navigate("site")
95+
} else {
96+
currentWpComClient = createWpComApiClient(account)
97+
navController.navigate("wpcom_site")
98+
}
9099
}
91100
}
92101
}

native/kotlin/example/composeApp/src/commonMain/kotlin/rs/wordpress/example/shared/di/AppModule.kt

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ import uniffi.wp_api.WpAuthenticationProvider
1616
import uniffi.wp_api.wpAuthenticationFromUsernameAndPassword
1717
import uniffi.wp_mobile.Account
1818
import uniffi.wp_mobile.AccountRepository
19+
import uniffi.wp_api.ParsedUrl
20+
import uniffi.wp_api.WpComBaseUrl
21+
import uniffi.wp_api.WpComDotOrgApiUrlResolver
22+
import uniffi.wp_mobile.SiteInfo
1923
import uniffi.wp_mobile.WpService
2024
import java.io.File
2125
import java.net.URI
@@ -47,9 +51,11 @@ fun createWpService(
4751
} else {
4852
wpAuthenticationFromUsernameAndPassword(account.username, account.password)
4953
}
50-
return WpService.selfHosted(
51-
siteUrl = account.domain,
52-
apiRoot = account.siteApiRoot,
54+
return WpService(
55+
siteInfo = SiteInfo.SelfHosted(
56+
siteUrl = ParsedUrl.parse(account.domain),
57+
apiRoot = ParsedUrl.parse(account.siteApiRoot)
58+
),
5359
delegate = WpApiClientDelegate(
5460
WpAuthenticationProvider.staticWithAuth(auth),
5561
requestExecutor = WpRequestExecutor(emptyList(), networkAvailabilityProvider),
@@ -60,6 +66,28 @@ fun createWpService(
6066
)
6167
}
6268

69+
fun createWpService(
70+
account: Account.WpCom,
71+
cache: WordPressApiCache,
72+
networkAvailabilityProvider: NetworkAvailabilityProvider = NetworkAvailabilityProvider { true }
73+
): WpService {
74+
val siteId = account.siteApiRoot.toULong()
75+
return WpService(
76+
siteInfo = SiteInfo.WordPressCom(
77+
siteId = siteId
78+
),
79+
delegate = WpApiClientDelegate(
80+
WpAuthenticationProvider.staticWithAuth(
81+
WpAuthentication.Bearer(token = account.token)
82+
),
83+
requestExecutor = WpRequestExecutor(emptyList(), networkAvailabilityProvider),
84+
middlewarePipeline = WpApiMiddlewarePipeline(listOf(DebugMiddleware())),
85+
appNotifier = EmptyAppNotifier()
86+
),
87+
cache = cache.cache
88+
)
89+
}
90+
6391
fun createWpApiClient(
6492
account: Account.SelfHostedSite,
6593
networkAvailabilityProvider: NetworkAvailabilityProvider = NetworkAvailabilityProvider { true }
@@ -77,6 +105,22 @@ fun createWpApiClient(
77105
)
78106
}
79107

108+
fun createWpApiClient(
109+
account: Account.WpCom,
110+
networkAvailabilityProvider: NetworkAvailabilityProvider = NetworkAvailabilityProvider { true }
111+
): WpApiClient {
112+
return WpApiClient(
113+
apiUrlResolver = WpComDotOrgApiUrlResolver(
114+
siteId = account.siteApiRoot,
115+
baseUrl = WpComBaseUrl.Production
116+
),
117+
authProvider = WpAuthenticationProvider.staticWithAuth(
118+
WpAuthentication.Bearer(token = account.token)
119+
),
120+
requestExecutor = WpRequestExecutor(emptyList(), networkAvailabilityProvider)
121+
)
122+
}
123+
80124
fun createWpComApiClient(
81125
account: Account.WpCom,
82126
networkAvailabilityProvider: NetworkAvailabilityProvider = NetworkAvailabilityProvider { true }

native/swift/Example/Example/WordPressAPI+Extensions.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,13 @@ extension WordPressAPI {
1515
throw CocoaError(.xpcConnectionInvalid)
1616
}
1717

18+
let siteUrl = try ParsedUrl.parse(input: apiRootUrl.asURL().deletingLastPathComponent().absoluteString)
1819
return WordPressAPI(
1920
urlSession: .shared,
20-
apiRootUrl: apiRootUrl,
21+
siteInfo: .selfHosted(
22+
siteUrl: siteUrl,
23+
apiRoot: apiRootUrl
24+
),
2125
authentication: loginCredentials,
2226
middlewarePipeline: MiddlewarePipeline(middlewares: [
2327
DebugMiddleware()

native/swift/Sources/wordpress-api/Exports.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public typealias UserRole = WordPressAPIInternal.UserRole
3333

3434
// MARK: - Service Layer
3535
public typealias WpService = WordPressAPIInternal.WpService
36+
public typealias SiteInfo = WordPressAPIInternal.SiteInfo
3637
public typealias AnyPostFilter = WordPressAPIInternal.AnyPostFilter
3738
public typealias WpApiCache = WordPressAPIInternal.WpApiCache
3839

native/swift/Sources/wordpress-api/WordPressAPI.swift

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public final class WordPressAPI: Sendable {
1616
case unableToParseResponse
1717
}
1818

19-
private let apiUrlResolver: ApiUrlResolver
19+
private let siteInfo: SiteInfo
2020
private let urlSession: URLSession
2121

2222
let requestExecutor: SafeRequestExecutor
@@ -26,13 +26,13 @@ public final class WordPressAPI: Sendable {
2626
public convenience init(
2727
urlSession: URLSession,
2828
notifyingDelegate: URLSessionTaskDelegate? = nil,
29-
apiRootUrl: ParsedUrl,
29+
siteInfo: SiteInfo,
3030
authentication: WpAuthentication,
3131
middlewarePipeline: MiddlewarePipeline = .default,
3232
appNotifier: WpAppNotifier? = nil
3333
) {
3434
self.init(
35-
apiUrlResolver: WpOrgSiteApiUrlResolver(apiRootUrl: apiRootUrl),
35+
siteInfo: siteInfo,
3636
authenticationProvider: .staticWithAuth(auth: authentication),
3737
executor: WpRequestExecutor(urlSession: urlSession, notifyingDelegate: notifyingDelegate),
3838
middlewarePipeline: middlewarePipeline,
@@ -42,13 +42,13 @@ public final class WordPressAPI: Sendable {
4242

4343
public convenience init(
4444
urlSession: URLSession,
45-
apiRootUrl: ParsedUrl,
45+
siteInfo: SiteInfo,
4646
authenticationProvider: WpAuthenticationProvider,
4747
middlewarePipeline: MiddlewarePipeline = .default,
4848
appNotifier: WpAppNotifier? = nil
4949
) {
5050
self.init(
51-
apiUrlResolver: WpOrgSiteApiUrlResolver(apiRootUrl: apiRootUrl),
51+
siteInfo: siteInfo,
5252
authenticationProvider: authenticationProvider,
5353
executor: WpRequestExecutor(urlSession: urlSession),
5454
middlewarePipeline: middlewarePipeline,
@@ -59,13 +59,13 @@ public final class WordPressAPI: Sendable {
5959
public convenience init(
6060
urlSession: URLSession,
6161
notifyingDelegate: URLSessionTaskDelegate? = nil,
62-
apiUrlResolver: ApiUrlResolver,
62+
siteInfo: SiteInfo,
6363
authenticationProvider: WpAuthenticationProvider,
6464
middlewarePipeline: MiddlewarePipeline = .default,
6565
appNotifier: WpAppNotifier? = nil
6666
) {
6767
self.init(
68-
apiUrlResolver: apiUrlResolver,
68+
siteInfo: siteInfo,
6969
authenticationProvider: authenticationProvider,
7070
executor: WpRequestExecutor(urlSession: urlSession, notifyingDelegate: notifyingDelegate),
7171
middlewarePipeline: middlewarePipeline,
@@ -76,22 +76,23 @@ public final class WordPressAPI: Sendable {
7676
public convenience init(
7777
urlSession: URLSession,
7878
notifyingDelegate: URLSessionTaskDelegate? = nil,
79-
siteUrl: String,
79+
siteUrl: ParsedUrl,
8080
apiRootUrl: ParsedUrl,
8181
username: String,
8282
password: String,
8383
middlewarePipeline: MiddlewarePipeline = .default,
8484
appNotifier: WpAppNotifier? = nil
8585
) {
86+
let siteInfo = SiteInfo.selfHosted(siteUrl: siteUrl, apiRoot: apiRootUrl)
8687
let executor = WpRequestExecutor(urlSession: urlSession, notifyingDelegate: notifyingDelegate)
8788
let provider = CookiesNonceAuthenticationProvider.withSiteUrl(
88-
url: siteUrl,
89+
url: siteUrl.url(),
8990
username: username,
9091
password: password,
9192
requestExecutor: executor
9293
)
9394
self.init(
94-
apiUrlResolver: WpOrgSiteApiUrlResolver(apiRootUrl: apiRootUrl),
95+
siteInfo: siteInfo,
9596
authenticationProvider: .dynamic(dynamicAuthenticationProvider: provider),
9697
executor: executor,
9798
middlewarePipeline: middlewarePipeline,
@@ -108,6 +109,10 @@ public final class WordPressAPI: Sendable {
108109
middlewarePipeline: MiddlewarePipeline = .default,
109110
appNotifier: WpAppNotifier? = nil
110111
) {
112+
let siteInfo = SiteInfo.selfHosted(
113+
siteUrl: details.parsedSiteUrl,
114+
apiRoot: details.apiRootUrl
115+
)
111116
let executor = WpRequestExecutor(urlSession: urlSession, notifyingDelegate: notifyingDelegate)
112117
let provider = CookiesNonceAuthenticationProvider(
113118
username: username,
@@ -116,7 +121,7 @@ public final class WordPressAPI: Sendable {
116121
requestExecutor: executor
117122
)
118123
self.init(
119-
apiUrlResolver: WpOrgSiteApiUrlResolver(apiRootUrl: details.apiRootUrl),
124+
siteInfo: siteInfo,
120125
authenticationProvider: .dynamic(dynamicAuthenticationProvider: provider),
121126
executor: executor,
122127
middlewarePipeline: middlewarePipeline,
@@ -126,39 +131,29 @@ public final class WordPressAPI: Sendable {
126131

127132
init(
128133
urlSession: URLSession = .shared,
129-
apiUrlResolver: ApiUrlResolver,
134+
siteInfo: SiteInfo,
130135
authenticationProvider: WpAuthenticationProvider,
131136
executor: SafeRequestExecutor,
132137
middlewarePipeline: MiddlewarePipeline,
133138
appNotifier: WpAppNotifier?
134139
) {
135140
self.urlSession = urlSession
136-
self.apiUrlResolver = apiUrlResolver
141+
self.siteInfo = siteInfo
137142
self.apiClientDelegate = WpApiClientDelegate(
138143
authProvider: authenticationProvider,
139144
requestExecutor: executor,
140145
middlewarePipeline: middlewarePipeline,
141146
appNotifier: appNotifier ?? EmptyAppNotifier()
142147
)
143148
self.requestBuilder = UniffiWpApiClient(
144-
apiUrlResolver: self.apiUrlResolver,
149+
apiUrlResolver: siteInfo.apiUrlResolver(),
145150
delegate: self.apiClientDelegate
146151
)
147152
self.requestExecutor = executor
148153
}
149154

150-
public func createSelfHostedService(cache: WordPressApiCache) throws -> WpService {
151-
let apiURL = apiUrlResolver.resolve(namespace: "", endpointSegments: []).asURL()
152-
return try WpService.selfHosted(
153-
siteUrl: apiURL.deletingLastPathComponent().absoluteString,
154-
apiRoot: apiURL.absoluteString,
155-
delegate: apiClientDelegate,
156-
cache: cache.cache
157-
)
158-
}
159-
160-
public func createWordPressComService(siteId: WpComSiteId, cache: WordPressApiCache) throws -> WpService {
161-
try WpService.wordpressCom(siteId: siteId, delegate: apiClientDelegate, cache: cache.cache)
155+
public func createService(cache: WordPressApiCache) throws -> WpService {
156+
try WpService(siteInfo: siteInfo, delegate: apiClientDelegate, cache: cache.cache)
162157
}
163158

164159
public var users: UsersRequestExecutor {

native/swift/Sources/wordpress-api/WordPressLoginClient.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,10 @@ public final class WordPressLoginClient: @unchecked Sendable {
7272
let nonceRetrieval = WpRestNonceRetrieval(details: details, requestExecutor: requestExecutor)
7373
let nonce = try await nonceRetrieval.getNonce(username: username, password: password)
7474
return WordPressAPI(
75-
apiUrlResolver: WpOrgSiteApiUrlResolver(apiRootUrl: details.apiRootUrl),
75+
siteInfo: .selfHosted(
76+
siteUrl: details.parsedSiteUrl,
77+
apiRoot: details.apiRootUrl
78+
),
7679
authenticationProvider: .staticWithAuth(auth: .nonce(nonce: nonce)),
7780
executor: requestExecutor,
7881
middlewarePipeline: middleware,

native/swift/Tests/integration-tests/Helpers.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ func restoreTestServer() async throws {
1111
}
1212

1313
extension TestCredentials {
14+
var siteURL: ParsedUrl {
15+
// swiftlint:disable:next force_try
16+
try! ParsedUrl.parse(input: siteUrl)
17+
}
18+
1419
var apiRootURL: ParsedUrl {
1520
// swiftlint:disable:next force_try
1621
try! ParsedUrl.parse(input: siteUrl + "/wp-json")
@@ -27,7 +32,10 @@ extension WordPressAPI {
2732
return WordPressAPI(
2833
urlSession: .init(configuration: .ephemeral),
2934
notifyingDelegate: notifyingDelegate,
30-
apiRootUrl: credentials.apiRootURL,
35+
siteInfo: .selfHosted(
36+
siteUrl: credentials.siteURL,
37+
apiRoot: credentials.apiRootURL
38+
),
3139
authentication: credentials.adminAuthentication
3240
)
3341
}

0 commit comments

Comments
 (0)