Added Btilink creation config builder. Closes #10

This commit is contained in:
Erik C. Thauvin 2023-01-30 23:17:21 -08:00
parent 725ea3b23b
commit e64afca3cc
4 changed files with 161 additions and 59 deletions

View file

@ -31,10 +31,12 @@
package net.thauvin.erik.bitly package net.thauvin.erik.bitly
import net.thauvin.erik.bitly.Utils.Companion.isSevereLoggable import net.thauvin.erik.bitly.Utils.isSevereLoggable
import net.thauvin.erik.bitly.Utils.Companion.isValidUrl import net.thauvin.erik.bitly.Utils.isValidUrl
import net.thauvin.erik.bitly.Utils.Companion.removeHttp import net.thauvin.erik.bitly.Utils.removeHttp
import net.thauvin.erik.bitly.Utils.Companion.toEndPoint import net.thauvin.erik.bitly.Utils.toEndPoint
import net.thauvin.erik.bitly.config.CreateConfig
import net.thauvin.erik.bitly.config.UpdateConfig
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
import java.util.logging.Level import java.util.logging.Level
@ -79,7 +81,7 @@ open class Bitlinks(private val accessToken: String) {
if (bitlink.isNotBlank()) { if (bitlink.isNotBlank()) {
lastCallResponse = Utils.call( lastCallResponse = Utils.call(
accessToken, accessToken,
("/bitlinks/${bitlink.removeHttp()}/clicks/summary").toEndPoint(), ("bitlinks/${bitlink.removeHttp()}/clicks/summary").toEndPoint(),
mapOf( mapOf(
"unit" to unit.toString().lowercase(), "unit" to unit.toString().lowercase(),
"units" to units.toString(), "units" to units.toString(),
@ -93,6 +95,27 @@ open class Bitlinks(private val accessToken: String) {
return clicks return clicks
} }
/**
* Converts a long url to a Bitlink and sets additional parameters.
*
* See the [Bit.ly API](https://dev.bitly.com/api-reference#createFullBitlink) for more information.
*
* @param config The parameters' configuration.
* @return The shortened URL or an empty string on error.
*/
@Synchronized
fun create(config: CreateConfig): String {
return create(
config.domain,
config.title,
config.group_guid,
config.tags,
config.deepLinks,
config.long_url,
config.toJson
)
}
/** /**
* Converts a long url to a Bitlink and sets additional parameters. * Converts a long url to a Bitlink and sets additional parameters.
* *
@ -102,10 +125,9 @@ open class Bitlinks(private val accessToken: String) {
* @param group_guid A GUID for a Bitly group. * @param group_guid A GUID for a Bitly group.
* @param long_url The long URL. * @param long_url The long URL.
* @param toJson Returns the full JSON response if `true`. * @param toJson Returns the full JSON response if `true`.
* @return The shorten URL or an empty string on error. * @return The shortened URL or an empty string on error.
*/ */
@Synchronized @Synchronized
@JvmOverloads
fun create( fun create(
domain: String = Constants.EMPTY, domain: String = Constants.EMPTY,
title: String = Constants.EMPTY, title: String = Constants.EMPTY,
@ -119,7 +141,7 @@ open class Bitlinks(private val accessToken: String) {
if (long_url.isNotBlank()) { if (long_url.isNotBlank()) {
lastCallResponse = Utils.call( lastCallResponse = Utils.call(
accessToken, accessToken,
"/bitlinks".toEndPoint(), "bitlinks".toEndPoint(),
mutableMapOf<String, Any>("long_url" to long_url).apply { mutableMapOf<String, Any>("long_url" to long_url).apply {
if (domain.isNotBlank()) put("domain", domain) if (domain.isNotBlank()) put("domain", domain)
if (title.isNotBlank()) put("title", title) if (title.isNotBlank()) put("title", title)
@ -149,7 +171,7 @@ open class Bitlinks(private val accessToken: String) {
if (bitlink_id.isNotBlank()) { if (bitlink_id.isNotBlank()) {
lastCallResponse = Utils.call( lastCallResponse = Utils.call(
accessToken, accessToken,
"/expand".toEndPoint(), "expand".toEndPoint(),
mapOf("bitlink_id" to bitlink_id.removeHttp()) mapOf("bitlink_id" to bitlink_id.removeHttp())
) )
longUrl = parseJsonResponse(lastCallResponse, "long_url", longUrl, toJson) longUrl = parseJsonResponse(lastCallResponse, "long_url", longUrl, toJson)
@ -213,7 +235,7 @@ open class Bitlinks(private val accessToken: String) {
if (domain.isNotBlank()) put("domain", domain) if (domain.isNotBlank()) put("domain", domain)
} }
lastCallResponse = Utils.call(accessToken, "/shorten".toEndPoint(), params) lastCallResponse = Utils.call(accessToken, "shorten".toEndPoint(), params)
bitlink = parseJsonResponse(lastCallResponse, "link", bitlink, toJson) bitlink = parseJsonResponse(lastCallResponse, "link", bitlink, toJson)
} }
@ -223,11 +245,11 @@ open class Bitlinks(private val accessToken: String) {
/** /**
* Updates fields in the specified Bitlink. * Updates parameters in the specified Bitlink.
* *
* See the [Bit.ly API](https://dev.bitly.com/api-reference#updateBitlink) for more information. * See the [Bit.ly API](https://dev.bitly.com/api-reference#updateBitlink) for more information.
* *
* @param config The update configuration. * @param config The parameters' configuration.
* @return [Constants.TRUE] if the update was successful, [Constants.FALSE] otherwise. * @return [Constants.TRUE] if the update was successful, [Constants.FALSE] otherwise.
*/ */
@Synchronized @Synchronized
@ -251,7 +273,7 @@ open class Bitlinks(private val accessToken: String) {
} }
/** /**
* Updates fields in the specified Bitlink. * Updates parameters in the specified Bitlink.
* *
* See the [Bit.ly API](https://dev.bitly.com/api-reference#updateBitlink) for more information. * See the [Bit.ly API](https://dev.bitly.com/api-reference#updateBitlink) for more information.
* *
@ -260,7 +282,6 @@ open class Bitlinks(private val accessToken: String) {
* @return [Constants.TRUE] if the update was successful, [Constants.FALSE] otherwise. * @return [Constants.TRUE] if the update was successful, [Constants.FALSE] otherwise.
*/ */
@Synchronized @Synchronized
@JvmOverloads
fun update( fun update(
bitlink: String, bitlink: String,
references: Map<String, String> = emptyMap(), references: Map<String, String> = emptyMap(),
@ -280,7 +301,7 @@ open class Bitlinks(private val accessToken: String) {
var result = if (toJson) Constants.EMPTY_JSON else Constants.FALSE var result = if (toJson) Constants.EMPTY_JSON else Constants.FALSE
if (bitlink.isNotBlank()) { if (bitlink.isNotBlank()) {
lastCallResponse = Utils.call( lastCallResponse = Utils.call(
accessToken, "/bitlinks/${bitlink.removeHttp()}".toEndPoint(), mutableMapOf<String, Any>().apply { accessToken, "bitlinks/${bitlink.removeHttp()}".toEndPoint(), mutableMapOf<String, Any>().apply {
if (references.isNotEmpty()) put("references", references) if (references.isNotEmpty()) put("references", references)
if (archived) put("archived", true) if (archived) put("archived", true)
if (tags.isNotEmpty()) put("tags", tags) if (tags.isNotEmpty()) put("tags", tags)

View file

@ -0,0 +1,81 @@
/*
* CreateConfig.kt
*
* Copyright 2020-2023 Erik C. Thauvin (erik@thauvin.net)
*
* 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.config
import net.thauvin.erik.bitly.Constants
/**
* Provides a builder to create a Bitlink.
*/
class CreateConfig private constructor(
val domain: String,
val title: String,
val group_guid: String,
val tags: Array<String>,
val deepLinks: Array<Map<String, String>>,
val long_url: String,
val toJson: Boolean
) {
/**
* Configures the creation parameters of a Bitlink.
*
* See the [Bit.ly API](https://dev.bitly.com/api-reference#createFullBitlink) for more information.
**/
@Suppress("unused", "ArrayInDataClass")
data class Builder(
var domain: String = Constants.EMPTY,
var title: String = Constants.EMPTY,
var group_guid: String = Constants.EMPTY,
var tags: Array<String> = emptyArray(),
var deeplinks: Array<Map<String, String>> = emptyArray(),
var long_url: String = Constants.EMPTY,
var toJson: Boolean = false
) {
fun domain(domain: String) = apply { this.domain = domain }
fun title(title: String) = apply { this.title = title }
fun group_guid(group_guid: String) = apply { this.group_guid = group_guid }
fun tags(tags: Array<String>) = apply { this.tags = tags }
fun deeplinks(deeplinks: Array<Map<String, String>>) = apply { this.deeplinks = deeplinks }
fun longUrl(long_url: String) = apply { this.long_url = long_url }
fun toJson(toJson: Boolean) = apply { this.toJson = toJson }
fun build() = CreateConfig(
domain,
title,
group_guid,
tags,
deeplinks,
long_url,
toJson
)
}
}

View file

@ -29,8 +29,13 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package net.thauvin.erik.bitly package net.thauvin.erik.bitly.config
import net.thauvin.erik.bitly.Constants
/**
* Provides a builder to update a Bitlink.
*/
class UpdateConfig private constructor( class UpdateConfig private constructor(
val bitlink: String, val bitlink: String,
val references: Map<String, String>, val references: Map<String, String>,
@ -47,7 +52,12 @@ class UpdateConfig private constructor(
val id: String, val id: String,
val toJson: Boolean, val toJson: Boolean,
) { ) {
@Suppress("unused") /**
* Configures the update parameters of a Bitlink.
*
* See the [Bit.ly API](https://dev.bitly.com/api-reference#updateBitlink) for more information.
**/
@Suppress("unused", "ArrayInDataClass")
data class Builder( data class Builder(
var bitlink: String = Constants.EMPTY, var bitlink: String = Constants.EMPTY,
var references: Map<String, String> = emptyMap(), var references: Map<String, String> = emptyMap(),
@ -95,47 +105,5 @@ class UpdateConfig private constructor(
id, id,
toJson toJson
) )
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Builder
if (bitlink != other.bitlink) return false
if (references != other.references) return false
if (archived != other.archived) return false
if (!tags.contentEquals(other.tags)) return false
if (created_at != other.created_at) return false
if (title != other.title) return false
if (!deeplinks.contentEquals(other.deeplinks)) return false
if (created_by != other.created_by) return false
if (long_url != other.long_url) return false
if (client_id != other.client_id) return false
if (!custom_bitlinks.contentEquals(other.custom_bitlinks)) return false
if (link != other.link) return false
if (id != other.id) return false
if (toJson != other.toJson) return false
return true
}
override fun hashCode(): Int {
var result = bitlink.hashCode()
result = 31 * result + references.hashCode()
result = 31 * result + archived.hashCode()
result = 31 * result + tags.contentHashCode()
result = 31 * result + created_at.hashCode()
result = 31 * result + title.hashCode()
result = 31 * result + deeplinks.contentHashCode()
result = 31 * result + created_by.hashCode()
result = 31 * result + long_url.hashCode()
result = 31 * result + client_id.hashCode()
result = 31 * result + custom_bitlinks.contentHashCode()
result = 31 * result + link.hashCode()
result = 31 * result + id.hashCode()
result = 31 * result + toJson.hashCode()
return result
}
} }
} }

View file

@ -184,6 +184,38 @@ class BitlyTest {
) )
} }
@Test
fun `create bitlink with config`() {
var config = CreateConfig.Builder().apply {
long_url = longUrl
}.build()
assertThat(bitly.bitlinks().create(config), "create(config)")
.matches("https://\\w+.\\w{2}/\\w{7}".toRegex())
config = CreateConfig.Builder().apply {
domain = "bit.ly"
title = "Erik's Blog"
tags = arrayOf("erik", "thauvin", "blog", "weblog")
long_url = longUrl
}.build()
assertEquals(
shortUrl,
bitly.bitlinks().create(config)
)
}
@Test
fun `shorten with invalid domain`() {
val bl = bitly.bitlinks()
bl.shorten("https://www.examples.com", domain = "foo.com")
assertThat(bl.lastCallResponse).all {
prop(CallResponse::isSuccessful).isFalse()
prop(CallResponse::isBadRequest).isTrue()
prop(CallResponse::message).isEqualTo("INVALID_ARG_DOMAIN")
prop(CallResponse::description).contains("invalid")
}
}
@Test @Test
fun `update bitlink`() { fun `update bitlink`() {
val bl = bitly.bitlinks() val bl = bitly.bitlinks()