Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wrapper functions for endpoint calls #73

Merged
merged 1 commit into from
Jan 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
254 changes: 179 additions & 75 deletions bigbone/src/main/kotlin/social/bigbone/MastodonClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import social.bigbone.api.Pageable
import social.bigbone.api.exception.BigboneRequestException
import social.bigbone.extension.emptyRequestBody
import java.io.IOException
Expand All @@ -22,78 +23,119 @@ private constructor(
) {
private var debug = false

class Builder(
private val instanceName: String
) {

private val okHttpClientBuilder = OkHttpClient.Builder()
private val gson = Gson()
private var accessToken: String? = null
private var debug = false

fun accessToken(accessToken: String) = apply {
this.accessToken = accessToken
}
enum class Method {
DELETE,
GET,
POST,
PATCH
}

fun useStreamingApi() = apply {
okHttpClientBuilder.readTimeout(60, TimeUnit.SECONDS)
}
open fun getSerializer() = gson

fun debug() = apply {
this.debug = true
}
open fun getInstanceName() = instanceName

fun build(): MastodonClient {
return MastodonClient(
instanceName,
okHttpClientBuilder.addNetworkInterceptor(AuthorizationInterceptor(accessToken)).build(),
gson
).also {
it.debug = debug
}
}
/**
* Returns a MastodonRequest for the defined action, allowing to retrieve returned data.
* @param endpoint the Mastodon API endpoint to call
* @param method the HTTP method to use
* @param parameters parameters to use in the action; can be null
*/
internal inline fun <reified T : Any> getMastodonRequest(
endpoint: String,
method: Method,
parameters: Parameter? = null
): MastodonRequest<T> {
return MastodonRequest(
{
when (method) {
Method.DELETE -> delete(endpoint)
Method.GET -> get(endpoint, parameters)
Method.PATCH -> patch(endpoint, parameters)
Method.POST -> post(endpoint, parameters)
}
},
{ getSerializer().fromJson(it, T::class.java) }
)
}

private fun debugPrintUrl(url: HttpUrl) {
if (debug) {
println(url.toUrl().toString())
}
/**
* Returns a MastodonRequest for the defined action, allowing to retrieve returned data as a Pageable.
* @param endpoint the Mastodon API endpoint to call
* @param method the HTTP method to use
* @param parameters parameters to use in the action; can be null
*/
internal inline fun <reified T : Any> getPageableMastodonRequest(
endpoint: String,
method: Method,
parameters: Parameter? = null
): MastodonRequest<Pageable<T>> {
return MastodonRequest<Pageable<T>>(
{
when (method) {
Method.DELETE -> delete(endpoint)
Method.GET -> get(endpoint, parameters)
Method.PATCH -> patch(endpoint, parameters)
Method.POST -> post(endpoint, parameters)
}
},
{ getSerializer().fromJson(it, T::class.java) }
).toPageable()
}

private class AuthorizationInterceptor(val accessToken: String? = null) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val compressedRequest = originalRequest.newBuilder()
.headers(originalRequest.headers)
.method(originalRequest.method, originalRequest.body)
.apply {
accessToken?.let {
header("Authorization", String.format("Bearer %s", it))
}
/**
* Returns a MastodonRequest for the defined action, allowing to retrieve returned data as a List.
* @param endpoint the Mastodon API endpoint to call
* @param method the HTTP method to use
* @param parameters parameters to use in the action; can be null
*/
internal inline fun <reified T : Any> getMastodonRequestForList(
endpoint: String,
method: Method,
parameters: Parameter? = null
): MastodonRequest<List<T>> {
return MastodonRequest(
{
when (method) {
Method.DELETE -> delete(endpoint)
Method.GET -> get(endpoint, parameters)
Method.PATCH -> patch(endpoint, parameters)
Method.POST -> post(endpoint, parameters)
}
.build()
return chain.proceed(compressedRequest)
}
},
{ getSerializer().fromJson(it, T::class.java) }
)
}

open fun getSerializer() = gson

open fun getInstanceName() = instanceName
/**
* Performs the defined action and throws an exception if unsuccessful, without returning any data.
* @param endpoint the Mastodon API endpoint to call
* @param method the HTTP method to use
*/
@Throws(BigboneRequestException::class)
internal fun performAction(endpoint: String, method: Method) {
val response = when (method) {
Method.DELETE -> delete(endpoint)
Method.GET -> get(endpoint)
Method.PATCH -> patch(endpoint, null)
Method.POST -> post(endpoint)
}
if (!response.isSuccessful) {
throw BigboneRequestException(response)
}
}

/**
* Get a response from the Mastodon instance defined for this client using the GET method.
* Get a response from the Mastodon instance defined for this client using the DELETE method.
* @param path an absolute path to the API endpoint to call
* @param query the parameters to use as query string for this request; may be null
*/
open fun get(path: String, query: Parameter? = null): Response {
open fun delete(path: String): Response {
try {
val url = fullUrl(instanceName, path, query)
val url = fullUrl(instanceName, path)
debugPrintUrl(url)
val call = client.newCall(
Request.Builder()
.url(url)
.get()
.delete()
.build()
)
return call.execute()
Expand All @@ -103,34 +145,21 @@ private constructor(
}

/**
* Get a response from the Mastodon instance defined for this client using the POST method.
* @param path an absolute path to the API endpoint to call
* @param body the parameters to use in the request body for this request; may be null
*/
open fun post(path: String, body: Parameter? = null): Response =
postRequestBody(path, parameterBody(body))

/**
* Get a response from the Mastodon instance defined for this client using the POST method. Use this method if
* you need to build your own RequestBody; see post() otherwise.
* Get a response from the Mastodon instance defined for this client using the GET method.
* @param path an absolute path to the API endpoint to call
* @param body the RequestBody to use for this request
*
* @see post
* @param query the parameters to use as query string for this request; may be null
*/
open fun postRequestBody(path: String, body: RequestBody): Response {
open fun get(path: String, query: Parameter? = null): Response {
try {
val url = fullUrl(instanceName, path)
val url = fullUrl(instanceName, path, query)
debugPrintUrl(url)
val call = client.newCall(
Request.Builder()
.url(url)
.post(body)
.get()
.build()
)
return call.execute()
} catch (e: IllegalArgumentException) {
throw BigboneRequestException(e)
} catch (e: IOException) {
throw BigboneRequestException(e)
}
Expand All @@ -141,7 +170,11 @@ private constructor(
* @param path an absolute path to the API endpoint to call
* @param body the parameters to use in the request body for this request
*/
open fun patch(path: String, body: Parameter): Response {
open fun patch(path: String, body: Parameter?): Response {
if (body == null) {
throw BigboneRequestException(Exception("body must not be empty"))
}

try {
val url = fullUrl(instanceName, path)
debugPrintUrl(url)
Expand All @@ -158,25 +191,48 @@ private constructor(
}

/**
* Get a response from the Mastodon instance defined for this client using the DELETE method.
* Get a response from the Mastodon instance defined for this client using the POST method.
* @param path an absolute path to the API endpoint to call
* @param body the parameters to use in the request body for this request; may be null
*/
open fun delete(path: String): Response {
open fun post(path: String, body: Parameter? = null): Response =
postRequestBody(path, parameterBody(body))

/**
* Get a response from the Mastodon instance defined for this client using the POST method. Use this method if
* you need to build your own RequestBody; see post() otherwise.
* @param path an absolute path to the API endpoint to call
* @param body the RequestBody to use for this request
*
* @see post
*/
open fun postRequestBody(path: String, body: RequestBody): Response {
try {
val url = fullUrl(instanceName, path)
debugPrintUrl(url)
val call = client.newCall(
Request.Builder()
.url(url)
.delete()
.post(body)
.build()
)
return call.execute()
} catch (e: IllegalArgumentException) {
throw BigboneRequestException(e)
} catch (e: IOException) {
throw BigboneRequestException(e)
}
}

/**
* In debug mode, print out any accessed URL. Debug mode can be activated via MastodonClient.Builder.debug().
*/
private fun debugPrintUrl(url: HttpUrl) {
if (debug) {
println(url.toUrl().toString())
}
}

companion object {
/**
* Returns a HttpUrl
Expand Down Expand Up @@ -206,4 +262,52 @@ private constructor(
?: emptyRequestBody()
}
}

class Builder(
private val instanceName: String
) {
private val okHttpClientBuilder = OkHttpClient.Builder()
private val gson = Gson()
private var accessToken: String? = null
private var debug = false

fun accessToken(accessToken: String) = apply {
this.accessToken = accessToken
}

fun useStreamingApi() = apply {
okHttpClientBuilder.readTimeout(60, TimeUnit.SECONDS)
}

fun debug() = apply {
this.debug = true
}

fun build(): MastodonClient {
return MastodonClient(
instanceName,
okHttpClientBuilder.addNetworkInterceptor(AuthorizationInterceptor(accessToken)).build(),
gson
).also {
it.debug = debug
}
}
}

private class AuthorizationInterceptor(val accessToken: String? = null) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val compressedRequest = originalRequest.newBuilder()
.headers(originalRequest.headers)
.method(originalRequest.method, originalRequest.body)
.apply {
accessToken?.let {
header("Authorization", String.format("Bearer %s", it))
}
}
.build()
return chain.proceed(compressedRequest)
}
}
}
Loading