diff --git a/README.md b/README.md index e3ba552..f31ede5 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ [![License (3-Clause BSD)](https://img.shields.io/badge/license-BSD%203--Clause-blue.svg?style=flat-square)](https://opensource.org/licenses/BSD-3-Clause) -[![Kotlin](https://img.shields.io/badge/kotlin-1.8.0-blue)](https://kotlinlang.org/) +[![Kotlin](https://img.shields.io/badge/kotlin-1.9.10-7f52ff)](https://kotlinlang.org/) [![Nexus Snapshot](https://img.shields.io/nexus/s/net.thauvin.erik/isgd-shorten?label=snapshot&server=https%3A%2F%2Foss.sonatype.org%2F)](https://oss.sonatype.org/content/repositories/snapshots/net/thauvin/erik/isgd-shorten/) [![release](https://img.shields.io/github/release/ethauvin/isgd-shorten.svg)](https://github.com/ethauvin/isgd-shorten/releases/latest) -[![Maven Central](https://maven-badges.herokuapp.com/maven-central/net.thauvin.erik/isgd-shorten/badge.svg)](https://maven-badges.herokuapp.com/maven-central/net.thauvin.erik/isgd-shorten) +[![Maven Central](https://img.shields.io/maven-central/v/net.thauvin.erik/isgd-shorten.svg?color=blue)](https://central.sonatype.com/artifact/net.thauvin.erik/isgd-shorten) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=ethauvin_isgd-shorten&metric=alert_status)](https://sonarcloud.io/dashboard?id=ethauvin_isgd-shorten) [![GitHub CI](https://github.com/ethauvin/isgd-shorten/actions/workflows/gradle.yml/badge.svg)](https://github.com/ethauvin/isgd-shorten/actions/workflows/gradle.yml) @@ -67,8 +67,31 @@ dependencies { implementation("net.thauvin.erik:isgd-shorten:0.9.2") } ``` -Instructions for using with Maven, Ivy, etc. can be found on [Maven Central](https://maven-badges.herokuapp.com/maven-central/net.thauvin.erik/isgd-shorten). +Instructions for using with Maven, Ivy, etc. can be found on [Maven Central](https://central.sonatype.com/artifact/net.thauvin.erik/isgd-shorten). +## Java + +To make it easier to use the library with Java, configuration builders are available: + +```java +var config = new Config.Builder() + .url("https://www.example.com/") + .shorturl("foobar") + .callback("test") + .logstats(true) + .format(Format.JSON) + .build(); + +Isgd.shorten(config); +``` +```java +var config = new Config.Builder() + .shortUrl("https://is.gd/Pt2sET") + .format(Format.XML) + .build(); + +Isgd.lookup(config) +``` ### Errors An `IsgdException` is thrown when an API error occurs. The error message (text, XML or JSON) and HTTP status code can be retrieved as follows: diff --git a/config/detekt/baseline.xml b/config/detekt/baseline.xml index f7b49af..693c365 100644 --- a/config/detekt/baseline.xml +++ b/config/detekt/baseline.xml @@ -5,5 +5,6 @@ LongParameterList:Isgd.kt$Isgd.Companion$( url: String, shorturl: String = "", callback: String = "", logstats: Boolean = false, format: Format = Format.SIMPLE, isVgd: Boolean = false ) MagicNumber:Isgd.kt$Isgd.Companion$200 MagicNumber:Isgd.kt$Isgd.Companion$399 + WildcardImport:IsgdTest.kt$import assertk.assertions.* diff --git a/src/main/kotlin/net/thauvin/erik/isgd/Config.kt b/src/main/kotlin/net/thauvin/erik/isgd/Config.kt new file mode 100644 index 0000000..f6bcb9d --- /dev/null +++ b/src/main/kotlin/net/thauvin/erik/isgd/Config.kt @@ -0,0 +1,72 @@ +/* + * Config.kt + * + * Copyright 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.isgd + +/** + * Provides a builder to create/lookup an is.gd shortlink. + */ +class Config private constructor( + val url: String, + val shorturl: String, + val callback: String, + val logstats: Boolean, + val format: Format, + val isVgd: Boolean +) { + /** + * Configures the parameters to create/lookup an is.gd shortlink. + */ + data class Builder( + var url: String = "", + var shorturl: String = "", + var callback: String = "", + var logstats: Boolean = false, + var format: Format = Format.SIMPLE, + var isVgd: Boolean = false + ) { + fun url(url: String) = apply { this.url = url } + fun shorturl(shorturl: String) = apply { this.shorturl = shorturl } + fun callback(callback: String) = apply { this.callback = callback } + fun logstats(logstats: Boolean) = apply { this.logstats = logstats } + fun format(format: Format) = apply { this.format = format } + fun isVgd(isVgd: Boolean) = apply { this.isVgd = isVgd } + + fun build() = Config( + url, + shorturl, + callback, + logstats, + format, + isVgd + ) + } +} diff --git a/src/main/kotlin/net/thauvin/erik/isgd/Isgd.kt b/src/main/kotlin/net/thauvin/erik/isgd/Isgd.kt index 27b2af7..4c6728c 100644 --- a/src/main/kotlin/net/thauvin/erik/isgd/Isgd.kt +++ b/src/main/kotlin/net/thauvin/erik/isgd/Isgd.kt @@ -31,7 +31,7 @@ package net.thauvin.erik.isgd -import net.thauvin.erik.urlencoder.UrlEncoder +import net.thauvin.erik.urlencoder.UrlEncoderUtil import java.net.HttpURLConnection import java.net.URL @@ -42,7 +42,7 @@ enum class Format(val type: String) { WEB("web"), SIMPLE("simple"), XML("xml"), JSON("json") } -fun String.encode(): String = UrlEncoder.encode(this) +fun String.encode(): String = UrlEncoderUtil.encode(this) /** * Implements the [is.gd API](https://is.gd/developers.php). @@ -69,6 +69,24 @@ class Isgd private constructor() { } /** + * Lookup a shortlink. + * + * See the [is.gd API](https://is.gd/apilookupreference.php). + */ + @JvmStatic + @Throws(IsgdException::class) + fun lookup(config: Config): String { + return lookup( + config.shorturl, + config.callback, + config.format, + config.isVgd + ) + } + + /** + * Lookup a shortlink. + * * See the [is.gd API](https://is.gd/apilookupreference.php). */ @JvmStatic @@ -94,6 +112,26 @@ class Isgd private constructor() { } /** + * Shortens a link. + * + * See the [is.gd API](https://is.gd/apishorteningreference.php). + */ + @JvmStatic + @Throws(IsgdException::class) + fun shorten(config: Config): String { + return shorten( + config.url, + config.shorturl, + config.callback, + config.logstats, + config.format, + config.isVgd + ) + } + + /** + * Shortens a link. + * * See the [is.gd API](https://is.gd/apishorteningreference.php). */ @JvmStatic diff --git a/src/test/kotlin/net/thauvin/erik/isgd/IsgdTest.kt b/src/test/kotlin/net/thauvin/erik/isgd/IsgdTest.kt index 59ca783..e01e4ee 100644 --- a/src/test/kotlin/net/thauvin/erik/isgd/IsgdTest.kt +++ b/src/test/kotlin/net/thauvin/erik/isgd/IsgdTest.kt @@ -33,12 +33,7 @@ package net.thauvin.erik.isgd import assertk.all import assertk.assertThat -import assertk.assertions.contains -import assertk.assertions.isEqualTo -import assertk.assertions.isNotNull -import assertk.assertions.matches -import assertk.assertions.prop -import assertk.assertions.startsWith +import assertk.assertions.* import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -66,6 +61,15 @@ class IsgdTest { } } + @Test + fun testExceptionConfig() { + assertFailsWith( + message = "shorten(config:duplicate)", + exceptionClass = IsgdException::class, + block = { Isgd.shorten(Config.Builder().url(shortUrl).build()) } + ) + } + @Test fun testLookup() { assertFailsWith( @@ -75,15 +79,35 @@ class IsgdTest { ) } + @Test + fun testLookupConfig() { + assertFailsWith( + message = "lookup(config:empty)", + exceptionClass = IllegalArgumentException::class, + block = { Isgd.lookup(Config.Builder().shorturl("").build()) } + ) + } + @Test fun testLookupDefault() { assertEquals(url, Isgd.lookup(shortUrl)) assertEquals(url, Isgd.lookup(shortVgdUrl, isVgd = true), "lookup(isVgd)") } + @Test + fun testLookupDefaultConfig() { + assertEquals(url, Isgd.lookup(Config.Builder().shorturl(shortUrl).build()), "lookup(config)") + assertEquals( + url, Isgd.lookup( + Config.Builder().shorturl(shortVgdUrl).isVgd(true).build() + ), "lookup(config:isVgd)" + ) + } + @Test fun testLookupJson() { assertEquals("{ \"url\": \"$url\" }", Isgd.lookup(shortUrl, format = Format.JSON)) + assertEquals( "test({ \"url\": \"$url\" });", Isgd.lookup(shortUrl, callback = "test", format = Format.JSON), @@ -91,6 +115,20 @@ class IsgdTest { ) } + @Test + fun testLookupJsonConfig() { + assertEquals( + "{ \"url\": \"$url\" }", + Isgd.lookup(Config.Builder().shorturl(shortUrl).format(Format.JSON).build()), "lookup(config)" + ) + + assertEquals( + "test({ \"url\": \"$url\" });", + Isgd.lookup(Config.Builder().shorturl(shortUrl).callback("test").format(Format.JSON).build()), + "lookup(config:callback)" + ) + } + @Test fun testLookupXml() { assertEquals( @@ -99,6 +137,15 @@ class IsgdTest { ) } + @Test + fun testLookupXmlConfig() { + assertEquals( + "$url", + Isgd.lookup(Config.Builder().shorturl(shortUrl).format(Format.XML).build()), + "lookup(config:xml)" + ) + } + @Test fun testShorten() { assertFailsWith( @@ -114,13 +161,38 @@ class IsgdTest { ) } + @Test + fun testShortenConfig() { + assertFailsWith( + message = "shorten(config:empty)", + exceptionClass = IllegalArgumentException::class, + block = { Isgd.shorten(Config.Builder().url("").build()) } + ) + + assertFailsWith( + message = "shorten(config:shorturl)", + exceptionClass = IsgdException::class, + block = { Isgd.shorten(Config.Builder(url).shorturl("test").build()) } + ) + } + @Test fun testShortenDefault() { assertEquals(shortUrl, Isgd.shorten(url), "shorten(url)") assertEquals(shortVgdUrl, Isgd.shorten(url, isVgd = true), "shorten(isVgd)") assertThat(Isgd.shorten(url, logstats = true), "shorten(callback)").matches("https://is.gd/\\w{6}".toRegex()) + } - + @Test + fun testShortenDefaultConfig() { + assertEquals(shortUrl, Isgd.shorten(Config.Builder().url(url).build()), "shorten(config:url)") + assertEquals( + shortVgdUrl, + Isgd.shorten(Config.Builder().url(url).isVgd(true).build()), + "shorten(config:isVgd)" + ) + assertThat(Isgd.shorten(Config.Builder().url(url).logstats(true).build()), "shorten(config:callback)") + .matches("https://is.gd/\\w{6}".toRegex()) } @Test @@ -133,6 +205,19 @@ class IsgdTest { ) } + @Test + fun testShortenJsonConfig() { + assertEquals( + "{ \"shorturl\": \"$shortUrl\" }", + Isgd.shorten(Config.Builder().url(url).format(Format.JSON).build()), "shorten(config:json)" + ) + assertEquals( + "test({ \"shorturl\": \"$shortUrl\" });", + Isgd.shorten(Config.Builder().url(url).callback("test").format(Format.JSON).build()), + "shorten(config:callback,json)" + ) + } + @Test fun testShortenXml() { assertEquals( @@ -142,8 +227,24 @@ class IsgdTest { ) } + @Test + fun testShortenXmlConfig() { + assertEquals( + "" + + "$shortUrl", + Isgd.shorten(Config.Builder().url(url).format(Format.XML).build()), + "shorten(config:xml)" + ) + } + @Test fun testShortenWeb() { assertThat(Isgd.shorten(url, format = Format.WEB)).contains(shortUrl) } + + @Test + fun testShortenWebConfig() { + assertThat(Isgd.shorten(Config.Builder().url(url).format(Format.WEB).build()), "shorten(config:web)") + .contains(shortUrl) + } }