Skip to content

Commit a6af381

Browse files
authored
Adds Kotlin JetpackApiClient and WpComApiClient wrappers (#738)
* Adds Kotlin JetpackApiClient and WpComApiClient * Change default login url for Kotlin example app to wpmt.co * Address Detekt issues in api client files
1 parent a941953 commit a6af381

File tree

7 files changed

+152
-42
lines changed

7 files changed

+152
-42
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package rs.wordpress.api.kotlin
2+
3+
import uniffi.wp_api.WpApiException
4+
5+
fun <T> mapWpApiExceptionToWpRequestResult(apiException: WpApiException): WpRequestResult<T> =
6+
when (apiException) {
7+
is WpApiException.InvalidHttpStatusCode -> WpRequestResult.InvalidHttpStatusCode<T>(
8+
statusCode = apiException.statusCode,
9+
)
10+
11+
is WpApiException.RequestExecutionFailed -> WpRequestResult.RequestExecutionFailed<T>(
12+
statusCode = apiException.statusCode,
13+
redirects = apiException.redirects,
14+
reason = apiException.reason
15+
)
16+
17+
is WpApiException.MediaFileNotFound -> WpRequestResult.MediaFileNotFound<T>(
18+
filePath = apiException.filePath
19+
)
20+
21+
is WpApiException.ResponseParsingException -> WpRequestResult.ResponseParsingError<T>(
22+
reason = apiException.reason,
23+
response = apiException.response,
24+
)
25+
26+
is WpApiException.SiteUrlParsingException -> WpRequestResult.SiteUrlParsingError<T>(
27+
reason = apiException.reason,
28+
)
29+
30+
is WpApiException.UnknownException -> WpRequestResult.UnknownError<T>(
31+
statusCode = apiException.statusCode,
32+
response = apiException.response,
33+
)
34+
35+
is WpApiException.WpException -> WpRequestResult.WpError<T>(
36+
errorCode = apiException.errorCode,
37+
errorMessage = apiException.errorMessage,
38+
statusCode = apiException.statusCode,
39+
response = apiException.response,
40+
)
41+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package rs.wordpress.api.kotlin
2+
3+
import uniffi.wp_api.WpAppNotifier
4+
5+
class EmptyAppNotifier : WpAppNotifier {
6+
override suspend fun requestedWithInvalidAuthentication() {
7+
// no-op
8+
}
9+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package rs.wordpress.api.kotlin
2+
3+
import kotlinx.coroutines.CoroutineDispatcher
4+
import kotlinx.coroutines.Dispatchers
5+
import kotlinx.coroutines.withContext
6+
import uniffi.wp_api.ParsedUrl
7+
import uniffi.wp_api.RequestExecutor
8+
import uniffi.wp_api.UniffiJetpackApiClient
9+
import uniffi.wp_api.WpApiClientDelegate
10+
import uniffi.wp_api.WpApiException
11+
import uniffi.wp_api.WpApiMiddlewarePipeline
12+
import uniffi.wp_api.WpAppNotifier
13+
import uniffi.wp_api.WpAuthenticationProvider
14+
15+
class JetpackApiClient(
16+
apiRootUrl: ParsedUrl,
17+
authProvider: WpAuthenticationProvider,
18+
private val requestExecutor: RequestExecutor = WpRequestExecutor(),
19+
private val appNotifier: WpAppNotifier = EmptyAppNotifier(),
20+
private val dispatcher: CoroutineDispatcher = Dispatchers.IO
21+
) {
22+
// Don't expose `WpRequestBuilder` directly so we can control how it's used
23+
private val requestBuilder by lazy {
24+
UniffiJetpackApiClient(
25+
apiRootUrl,
26+
WpApiClientDelegate(
27+
authProvider,
28+
requestExecutor = requestExecutor,
29+
middlewarePipeline = WpApiMiddlewarePipeline(emptyList()),
30+
appNotifier
31+
)
32+
)
33+
}
34+
35+
// Provides the _only_ way to execute authenticated requests using our Kotlin wrapper.
36+
//
37+
// It makes sure that the errors are wrapped in `WpRequestResult` type instead of forcing
38+
// clients to try/catch the errors.
39+
//
40+
// It'll also help make sure any breaking changes to the API will end up as a compiler error.
41+
suspend fun <T> request(
42+
executeRequest: suspend (UniffiJetpackApiClient) -> T
43+
): WpRequestResult<T> = withContext(dispatcher) {
44+
try {
45+
WpRequestResult.WpRequestSuccess(data = executeRequest(requestBuilder))
46+
} catch (exception: WpApiException) {
47+
mapWpApiExceptionToWpRequestResult(exception)
48+
}
49+
}
50+
}

native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpApiClient.kt

Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ import uniffi.wp_api.WpApiMiddlewarePipeline
1212
import uniffi.wp_api.WpAppNotifier
1313
import uniffi.wp_api.WpAuthenticationProvider
1414

15-
class WpApiClient
16-
@Throws(WpApiException::class)
17-
constructor(
15+
class WpApiClient(
1816
apiRootUrl: ParsedUrl,
1917
authProvider: WpAuthenticationProvider,
2018
private val requestExecutor: RequestExecutor = WpRequestExecutor(),
@@ -46,42 +44,7 @@ constructor(
4644
try {
4745
WpRequestResult.WpRequestSuccess(data = executeRequest(requestBuilder))
4846
} catch (exception: WpApiException) {
49-
when (exception) {
50-
is WpApiException.InvalidHttpStatusCode -> WpRequestResult.InvalidHttpStatusCode(
51-
statusCode = exception.statusCode,
52-
)
53-
is WpApiException.RequestExecutionFailed -> WpRequestResult.RequestExecutionFailed(
54-
statusCode = exception.statusCode,
55-
redirects = exception.redirects,
56-
reason = exception.reason
57-
)
58-
is WpApiException.MediaFileNotFound -> WpRequestResult.MediaFileNotFound(
59-
filePath = exception.filePath
60-
)
61-
is WpApiException.ResponseParsingException -> WpRequestResult.ResponseParsingError(
62-
reason = exception.reason,
63-
response = exception.response,
64-
)
65-
is WpApiException.SiteUrlParsingException -> WpRequestResult.SiteUrlParsingError(
66-
reason = exception.reason,
67-
)
68-
is WpApiException.UnknownException -> WpRequestResult.UnknownError(
69-
statusCode = exception.statusCode,
70-
response = exception.response,
71-
)
72-
is WpApiException.WpException -> WpRequestResult.WpError(
73-
errorCode = exception.errorCode,
74-
errorMessage = exception.errorMessage,
75-
statusCode = exception.statusCode,
76-
response = exception.response,
77-
)
78-
}
47+
mapWpApiExceptionToWpRequestResult(exception)
7948
}
8049
}
8150
}
82-
83-
class EmptyAppNotifier : WpAppNotifier {
84-
override suspend fun requestedWithInvalidAuthentication() {
85-
// no-op
86-
}
87-
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package rs.wordpress.api.kotlin
2+
3+
import kotlinx.coroutines.CoroutineDispatcher
4+
import kotlinx.coroutines.Dispatchers
5+
import kotlinx.coroutines.withContext
6+
import uniffi.wp_api.RequestExecutor
7+
import uniffi.wp_api.UniffiWpComApiClient
8+
import uniffi.wp_api.WpApiClientDelegate
9+
import uniffi.wp_api.WpApiException
10+
import uniffi.wp_api.WpApiMiddlewarePipeline
11+
import uniffi.wp_api.WpAppNotifier
12+
import uniffi.wp_api.WpAuthenticationProvider
13+
14+
class WpComApiClient(
15+
authProvider: WpAuthenticationProvider,
16+
private val requestExecutor: RequestExecutor = WpRequestExecutor(),
17+
private val appNotifier: WpAppNotifier = EmptyAppNotifier(),
18+
private val dispatcher: CoroutineDispatcher = Dispatchers.IO
19+
) {
20+
// Don't expose `WpRequestBuilder` directly so we can control how it's used
21+
private val requestBuilder by lazy {
22+
UniffiWpComApiClient(
23+
WpApiClientDelegate(
24+
authProvider,
25+
requestExecutor = requestExecutor,
26+
middlewarePipeline = WpApiMiddlewarePipeline(emptyList()),
27+
appNotifier
28+
)
29+
)
30+
}
31+
32+
// Provides the _only_ way to execute authenticated requests using our Kotlin wrapper.
33+
//
34+
// It makes sure that the errors are wrapped in `WpRequestResult` type instead of forcing
35+
// clients to try/catch the errors.
36+
//
37+
// It'll also help make sure any breaking changes to the API will end up as a compiler error.
38+
suspend fun <T> request(
39+
executeRequest: suspend (UniffiWpComApiClient) -> T
40+
): WpRequestResult<T> = withContext(dispatcher) {
41+
try {
42+
WpRequestResult.WpRequestSuccess(data = executeRequest(requestBuilder))
43+
} catch (exception: WpApiException) {
44+
mapWpApiExceptionToWpRequestResult(exception)
45+
}
46+
}
47+
}

native/kotlin/example/composeApp/src/commonMain/kotlin/rs/wordpress/example/shared/ui/login/LoginScreen.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ fun LoginScreen(authenticateSite: (String) -> Unit) {
2424
verticalArrangement = Arrangement.Center,
2525
modifier = Modifier.fillMaxSize(),
2626
) {
27-
var siteUrl by remember { mutableStateOf("magnificent-funnel.jurassic.ninja") }
27+
var siteUrl by remember { mutableStateOf("vanilla.wpmt.co") }
2828
TextField(value = siteUrl, onValueChange = { siteUrl = it })
2929
Button(onClick = { authenticateSite(siteUrl) }) {
3030
Text("Login")

wp_api/src/jetpack/client.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ struct UniffiJetpackApiClient {
4040
#[uniffi::export]
4141
impl UniffiJetpackApiClient {
4242
#[uniffi::constructor]
43-
fn new(site_url: Arc<ParsedUrl>, delegate: WpApiClientDelegate) -> Self {
43+
fn new(api_root_url: Arc<ParsedUrl>, delegate: WpApiClientDelegate) -> Self {
4444
Self {
45-
inner: JetpackApiClient::new(site_url, delegate),
45+
inner: JetpackApiClient::new(api_root_url, delegate),
4646
}
4747
}
4848
}

0 commit comments

Comments
 (0)