From e64afca3ccd1e061bcea5140805b085c231b7e9a Mon Sep 17 00:00:00 2001 From: "Erik C. Thauvin" Date: Mon, 30 Jan 2023 23:17:21 -0800 Subject: [PATCH] Added Btilink creation config builder. Closes #10 --- .../kotlin/net/thauvin/erik/bitly/Bitlinks.kt | 51 ++++++++---- .../thauvin/erik/bitly/config/CreateConfig.kt | 81 +++++++++++++++++++ .../erik/bitly/{ => config}/UpdateConfig.kt | 56 +++---------- .../net/thauvin/erik/bitly/BitlyTest.kt | 32 ++++++++ 4 files changed, 161 insertions(+), 59 deletions(-) create mode 100644 src/main/kotlin/net/thauvin/erik/bitly/config/CreateConfig.kt rename src/main/kotlin/net/thauvin/erik/bitly/{ => config}/UpdateConfig.kt (68%) diff --git a/src/main/kotlin/net/thauvin/erik/bitly/Bitlinks.kt b/src/main/kotlin/net/thauvin/erik/bitly/Bitlinks.kt index 9578aa8..0d76572 100644 --- a/src/main/kotlin/net/thauvin/erik/bitly/Bitlinks.kt +++ b/src/main/kotlin/net/thauvin/erik/bitly/Bitlinks.kt @@ -31,10 +31,12 @@ package net.thauvin.erik.bitly -import net.thauvin.erik.bitly.Utils.Companion.isSevereLoggable -import net.thauvin.erik.bitly.Utils.Companion.isValidUrl -import net.thauvin.erik.bitly.Utils.Companion.removeHttp -import net.thauvin.erik.bitly.Utils.Companion.toEndPoint +import net.thauvin.erik.bitly.Utils.isSevereLoggable +import net.thauvin.erik.bitly.Utils.isValidUrl +import net.thauvin.erik.bitly.Utils.removeHttp +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.JSONObject import java.util.logging.Level @@ -79,7 +81,7 @@ open class Bitlinks(private val accessToken: String) { if (bitlink.isNotBlank()) { lastCallResponse = Utils.call( accessToken, - ("/bitlinks/${bitlink.removeHttp()}/clicks/summary").toEndPoint(), + ("bitlinks/${bitlink.removeHttp()}/clicks/summary").toEndPoint(), mapOf( "unit" to unit.toString().lowercase(), "units" to units.toString(), @@ -93,6 +95,27 @@ open class Bitlinks(private val accessToken: String) { 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. * @@ -102,10 +125,9 @@ open class Bitlinks(private val accessToken: String) { * @param group_guid A GUID for a Bitly group. * @param long_url The long URL. * @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 - @JvmOverloads fun create( domain: String = Constants.EMPTY, title: String = Constants.EMPTY, @@ -119,7 +141,7 @@ open class Bitlinks(private val accessToken: String) { if (long_url.isNotBlank()) { lastCallResponse = Utils.call( accessToken, - "/bitlinks".toEndPoint(), + "bitlinks".toEndPoint(), mutableMapOf("long_url" to long_url).apply { if (domain.isNotBlank()) put("domain", domain) if (title.isNotBlank()) put("title", title) @@ -149,7 +171,7 @@ open class Bitlinks(private val accessToken: String) { if (bitlink_id.isNotBlank()) { lastCallResponse = Utils.call( accessToken, - "/expand".toEndPoint(), + "expand".toEndPoint(), mapOf("bitlink_id" to bitlink_id.removeHttp()) ) longUrl = parseJsonResponse(lastCallResponse, "long_url", longUrl, toJson) @@ -213,7 +235,7 @@ open class Bitlinks(private val accessToken: String) { 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) } @@ -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. * - * @param config The update configuration. + * @param config The parameters' configuration. * @return [Constants.TRUE] if the update was successful, [Constants.FALSE] otherwise. */ @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. * @@ -260,7 +282,6 @@ open class Bitlinks(private val accessToken: String) { * @return [Constants.TRUE] if the update was successful, [Constants.FALSE] otherwise. */ @Synchronized - @JvmOverloads fun update( bitlink: String, references: Map = emptyMap(), @@ -280,7 +301,7 @@ open class Bitlinks(private val accessToken: String) { var result = if (toJson) Constants.EMPTY_JSON else Constants.FALSE if (bitlink.isNotBlank()) { lastCallResponse = Utils.call( - accessToken, "/bitlinks/${bitlink.removeHttp()}".toEndPoint(), mutableMapOf().apply { + accessToken, "bitlinks/${bitlink.removeHttp()}".toEndPoint(), mutableMapOf().apply { if (references.isNotEmpty()) put("references", references) if (archived) put("archived", true) if (tags.isNotEmpty()) put("tags", tags) diff --git a/src/main/kotlin/net/thauvin/erik/bitly/config/CreateConfig.kt b/src/main/kotlin/net/thauvin/erik/bitly/config/CreateConfig.kt new file mode 100644 index 0000000..e26b17a --- /dev/null +++ b/src/main/kotlin/net/thauvin/erik/bitly/config/CreateConfig.kt @@ -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, + val deepLinks: Array>, + 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 = emptyArray(), + var deeplinks: Array> = 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) = apply { this.tags = tags } + fun deeplinks(deeplinks: Array>) = 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 + ) + } +} diff --git a/src/main/kotlin/net/thauvin/erik/bitly/UpdateConfig.kt b/src/main/kotlin/net/thauvin/erik/bitly/config/UpdateConfig.kt similarity index 68% rename from src/main/kotlin/net/thauvin/erik/bitly/UpdateConfig.kt rename to src/main/kotlin/net/thauvin/erik/bitly/config/UpdateConfig.kt index 3a6d68c..0fda24f 100644 --- a/src/main/kotlin/net/thauvin/erik/bitly/UpdateConfig.kt +++ b/src/main/kotlin/net/thauvin/erik/bitly/config/UpdateConfig.kt @@ -29,8 +29,13 @@ * 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( val bitlink: String, val references: Map, @@ -47,7 +52,12 @@ class UpdateConfig private constructor( val id: String, 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( var bitlink: String = Constants.EMPTY, var references: Map = emptyMap(), @@ -95,47 +105,5 @@ class UpdateConfig private constructor( id, 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 - } } } diff --git a/src/test/kotlin/net/thauvin/erik/bitly/BitlyTest.kt b/src/test/kotlin/net/thauvin/erik/bitly/BitlyTest.kt index 1f45041..8405d3f 100644 --- a/src/test/kotlin/net/thauvin/erik/bitly/BitlyTest.kt +++ b/src/test/kotlin/net/thauvin/erik/bitly/BitlyTest.kt @@ -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 fun `update bitlink`() { val bl = bitly.bitlinks()