diff --git a/detekt-baseline.xml b/detekt-baseline.xml
index 05bc531..39dc622 100644
--- a/detekt-baseline.xml
+++ b/detekt-baseline.xml
@@ -2,12 +2,12 @@
- ComplexMethod:Bitly.kt$Bitly$executeCall
- FunctionParameterNaming:Bitly.kt$Bitly$bitlink_id: String
- FunctionParameterNaming:Bitly.kt$Bitly$group_guid: String = ""
- FunctionParameterNaming:Bitly.kt$Bitly$long_url: String
- NestedBlockDepth:Bitly.kt$Bitly$executeCall
- NestedBlockDepth:Bitly.kt$Bitly$logApiError
- ReturnCount:Bitly.kt$Bitly$private fun parseJsonResponse(response: String, key: String, default: String, isJson: Boolean): String
+ ComplexMethod:Utils.kt$Utils.Companion$call
+ FunctionParameterNaming:Bitlinks.kt$Bitlinks$bitlink_id: String
+ FunctionParameterNaming:Bitlinks.kt$Bitlinks$group_guid: String = ""
+ FunctionParameterNaming:Bitlinks.kt$Bitlinks$long_url: String
+ NestedBlockDepth:Utils.kt$Utils.Companion$call
+ NestedBlockDepth:Utils.kt$Utils.Companion$logApiError
+ ReturnCount:Bitlinks.kt$Bitlinks$private fun parseJsonResponse(response: String, key: String, default: String, isJson: Boolean): String
diff --git a/examples/src/main/java/com/example/BitlySample.java b/examples/src/main/java/com/example/BitlySample.java
index d693dbe..6c593b7 100644
--- a/examples/src/main/java/com/example/BitlySample.java
+++ b/examples/src/main/java/com/example/BitlySample.java
@@ -4,7 +4,7 @@ import net.thauvin.erik.bitly.Bitly;
public class BitlySample {
public static void main(String[] args) {
- System.out.println(new Bitly("YOUR_API_KEY").shorten("https://erik.thauvin.net/blog"));
+ System.out.println(new Bitly("YOUR_API_KEY").bitlinks().shorten("https://erik.thauvin.net/blog"));
System.exit(0);
}
}
diff --git a/examples/src/main/kotlin/com/example/BitlyExample.kt b/examples/src/main/kotlin/com/example/BitlyExample.kt
index 5574a73..12c2e36 100644
--- a/examples/src/main/kotlin/com/example/BitlyExample.kt
+++ b/examples/src/main/kotlin/com/example/BitlyExample.kt
@@ -4,6 +4,6 @@ import net.thauvin.erik.bitly.Bitly
import kotlin.system.exitProcess
fun main() {
- println(Bitly("YOUR_API_KEY").shorten("https://erik.thauvin.net/blog"))
+ println(Bitly("YOUR_API_KEY").bitlinks().shorten("https://erik.thauvin.net/blog"))
exitProcess(0)
}
diff --git a/src/main/kotlin/net/thauvin/erik/bitly/Bitlinks.kt b/src/main/kotlin/net/thauvin/erik/bitly/Bitlinks.kt
new file mode 100644
index 0000000..cad0407
--- /dev/null
+++ b/src/main/kotlin/net/thauvin/erik/bitly/Bitlinks.kt
@@ -0,0 +1,125 @@
+/*
+ * Bitlinks.kt
+ *
+ * Copyright (c) 2020, Erik C. Thauvin (erik@thauvin.net)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of this project nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package net.thauvin.erik.bitly
+
+import org.json.JSONException
+import org.json.JSONObject
+import java.util.logging.Level
+
+/**
+ * Bitlinks methods implementation.
+ *
+ * See the [Bitly API](https://dev.bitly.com/v4/#tag/Bitlinks) for more information.
+ */
+class Bitlinks(private val accessToken: String) {
+ /**
+ * Expands a Bitlink.
+ *
+ * See the [Bit.ly API](https://dev.bitly.com/v4/#operation/expandBitlink) for more information.
+ *
+ * @param bitlink_id The bitlink ID.
+ * @param isJson Returns the full JSON API response if `true`
+ * @return THe long URL or JSON API response.
+ */
+ @JvmOverloads
+ fun expand(bitlink_id: String, isJson: Boolean = false): String {
+ var longUrl = if (isJson) "{}" else ""
+ if (bitlink_id.isNotBlank()) {
+ val response = Utils.call(
+ accessToken,
+ Utils.buildEndPointUrl("/expand"),
+ mapOf(Pair("bitlink_id", bitlink_id.removePrefix("http://").removePrefix("https://"))),
+ Methods.POST
+ )
+ longUrl = parseJsonResponse(response, "long_url", longUrl, isJson)
+ }
+
+ return longUrl
+ }
+
+ private fun JSONObject.getString(key: String, default: String): String {
+ return if (this.has(key))
+ this.getString(key)
+ else
+ default
+ }
+
+ /**
+ * Shortens a long URL.
+ *
+ * See the [Bit.ly API](https://dev.bitly.com/v4/#operation/createBitlink) for more information.
+ *
+ * @param long_url The long URL.
+ * @param group_guid The group UID.
+ * @param domain The domain for the short URL, defaults to `bit.ly`.
+ * @param isJson Returns the full JSON API response if `true`
+ * @return THe short URL or JSON API response.
+ */
+ @JvmOverloads
+ fun shorten(long_url: String, group_guid: String = "", domain: String = "", isJson: Boolean = false): String {
+ var bitlink = if (isJson) "{}" else ""
+ if (!Utils.validateUrl(long_url)) {
+ Utils.logger.severe("Please specify a valid URL to shorten.")
+ } else {
+ val params: HashMap = HashMap()
+ if (group_guid.isNotBlank()) {
+ params["group_guid"] = group_guid
+ }
+ if (domain.isNotBlank()) {
+ params["domain"] = domain
+ }
+ params["long_url"] = long_url
+
+ val response = Utils.call(accessToken, Utils.buildEndPointUrl("/shorten"), params)
+
+ bitlink = parseJsonResponse(response, "link", bitlink, isJson)
+ }
+
+ return bitlink
+ }
+
+ private fun parseJsonResponse(response: String, key: String, default: String, isJson: Boolean): String {
+ if (response.isNotEmpty()) {
+ if (isJson) {
+ return response
+ } else {
+ try {
+ return JSONObject(response).getString(key, default)
+ } catch (jse: JSONException) {
+ Utils.logger.log(Level.SEVERE, "An error occurred parsing the response from bitly.", jse)
+ }
+ }
+ }
+ return default
+ }
+}
diff --git a/src/main/kotlin/net/thauvin/erik/bitly/Bitly.kt b/src/main/kotlin/net/thauvin/erik/bitly/Bitly.kt
index b094b2d..d459bb2 100644
--- a/src/main/kotlin/net/thauvin/erik/bitly/Bitly.kt
+++ b/src/main/kotlin/net/thauvin/erik/bitly/Bitly.kt
@@ -32,25 +32,13 @@
package net.thauvin.erik.bitly
-import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
-import okhttp3.MediaType.Companion.toMediaTypeOrNull
-import okhttp3.OkHttpClient
-import okhttp3.Request
-import okhttp3.RequestBody.Companion.toRequestBody
-import okhttp3.logging.HttpLoggingInterceptor
-import org.json.JSONException
-import org.json.JSONObject
import java.io.File
-import java.net.MalformedURLException
-import java.net.URL
import java.nio.file.Files
import java.nio.file.Path
import java.util.Properties
-import java.util.logging.Level
-import java.util.logging.Logger
/**
- * The HTTP methods.
+ * HTTP methods.
*/
enum class Methods {
DELETE, GET, PATCH, POST
@@ -62,35 +50,9 @@ enum class Methods {
* @constructor Creates new instance.
*/
open class Bitly() {
- /** Constants for this package. **/
- object Constants {
- /** The Bitly API base URL. **/
- const val API_BASE_URL = "https://api-ssl.bitly.com/v4"
-
- /** The API access token environment variable. **/
- const val ENV_ACCESS_TOKEN = "BITLY_ACCESS_TOKEN"
- }
-
/** The API access token. **/
var accessToken: String = System.getenv(Constants.ENV_ACCESS_TOKEN) ?: ""
- /** The logger instance. **/
- val logger: Logger by lazy { Logger.getLogger(Bitly::class.java.simpleName) }
-
- private var client: OkHttpClient
-
- init {
- client = if (logger.isLoggable(Level.FINE)) {
- val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
- level = HttpLoggingInterceptor.Level.BODY
- redactHeader("Authorization")
- }
- OkHttpClient.Builder().addInterceptor(httpLoggingInterceptor).build()
- } else {
- OkHttpClient.Builder().build()
- }
- }
-
/**
* Creates a new instance using an [API Access Token][accessToken].
*
@@ -105,7 +67,7 @@ open class Bitly() {
* Creates a new instance using a [Properties][properties] and [Property Key][key].
*
* @param properties The properties.
- * @param key The property key.
+ * @param key The property key containing the [API Access Token][accessToken].
*/
@Suppress("unused")
@JvmOverloads
@@ -117,7 +79,7 @@ open class Bitly() {
* Creates a new instance using a [Properties File Path][propertiesFilePath] and [Property Key][key].
*
* @param propertiesFilePath The properties file path.
- * @param key The property key.
+ * @param key The property key containing the [API Access Token][accessToken].
*/
@JvmOverloads
constructor(propertiesFilePath: Path, key: String = Constants.ENV_ACCESS_TOKEN) : this() {
@@ -134,194 +96,24 @@ open class Bitly() {
* Creates a new instance using a [Properties File][propertiesFile] and [Property Key][key].
*
* @param propertiesFile The properties file.
- * @param key The property key.
+ * @param key The property key containing the [API Access Token][accessToken].
*/
@Suppress("unused")
@JvmOverloads
constructor(propertiesFile: File, key: String = Constants.ENV_ACCESS_TOKEN) : this(propertiesFile.toPath(), key)
- /**
- * Builds the full API endpoint URL using the [Constants.API_BASE_URL].
- *
- * @param endPointPath The REST method path. (eg. `/shorten', '/user`)
- */
- fun buildEndPointUrl(endPointPath: String): String {
- return if (endPointPath.startsWith('/')) {
- "${Constants.API_BASE_URL}$endPointPath"
- } else {
- "${Constants.API_BASE_URL}/$endPointPath"
- }
- }
+ /** Bitlinks accessor. **/
+ fun bitlinks(): Bitlinks = Bitlinks(accessToken)
/**
* Executes an API call.
*
- * @param endPoint The API endpoint. (eg. `/shorten`, `/user`)
+ * @param endPoint The REST endpoint. (eg. `https://api-ssl.bitly.com/v4/shorten`)
* @param params The request parameters kev/value map.
* @param method The submission [Method][Methods].
* @return The response (JSON) from the API.
*/
fun call(endPoint: String, params: Map, method: Methods = Methods.POST): String {
- var response = ""
- if (endPoint.isBlank()) {
- logger.severe("Please specify a valid API endpoint.")
- } else if (accessToken.isBlank()) {
- logger.severe("Please specify a valid API access token.")
- } else {
- val apiUrl = endPoint.toHttpUrlOrNull()
- if (apiUrl != null) {
- val builder = when (method) {
- Methods.POST, Methods.PATCH -> {
- val formBody = JSONObject(params).toString()
- .toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
- Request.Builder().apply {
- url(apiUrl.newBuilder().build())
- if (method == Methods.POST) {
- post(formBody)
- } else {
- patch(formBody)
- }
- }
- }
- Methods.DELETE -> Request.Builder().url(apiUrl.newBuilder().build()).delete()
- else -> {
- val httpUrl = apiUrl.newBuilder().apply {
- params.forEach {
- addQueryParameter(it.key, it.value)
- }
- }.build()
- Request.Builder().url(httpUrl)
- }
- }.addHeader("Authorization", "Bearer $accessToken")
-
- val result = client.newCall(builder.build()).execute()
-
- val body = result.body?.string()
- if (body != null) {
- if (!result.isSuccessful && body.isNotEmpty()) {
- logApiError(body, result.code)
- }
- response = body
- }
- }
- }
-
- return response
- }
-
- /**
- * Expands a Bitlink.
- *
- * See the [Bit.ly API](https://dev.bitly.com/v4/#operation/expandBitlink) for more information.
- *
- * @param bitlink_id The bitlink ID.
- * @param isJson Returns the full JSON API response if `true`
- * @return THe long URL or JSON API response.
- */
- @JvmOverloads
- fun expand(bitlink_id: String, isJson: Boolean = false): String {
- var longUrl = if (isJson) "{}" else ""
- if (bitlink_id.isNotBlank()) {
- val response = call(
- buildEndPointUrl("/expand"),
- mapOf(
- Pair(
- "bitlink_id",
- bitlink_id.removePrefix("http://").removePrefix("https://")
- )
- ),
- Methods.POST
- )
- longUrl = parseJsonResponse(response, "long_url", longUrl, isJson)
- }
-
- return longUrl
- }
-
- private fun JSONObject.getString(key: String, default: String): String {
- return if (this.has(key))
- this.getString(key)
- else
- default
- }
-
- /**
- * Shortens a long URL.
- *
- * See the [Bit.ly API](https://dev.bitly.com/v4/#operation/createBitlink) for more information.
- *
- * @param long_url The long URL.
- * @param group_guid The group UID.
- * @param domain The domain for the short URL, defaults to `bit.ly`.
- * @param isJson Returns the full JSON API response if `true`
- * @return THe short URL or JSON API response.
- */
- @JvmOverloads
- fun shorten(long_url: String, group_guid: String = "", domain: String = "", isJson: Boolean = false): String {
- var bitlink = if (isJson) "{}" else ""
- if (!validateUrl(long_url)) {
- logger.severe("Please specify a valid URL to shorten.")
- } else {
- val params: HashMap = HashMap()
- if (group_guid.isNotBlank()) {
- params["group_guid"] = group_guid
- }
- if (domain.isNotBlank()) {
- params["domain"] = domain
- }
- params["long_url"] = long_url
-
- val response = call(buildEndPointUrl("/shorten"), params)
-
- bitlink = parseJsonResponse(response, "link", bitlink, isJson)
- }
-
- return bitlink
- }
-
- private fun logApiError(body: String, resultCode: Int) {
- try {
- with(JSONObject(body)) {
- if (has("message")) {
- logger.severe(getString("message") + " ($resultCode)")
- }
- if (has("description")) {
- val description = getString("description")
- if (description.isNotBlank()) {
- logger.severe(description)
- }
- }
- }
- } catch (ignore: JSONException) {
- logger.severe("An error occurred parsing the error response from bitly.")
- }
- }
-
- private fun parseJsonResponse(response: String, key: String, default: String, isJson: Boolean): String {
- if (response.isNotEmpty()) {
- if (isJson) {
- return response
- } else {
- try {
- return JSONObject(response).getString(key, default)
- } catch (ignore: JSONException) {
- logger.severe("An error occurred parsing the response from bitly.")
- }
- }
- }
- return default
- }
-
- private fun validateUrl(url: String): Boolean {
- var isValid = url.isNotBlank()
- if (isValid) {
- try {
- URL(url)
- } catch (e: MalformedURLException) {
- logger.log(Level.FINE, "Invalid URL: $url", e)
- isValid = false
- }
- }
- return isValid
+ return Utils.call(accessToken, endPoint, params, method)
}
}
diff --git a/src/main/kotlin/net/thauvin/erik/bitly/Constants.kt b/src/main/kotlin/net/thauvin/erik/bitly/Constants.kt
new file mode 100644
index 0000000..78b3f81
--- /dev/null
+++ b/src/main/kotlin/net/thauvin/erik/bitly/Constants.kt
@@ -0,0 +1,44 @@
+/*
+ * Constants.kt
+ *
+ * Copyright (c) 2020, Erik C. Thauvin (erik@thauvin.net)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of this project nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package net.thauvin.erik.bitly
+
+/** Constants for this package. **/
+class Constants private constructor() {
+ companion object Constants {
+ /** The Bitly API base URL. **/
+ const val API_BASE_URL = "https://api-ssl.bitly.com/v4"
+
+ /** The API access token environment variable. **/
+ const val ENV_ACCESS_TOKEN = "BITLY_ACCESS_TOKEN"
+ }
+}
diff --git a/src/main/kotlin/net/thauvin/erik/bitly/Utils.kt b/src/main/kotlin/net/thauvin/erik/bitly/Utils.kt
new file mode 100644
index 0000000..914df17
--- /dev/null
+++ b/src/main/kotlin/net/thauvin/erik/bitly/Utils.kt
@@ -0,0 +1,172 @@
+/*
+ * Utils.kt
+ *
+ * Copyright (c) 2020, Erik C. Thauvin (erik@thauvin.net)
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of this project nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package net.thauvin.erik.bitly
+
+import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.RequestBody.Companion.toRequestBody
+import okhttp3.logging.HttpLoggingInterceptor
+import org.json.JSONException
+import org.json.JSONObject
+import java.net.MalformedURLException
+import java.net.URL
+import java.util.logging.Level
+import java.util.logging.Logger
+
+/** Useful functions. */
+class Utils private constructor() {
+ companion object {
+ /** The logger instance. **/
+ val logger: Logger by lazy { Logger.getLogger(Bitly::class.java.simpleName) }
+
+ private val client: OkHttpClient = if (logger.isLoggable(Level.FINE)) {
+ val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
+ level = HttpLoggingInterceptor.Level.BODY
+ redactHeader("Authorization")
+ }
+ OkHttpClient.Builder().addInterceptor(httpLoggingInterceptor).build()
+ } else {
+ OkHttpClient.Builder().build()
+ }
+
+ /**
+ * Builds the full API endpoint URL using the [Constants.API_BASE_URL].
+ *
+ * @param endPointPath The REST request path. (eg. `/shorten', '/user`)
+ */
+ fun buildEndPointUrl(endPointPath: String): String {
+ return if (endPointPath.startsWith('/')) {
+ "${Constants.API_BASE_URL}$endPointPath"
+ } else {
+ "${Constants.API_BASE_URL}/$endPointPath"
+ }
+ }
+
+ /**
+ * Executes an API call.
+ *
+ * @param accessToken The API access token.
+ * @param endPoint The REST endpoint. (eg. `https://api-ssl.bitly.com/v4/shorten`)
+ * @param params The request parameters kev/value map.
+ * @param method The submission [Method][Methods].
+ * @return The response (JSON) from the API.
+ */
+ fun call(
+ accessToken: String,
+ endPoint: String,
+ params: Map,
+ method: Methods = Methods.POST
+ ): String {
+ var response = ""
+ if (endPoint.isBlank()) {
+ logger.severe("Please specify a valid API endpoint.")
+ } else if (accessToken.isBlank()) {
+ logger.severe("Please specify a valid API access token.")
+ } else {
+ val apiUrl = endPoint.toHttpUrlOrNull()
+ if (apiUrl != null) {
+ val builder = when (method) {
+ Methods.POST, Methods.PATCH -> {
+ val formBody = JSONObject(params).toString()
+ .toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
+ Request.Builder().apply {
+ url(apiUrl.newBuilder().build())
+ if (method == Methods.POST) {
+ post(formBody)
+ } else {
+ patch(formBody)
+ }
+ }
+ }
+ Methods.DELETE -> Request.Builder().url(apiUrl.newBuilder().build()).delete()
+ else -> {
+ val httpUrl = apiUrl.newBuilder().apply {
+ params.forEach {
+ addQueryParameter(it.key, it.value)
+ }
+ }.build()
+ Request.Builder().url(httpUrl)
+ }
+ }.addHeader("Authorization", "Bearer $accessToken")
+
+ val result = client.newCall(builder.build()).execute()
+
+ val body = result.body?.string()
+ if (body != null) {
+ if (!result.isSuccessful && body.isNotEmpty()) {
+ logApiError(body, result.code)
+ }
+ response = body
+ }
+ }
+ }
+
+ return response
+ }
+
+ private fun logApiError(body: String, resultCode: Int) {
+ try {
+ with(JSONObject(body)) {
+ if (has("message")) {
+ logger.severe(getString("message") + " ($resultCode)")
+ }
+ if (has("description")) {
+ val description = getString("description")
+ if (description.isNotBlank()) {
+ logger.severe(description)
+ }
+ }
+ }
+ } catch (jse: JSONException) {
+ logger.log(Level.SEVERE, "An error occurred parsing the error response from bitly.", jse)
+ }
+ }
+
+ /**
+ * Validates a URL.
+ */
+ fun validateUrl(url: String): Boolean {
+ if (url.isNotBlank()) {
+ try {
+ URL(url)
+ return true
+ } catch (e: MalformedURLException) {
+ logger.log(Level.FINE, "Invalid URL: $url", e)
+ }
+ }
+ return false
+ }
+ }
+}
diff --git a/src/test/kotlin/net/thauvin/erik/bitly/BitlyTest.kt b/src/test/kotlin/net/thauvin/erik/bitly/BitlyTest.kt
index 075686a..4ce2f02 100644
--- a/src/test/kotlin/net/thauvin/erik/bitly/BitlyTest.kt
+++ b/src/test/kotlin/net/thauvin/erik/bitly/BitlyTest.kt
@@ -51,7 +51,7 @@ class BitlyTest {
@Before
fun before() {
- with(bitly.logger) {
+ with(Utils.logger) {
level = Level.FINE
}
}
@@ -62,33 +62,33 @@ class BitlyTest {
if (System.getenv("CI") == "true") {
test.accessToken = ""
}
- assertEquals("", test.shorten(blog))
+ assertEquals("", test.bitlinks().shorten(blog))
}
@Test
fun `token should be valid`() {
val test = Bitly().apply { accessToken = "12345679" }
- assertEquals("{\"message\":\"FORBIDDEN\"}", test.shorten("https://erik.thauvin.net/blog", isJson = true))
+ assertEquals("{\"message\":\"FORBIDDEN\"}", test.bitlinks().shorten("https://erik.thauvin.net/blog", isJson = true))
}
@Test
fun `long url should be valid`() {
- assertEquals("", bitly.shorten(""))
+ assertEquals("", bitly.bitlinks().shorten(""))
}
@Test
fun `shorten = expand`() {
- val shortUrl = bitly.shorten(blog, domain = "bit.ly")
- assertEquals(blog, bitly.expand(shortUrl))
+ val shortUrl = bitly.bitlinks().shorten(blog, domain = "bit.ly")
+ assertEquals(blog, bitly.bitlinks().expand(shortUrl))
}
@Test
fun `as json`() {
- assertTrue(bitly.shorten(blog, isJson = true).startsWith("{\"created_at\":"))
+ assertTrue(bitly.bitlinks().shorten(blog, isJson = true).startsWith("{\"created_at\":"))
}
@Test
fun `get user`() {
- assertTrue(bitly.call(bitly.buildEndPointUrl("user"), emptyMap(), Methods.GET).contains("\"login\":"))
+ assertTrue(bitly.call(Utils.buildEndPointUrl("user"), emptyMap(), Methods.GET).contains("\"login\":"))
}
}