diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index eef517b..251e704 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -5,22 +5,28 @@ on: [push, pull_request, workflow_dispatch] jobs: build: runs-on: ubuntu-latest + env: GRADLE_OPTS: "-Dorg.gradle.jvmargs=-XX:MaxMetaspaceSize=512m" SONAR_JDK: "11" + strategy: matrix: java-version: [ 1.8, 11, 15 ] + steps: - uses: actions/checkout@v2 with: fetch-depth: 0 + - name: Set up JDK ${{ matrix.java-version }} uses: actions/setup-java@v1 with: java-version: ${{ matrix.java-version }} + - name: Grant execute permission for gradlew run: chmod +x gradlew + - name: Cache SonarCloud packages if: matrix.java-version == env.SONAR_JDK uses: actions/cache@v1 @@ -28,6 +34,7 @@ jobs: path: ~/.sonar/cache key: ${{ runner.os }}-sonar restore-keys: ${{ runner.os }}-sonar + - name: Cache Gradle packages uses: actions/cache@v2 with: @@ -37,14 +44,17 @@ jobs: key: ${{ runner.os }}-gradle-${{ matrix.java-version }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} restore-keys: | ${{ runner.os }}-gradle-${{ matrix.java-version }}- + - name: Test with Gradle run: ./gradlew build check --stacktrace + - name: SonarCloud if: success() && matrix.java-version == env.SONAR_JDK env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: ./gradlew sonarqube + - name: Cleanup Gradle Cache run: | rm -f ~/.gradle/caches/modules-2/modules-2.lock diff --git a/README.md b/README.md index 2ffab64..3727d81 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,24 @@ dependencies { ``` Instructions for using with Maven, Ivy, etc. can be found on [Maven Central](https://search.maven.org/artifact/net.thauvin.erik/isgd-shorten/0.9.2/jar). +### 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: + +```kotlin +try { + Isgd.shorten("http://is.gd/Pt2sET") // already shorten +} catch (e: IsgdException) + println("Status Code: ${e.statusCode}") + println("${e.message}) +} +``` + +``` +Status Code: 400 +Error: Sorry, the URL you entered is on our internal blacklist. It may have been used abusively in the past, or it may link to another URL redirection service. +``` + ### v.gd Additionally, link can be shortened using [v.gd](https://v.gd/) by setting the `isVgd` flag: diff --git a/config/detekt/baseline.xml b/config/detekt/baseline.xml index 013dc30..f7b49af 100644 --- a/config/detekt/baseline.xml +++ b/config/detekt/baseline.xml @@ -3,5 +3,7 @@ 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 diff --git a/examples/src/main/java/com/example/IsgdSample.java b/examples/src/main/java/com/example/IsgdSample.java index 2328776..2f431cb 100644 --- a/examples/src/main/java/com/example/IsgdSample.java +++ b/examples/src/main/java/com/example/IsgdSample.java @@ -1,15 +1,20 @@ package com.example; import net.thauvin.erik.isgd.Isgd; +import net.thauvin.erik.isgd.IsgdException; public final class IsgdSample { public static void main(final String[] args) { if (args.length > 0) { for (final String arg : args) { - if (arg.contains("is.gd")) { - System.out.println(arg + " <-- " + Isgd.lookup(arg)); - } else { - System.out.println(arg + " --> " + Isgd.shorten(arg)); + try { + if (arg.contains("is.gd")) { + System.out.println(arg + " <-- " + Isgd.lookup(arg)); + } else { + System.out.println(arg + " --> " + Isgd.shorten(arg)); + } + } catch (IsgdException e) { + System.out.println(e.getMessage()); } } } else { diff --git a/examples/src/main/kotlin/com/example/IsgdExample.kt b/examples/src/main/kotlin/com/example/IsgdExample.kt index 5acaad5..fe721a0 100644 --- a/examples/src/main/kotlin/com/example/IsgdExample.kt +++ b/examples/src/main/kotlin/com/example/IsgdExample.kt @@ -1,15 +1,20 @@ package com.example import net.thauvin.erik.isgd.Isgd +import net.thauvin.erik.isgd.IsgdException import kotlin.system.exitProcess fun main(args: Array) { if (args.isNotEmpty()) { args.forEach { - if (it.contains("is.gd")) - println(it + " <-- " + Isgd.lookup(it)) - else - println(it + " --> " + Isgd.shorten(it)) + try { + if (it.contains("is.gd")) + println(it + " <-- " + Isgd.lookup(it)) + else + println(it + " --> " + Isgd.shorten(it)) + } catch (e: IsgdException) { + println(e.message) + } } } else { println("Try specifying one or more URLs as arguments.") diff --git a/pom.xml b/pom.xml index 5692130..9012136 100644 --- a/pom.xml +++ b/pom.xml @@ -35,11 +35,22 @@ GitHub https://github.com/ethauvin/isgd-shorten/issues + + + + org.jetbrains.kotlin + kotlin-bom + 1.5.10 + pom + import + + + org.jetbrains.kotlin kotlin-stdlib-jdk8 - 1.5.0 + 1.5.10 compile diff --git a/src/main/kotlin/net/thauvin/erik/isgd/Isgd.kt b/src/main/kotlin/net/thauvin/erik/isgd/Isgd.kt index c812161..0770277 100644 --- a/src/main/kotlin/net/thauvin/erik/isgd/Isgd.kt +++ b/src/main/kotlin/net/thauvin/erik/isgd/Isgd.kt @@ -38,7 +38,7 @@ import java.net.URLEncoder import java.nio.charset.StandardCharsets /** - * See the [is.gd API](https://is.gd/apishorteningreference.php) + * See the [is.gd API](https://is.gd/apishorteningreference.php). */ enum class Format(val type: String) { WEB("web"), SIMPLE("simple"), XML("xml"), JSON("json") @@ -48,18 +48,25 @@ fun String.encode(): String { return URLEncoder.encode(this, StandardCharsets.UTF_8.name()) } +/** + * Implements the [is.gd API](https://is.gd/developers.php). + */ class Isgd private constructor() { companion object { private fun callApi(url: String): String { val connection = URL(url).openConnection() as HttpURLConnection connection.setRequestProperty( "User-Agent", - "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0" + "Mozilla/5.0 (Linux x86_64; rv:89.0) Gecko/20100101 Firefox/89.0" ) - return connection.inputStream.bufferedReader().readText() + if (connection.responseCode in 200..399) { + return connection.inputStream.bufferedReader().readText() + } else { + throw IsgdException(connection.responseCode, connection.errorStream.bufferedReader().readText()) + } } - private fun host(isVgd: Boolean = false): String { + private fun getHost(isVgd: Boolean = false): String { return if (isVgd) "v.gd" else "is.gd" } @@ -68,6 +75,7 @@ class Isgd private constructor() { */ @JvmStatic @JvmOverloads + @Throws(IsgdException::class) fun lookup( shorturl: String, callback: String = "", @@ -78,7 +86,7 @@ class Isgd private constructor() { throw IllegalArgumentException("Please specify a valid short URL to lookup.") } - val sb = StringBuilder("https://${host(isVgd)}/forward.php?shorturl=${shorturl.encode()}") + val sb = StringBuilder("https://${getHost(isVgd)}/forward.php?shorturl=${shorturl.encode()}") if (callback.isNotEmpty()) { sb.append("&callback=${callback.encode()}") @@ -94,6 +102,7 @@ class Isgd private constructor() { */ @JvmStatic @JvmOverloads + @Throws(IsgdException::class) fun shorten( url: String, shorturl: String = "", @@ -106,7 +115,7 @@ class Isgd private constructor() { throw IllegalArgumentException("Please enter a valid URL to shorten.") } - val sb = StringBuilder("https://${host(isVgd)}/create.php?url=${url.encode()}") + val sb = StringBuilder("https://${getHost(isVgd)}/create.php?url=${url.encode()}") if (shorturl.isNotEmpty()) { sb.append("&shorturl=${shorturl.encode()}") diff --git a/src/main/kotlin/net/thauvin/erik/isgd/IsgdException.kt b/src/main/kotlin/net/thauvin/erik/isgd/IsgdException.kt new file mode 100644 index 0000000..2fabf3f --- /dev/null +++ b/src/main/kotlin/net/thauvin/erik/isgd/IsgdException.kt @@ -0,0 +1,45 @@ +/* + * IsgdException.kt + * + * Copyright (c) 2020-2021, 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.isgd + +/** + * Thrown when an exceptional condition has occurred. + * + * @property statusCode The HTTP status code. + * @property message The error message. + */ +class IsgdException(val statusCode: Int, message: String) : Exception(message) { + companion object { + private const val serialVersionUID = 1L + } +} diff --git a/src/test/kotlin/net/thauvin/erik/isgd/IsgdTest.kt b/src/test/kotlin/net/thauvin/erik/isgd/IsgdTest.kt index b6ecd6b..1a1fd39 100644 --- a/src/test/kotlin/net/thauvin/erik/isgd/IsgdTest.kt +++ b/src/test/kotlin/net/thauvin/erik/isgd/IsgdTest.kt @@ -34,6 +34,7 @@ package net.thauvin.erik.isgd import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.test.assertTrue class IsgdTest { @@ -41,6 +42,21 @@ class IsgdTest { private val shortUrl = "https://is.gd/Pt2sET" private val shortVgdUrl = "https://v.gd/2z2ncj" + @Test + fun testException() { + assertFailsWith( + message = "URL is already shorten", + exceptionClass = IsgdException::class, + block = { Isgd.shorten(shortUrl) } + ) + + try { + Isgd.shorten(shortUrl) + } catch (e: IsgdException) { + assertTrue(e.statusCode == 400, "status code == 400") + } + } + @Test fun testLookupDefault() { assertEquals(url, Isgd.lookup(shortUrl))