diff --git a/.circleci/config.yml b/.circleci/config.yml index 8dabc3f..2922277 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,62 +1,53 @@ -version: 2.1 - -orbs: - sdkman: joshdholtz/sdkman@0.2.0 - +version: 2 defaults: &defaults working_directory: ~/repo environment: JVM_OPTS: -Xmx3200m TERM: dumb - CI_NAME: "CircleCI" - -commands: - build_and_test: - parameters: - reports-dir: - type: string - default: "build/reports/test_results" - steps: - - checkout - - sdkman/setup-sdkman - - sdkman/sdkman-install: - candidate: kotlin - version: 2.0.20 - - run: - name: Download dependencies - command: ./bld download - - run: - name: Compile source - command: ./bld compile - - run: - name: Run tests - command: ./bld jacoco -reports-dir=<< parameters.reports-dir >> - - store_test_results: - path: << parameters.reports-dir >> - - store_artifacts: - path: build/reports/jacoco/test/html + CI: true +defaults_gradle: &defaults_gradle + steps: + - checkout + - restore_cache: + keys: + - gradle-dependencies-{{ checksum "build.gradle.kts" }} + # fallback to using the latest cache if no exact match is found + - gradle-dependencies- + - run: + name: Gradle Dependencies + command: ./gradlew dependencies + - save_cache: + paths: ~/.m2 + key: gradle-dependencies-{{ checksum "build.gradle.kts" }} + - run: + name: Run All Checks + command: ./gradlew check + - store_artifacts: + path: build/reports/ + destination: reports + - store_test_results: + path: build/reports/ jobs: - bld_jdk17: + build_gradle_jdk17: <<: *defaults docker: - image: cimg/openjdk:17.0 - steps: - - build_and_test + <<: *defaults_gradle - bld_jdk20: + build_gradle_jdk11: <<: *defaults docker: - - image: cimg/openjdk:20.0 + - image: cimg/openjdk:11.0 - steps: - - build_and_test + <<: *defaults_gradle workflows: - bld: + version: 2 + gradle: jobs: - - bld_jdk17 - - bld_jdk20 + - build_gradle_jdk11 + - build_gradle_jdk17 diff --git a/.github/workflows/bld.yml b/.github/workflows/bld.yml deleted file mode 100644 index 007e63a..0000000 --- a/.github/workflows/bld.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: bld-ci - -on: [ push, pull_request, workflow_dispatch ] - -env: - COVERAGE_JDK: "21" - COVERAGE_KOTLIN: "2.1.20" - -jobs: - build-bld-project: - strategy: - matrix: - java-version: [ 17, 21, 24 ] - kotlin-version: [ 1.9.25, 2.0.21, 2.1.20 ] - os: [ ubuntu-latest, windows-latest, macos-latest ] - - runs-on: ${{ matrix.os }} - - steps: - - name: Checkout source repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up JDK ${{ matrix.java-version }} with Kotlin ${{ matrix.kotlin-version }} - uses: actions/setup-java@v4 - with: - distribution: "zulu" - java-version: ${{ matrix.java-version }} - - - name: Download dependencies - run: ./bld download - - - name: Compile source - run: ./bld compile - - - name: Run tests - run: ./bld jacoco - - - name: Remove pom.xml - if: success() && matrix.java-version == env.COVERAGE_JDK && matrix.kotlin-version == env.COVERAGE_KOTLIN - && matrix.os == 'ubuntu-latest' - run: rm -rf pom.xml - - - name: SonarCloud Scan - uses: sonarsource/sonarcloud-github-action@master - if: success() && matrix.java-version == env.COVERAGE_JDK && matrix.kotlin-version == env.COVERAGE_KOTLIN - && matrix.os == 'ubuntu-latest' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 0000000..42bc1da --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,49 @@ +name: gradle-ci + +on: [ push, pull_request, workflow_dispatch ] + +jobs: + build: + runs-on: ubuntu-latest + + env: + GRADLE_OPTS: "-Dorg.gradle.jvmargs=-XX:MaxMetaspaceSize=512m" + SONAR_JDK: "17" + + strategy: + matrix: + java-version: [ 11, 17, 20 ] + + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up JDK ${{ matrix.java-version }} + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + 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@v3 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + + - name: Test with Gradle + uses: gradle/gradle-build-action@v2 + with: + arguments: 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 sonar --info diff --git a/.gitignore b/.gitignore index ea86fe8..0742f86 100644 --- a/.gitignore +++ b/.gitignore @@ -1,57 +1,84 @@ -.gradle +!.vscode/extensions.json +!.vscode/launch.json +!.vscode/settings.json +!.vscode/tasks.json +*.class +*.code-workspace +*.ctxt +*.iws +*.log +*.nar +*.rar +*.sublime-* +*.tar.gz +*.zip .DS_Store -build -lib/bld/** -!lib/bld/bld-wrapper.jar -!lib/bld/bld-wrapper.properties -lib/compile/ -lib/runtime/ -lib/standalone/ -lib/test/ - -# IDEA ignores - -# User-specific -.idea/**/workspace.xml -.idea/**/tasks.xml -.idea/**/usage.statistics.xml -.idea/**/dictionaries -.idea/**/shelf - -# AWS User-specific -.idea/**/aws.xml - -# Generated files -.idea/**/contentModel.xml - -# Sensitive or high-churn files -.idea/**/dataSources/ -.idea/**/dataSources.ids -.idea/**/dataSources.local.xml -.idea/**/sqlDataSources.xml -.idea/**/dynamic.xml -.idea/**/uiDesigner.xml -.idea/**/dbnavigator.xml - -# Gradle -.idea/**/gradle.xml - -# Mongo Explorer plugin -.idea/**/mongoSettings.xml - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin +.classpath +.gradle +.history +.kobalt +.mtj.tmp/ +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar +.nb-gradle +.project +.scannerwork +.settings +.vscode/* +/**/.idea/$CACHE_FILE$ +/**/.idea/$PRODUCT_WORKSPACE_FILE$ +/**/.idea/**/caches/build_file_checksums.ser +/**/.idea/**/contentModel.xml +/**/.idea/**/dataSources.ids +/**/.idea/**/dataSources.local.xml +/**/.idea/**/dataSources/ +/**/.idea/**/dbnavigator.xml +/**/.idea/**/dictionaries +/**/.idea/**/dynamic.xml +/**/.idea/**/gradle.xml +/**/.idea/**/httpRequests +/**/.idea/**/libraries +/**/.idea/**/mongoSettings.xml +/**/.idea/**/replstate.xml +/**/.idea/**/shelf +/**/.idea/**/shelf/ +/**/.idea/**/sqlDataSources.xml +/**/.idea/**/tasks.xml +/**/.idea/**/uiDesigner.xml +/**/.idea/**/usage.statistics.xml +/**/.idea/**/workspace.xml +/**/.idea/sonarlint* +/**/.idea_modules/ +Thumbs.db +__pycache__ atlassian-ide-plugin.xml - -# Cursive Clojure plugin -.idea/replstate.xml - -# SonarLint plugin -.idea/sonarlint/ - -# Editor-based Rest Client -.idea/httpRequests - +bin/ +build/ +cmake-build-*/ +com_crashlytics_export_strings.xml +crashlytics-build.properties +crashlytics.properties +dependency-reduced-pom.xml +deploy/ +dist/ +ehthumbs.db +fabric.properties +gen/ +hs_err_pid* +kobaltBuild +kobaltw*-test +lib/kotlin* +libs/ local.properties +out/ +pom.xml.asc +pom.xml.next +pom.xml.releaseBackup +pom.xml.tag +pom.xml.versionsBackup +proguard-project.txt +project.properties +release.properties +target/ +test-output +venv diff --git a/.idea/app.iml b/.idea/app.iml deleted file mode 100644 index 2c1fe21..0000000 --- a/.idea/app.iml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/bld.iml b/.idea/bld.iml deleted file mode 100644 index e63e11e..0000000 --- a/.idea/bld.iml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/bld.xml b/.idea/bld.xml deleted file mode 100644 index 6600cee..0000000 --- a/.idea/bld.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/intellij-javadocs-4.0.1.xml b/.idea/intellij-javadocs-4.0.1.xml deleted file mode 100644 index 4b17413..0000000 --- a/.idea/intellij-javadocs-4.0.1.xml +++ /dev/null @@ -1,204 +0,0 @@ - - - - - UPDATE - false - true - - TYPE - METHOD - FIELD - - - DEFAULT - PROTECTED - PUBLIC - - - - - - ^.*(public|protected|private)*.+interface\s+\w+.* - /**\n - * The interface ${name}.\n -<#if element.typeParameters?has_content> * \n -</#if> -<#list element.typeParameters as typeParameter> - * @param <${typeParameter.name}> the type parameter\n -</#list> - */ - - - ^.*(public|protected|private)*.+enum\s+\w+.* - /**\n - * The enum ${name}.\n - */ - - - ^.*(public|protected|private)*.+class\s+\w+.* - /**\n - * The type ${name}.\n -<#if element.typeParameters?has_content> * \n -</#if> -<#list element.typeParameters as typeParameter> - * @param <${typeParameter.name}> the type parameter\n -</#list> - */ - - - .+ - /**\n - * The type ${name}.\n - */ - - - - - .+ - /**\n - * Instantiates a new ${name}.\n -<#if element.parameterList.parameters?has_content> - *\n -</#if> -<#list element.parameterList.parameters as parameter> - * @param ${parameter.name} the ${paramNames[parameter.name]}\n -</#list> -<#if element.throwsList.referenceElements?has_content> - *\n -</#if> -<#list element.throwsList.referenceElements as exception> - * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n -</#list> - */ - - - - - ^.*(public|protected|private)*\s*.*(\w(\s*<.+>)*)+\s+get\w+\s*\(.*\).+ - /**\n - * Gets ${partName}.\n -<#if element.typeParameters?has_content> * \n -</#if> -<#list element.typeParameters as typeParameter> - * @param <${typeParameter.name}> the type parameter\n -</#list> -<#if element.parameterList.parameters?has_content> - *\n -</#if> -<#list element.parameterList.parameters as parameter> - * @param ${parameter.name} the ${paramNames[parameter.name]}\n -</#list> -<#if isNotVoid> - *\n - * @return the ${partName}\n -</#if> -<#if element.throwsList.referenceElements?has_content> - *\n -</#if> -<#list element.throwsList.referenceElements as exception> - * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n -</#list> - */ - - - ^.*(public|protected|private)*\s*.*(void|\w(\s*<.+>)*)+\s+set\w+\s*\(.*\).+ - /**\n - * Sets ${partName}.\n -<#if element.typeParameters?has_content> * \n -</#if> -<#list element.typeParameters as typeParameter> - * @param <${typeParameter.name}> the type parameter\n -</#list> -<#if element.parameterList.parameters?has_content> - *\n -</#if> -<#list element.parameterList.parameters as parameter> - * @param ${parameter.name} the ${paramNames[parameter.name]}\n -</#list> -<#if isNotVoid> - *\n - * @return the ${partName}\n -</#if> -<#if element.throwsList.referenceElements?has_content> - *\n -</#if> -<#list element.throwsList.referenceElements as exception> - * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n -</#list> - */ - - - ^.*((public\s+static)|(static\s+public))\s+void\s+main\s*\(\s*String\s*(\[\s*\]|\.\.\.)\s+\w+\s*\).+ - /**\n - * The entry point of application.\n - - <#if element.parameterList.parameters?has_content> - *\n -</#if> - * @param ${element.parameterList.parameters[0].name} the input arguments\n -<#if element.throwsList.referenceElements?has_content> - *\n -</#if> -<#list element.throwsList.referenceElements as exception> - * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n -</#list> - */ - - - .+ - /**\n - * ${name}<#if isNotVoid> ${return}</#if>.\n -<#if element.typeParameters?has_content> * \n -</#if> -<#list element.typeParameters as typeParameter> - * @param <${typeParameter.name}> the type parameter\n -</#list> -<#if element.parameterList.parameters?has_content> - *\n -</#if> -<#list element.parameterList.parameters as parameter> - * @param ${parameter.name} the ${paramNames[parameter.name]}\n -</#list> -<#if isNotVoid> - *\n - * @return the ${return}\n -</#if> -<#if element.throwsList.referenceElements?has_content> - *\n -</#if> -<#list element.throwsList.referenceElements as exception> - * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n -</#list> - */ - - - - - ^.*(public|protected|private)*.+static.*(\w\s\w)+.+ - /**\n - * The constant ${element.getName()}.\n - */ - - - ^.*(public|protected|private)*.*(\w\s\w)+.+ - /**\n - <#if element.parent.isInterface()> - * The constant ${element.getName()}.\n -<#else> - * The ${name}.\n -</#if> */ - - - .+ - /**\n - <#if element.parent.isEnum()> - *${name} ${typeName}.\n -<#else> - * The ${name}.\n -</#if>*/ - - - - - \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index a8d9757..f8467b4 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - - + \ No newline at end of file diff --git a/.idea/libraries/bld.xml b/.idea/libraries/bld.xml deleted file mode 100644 index 153a060..0000000 --- a/.idea/libraries/bld.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/.idea/libraries/compile.xml b/.idea/libraries/compile.xml deleted file mode 100644 index 99cc0c0..0000000 --- a/.idea/libraries/compile.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/runtime.xml b/.idea/libraries/runtime.xml deleted file mode 100644 index d4069f2..0000000 --- a/.idea/libraries/runtime.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/test.xml b/.idea/libraries/test.xml deleted file mode 100644 index 57ed5ef..0000000 --- a/.idea/libraries/test.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index f2b4c1e..caf34dd 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,21 +1,9 @@ - - - - - - - - - - - - - - - + + + - - + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 55adcb9..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt index 82ecd17..4331a4d 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright 2022-2025 Erik C. Thauvin (erik@thauvin.net) +Copyright 2022-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: diff --git a/README.md b/README.md index bb86972..3627830 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,11 @@ [![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-2.1.20-7f52ff)](https://kotlinlang.org/) -[![bld](https://img.shields.io/badge/2.2.1-FA9052?label=bld&labelColor=2392FF)](https://rife2.com/bld) +[![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/jokeapi?label=snapshot&server=https%3A%2F%2Foss.sonatype.org%2F)](https://oss.sonatype.org/content/repositories/snapshots/net/thauvin/erik/jokeapi/) [![Release](https://img.shields.io/github/release/ethauvin/jokeapi.svg)](https://github.com/ethauvin/jokeapi/releases/latest) [![Maven Central](https://img.shields.io/maven-central/v/net.thauvin.erik/jokeapi?color=blue)](https://central.sonatype.com/artifact/net.thauvin.erik/jokeapi) -[![Nexus Snapshot](https://img.shields.io/nexus/s/net.thauvin.erik/jokeapi?label=snapshot&server=https%3A%2F%2Foss.sonatype.org%2F)](https://oss.sonatype.org/content/repositories/snapshots/net/thauvin/erik/jokeapi/) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=ethauvin_jokeapi&metric=alert_status)](https://sonarcloud.io/dashboard?id=ethauvin_jokeapi) -[![GitHub CI](https://github.com/ethauvin/jokeapi/actions/workflows/bld.yml/badge.svg)](https://github.com/ethauvin/jokeapi/actions/workflows/bld.yml) +[![GitHub CI](https://github.com/ethauvin/jokeapi/actions/workflows/gradle.yml/badge.svg)](https://github.com/ethauvin/jokeapi/actions/workflows/gradle.yml) [![CircleCI](https://circleci.com/gh/ethauvin/jokeapi/tree/master.svg?style=shield)](https://circleci.com/gh/ethauvin/jokeapi/tree/master) # JokeAPI for Kotlin, Java and Android @@ -16,7 +15,7 @@ A simple library to retrieve jokes from [Sv443's JokeAPI](https://v2.jokeapi.dev ## Examples (TL;DR) ```kotlin -import net.thauvin.erik.jokeapi.joke +import net.thauvin.erik.jokeapi.getJoke val joke = joke() val safe = joke(safe = true) @@ -89,19 +88,6 @@ var config = new JokeConfig.Builder() var joke = JokeApi.joke(config); joke.getJoke().forEach(System.out::println); ``` - -## bld - -To use with [bld](https://rife2.com/bld), include the following dependency in your build file: - -```java -repositories = List.of(MAVEN_CENTRAL, SONATYPE_SNAPSHOTS_LEGACY); - -scope(compile) - .include(dependency("net.thauvin.erik", "jokeapi", "1.0.0")); -``` -Be sure to use the [bld Kotlin extension](https://github.com/rife2/bld-kotlin) in your project. - ## Gradle, Maven, etc. To use with [Gradle](https://gradle.org/), include the following dependency in your build file: @@ -112,7 +98,7 @@ repositories { } dependencies { - implementation("net.thauvin.erik:jokeapi:1.0.0") + implementation("net.thauvin.erik:jokeapi:0.9.0") } ``` @@ -124,10 +110,9 @@ You can also retrieve one or more raw (unprocessed) jokes in all [supported form For example for YAML: ```kotlin -var jokes = getRawJokes(format = Format.YAML, idRange = IdRange(22)) -println(jokes.data) +var joke = getRawJokes(format = Format.YAML, idRange = IdRange(22)) +println(joke) ``` - ```yaml error: false category: "Programming" @@ -143,8 +128,8 @@ flags: id: 22 safe: true lang: "en" -``` +``` - View more [examples](https://github.com/ethauvin/jokeapi/blob/master/src/test/kotlin/net/thauvin/erik/jokeapi/GetRawJokesTest.kt#L46)... ## Extending @@ -154,37 +139,15 @@ A generic `apiCall()` function is available to access other [JokeAPI endpoints]( For example to retrieve the French [language code](https://v2.jokeapi.dev/#langcode-endpoint): ```kotlin -val response = JokeApi.apiCall( +val lang = JokeApi.apiCall( endPoint = "langcode", path = "french", params = mapOf(Parameter.FORMAT to Format.YAML.value) ) -if (response.statusCode == 200) { - println(response.data) -} +println(lang) ``` - ```yaml error: false code: "fr" ``` - View more [examples](https://github.com/ethauvin/jokeapi/blob/master/src/test/kotlin/net/thauvin/erik/jokeapi/ApiCallTest.kt#L48)... - -## Contributing - -If you want to contribute to this project, all you have to do is clone the GitHub -repository: - -```console -git clone git@github.com:ethauvin/jokeapi.git -``` - -Then use [bld](https://rife2.com/bld) to build: - -```console -cd jokeapi -./bld compile -``` - -The project has an [IntelliJ IDEA](https://www.jetbrains.com/idea/) project structure. You can just open it after all -the dependencies were downloaded and peruse the code. diff --git a/bin/main/net/thauvin/erik/jokeapi/JokeApi.kt b/bin/main/net/thauvin/erik/jokeapi/JokeApi.kt deleted file mode 100644 index b4df9aa..0000000 --- a/bin/main/net/thauvin/erik/jokeapi/JokeApi.kt +++ /dev/null @@ -1,318 +0,0 @@ -/* - * JokeApi.kt - * - * Copyright 2022-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.jokeapi - -import net.thauvin.erik.jokeapi.exceptions.HttpErrorException -import net.thauvin.erik.jokeapi.exceptions.JokeException -import net.thauvin.erik.jokeapi.models.* -import net.thauvin.erik.urlencoder.UrlEncoderUtil -import org.json.JSONObject -import java.util.logging.Logger -import java.util.stream.Collectors - -/** - * Implements the [Sv443's JokeAPI](https://jokeapi.dev/). - */ -object JokeApi { - private const val API_URL = "https://v2.jokeapi.dev/" - - @JvmStatic - val logger: Logger by lazy { Logger.getLogger(JokeApi::class.java.simpleName) } - - /** - * Makes a direct API call. - * - * Sse the [JokeAPI Documentation](https://jokeapi.dev/#endpoints) for more details. - */ - @JvmStatic - @JvmOverloads - @Throws(HttpErrorException::class) - fun apiCall( - endPoint: String, - path: String = "", - params: Map = emptyMap(), - auth: String = "" - ): String { - val urlBuilder = StringBuilder("$API_URL$endPoint") - - if (path.isNotEmpty()) { - if (!urlBuilder.endsWith(('/'))) { - urlBuilder.append('/') - } - urlBuilder.append(path) - } - - if (params.isNotEmpty()) { - urlBuilder.append('?') - val it = params.iterator() - while (it.hasNext()) { - val param = it.next() - urlBuilder.append(param.key) - if (param.value.isNotEmpty()) { - urlBuilder.append("=").append(UrlEncoderUtil.encode(param.value)) - } - if (it.hasNext()) { - urlBuilder.append("&") - } - } - } - return fetchUrl(urlBuilder.toString(), auth) - } - - /** - * Returns one or more jokes using a [configuration][JokeConfig]. - * - * See the [JokeAPI Documentation](https://jokeapi.dev/#joke-endpoint) for more details. - */ - @JvmStatic - @Throws(HttpErrorException::class) - fun getRawJokes(config: JokeConfig): String { - return rawJokes( - categories = config.categories, - lang = config.language, - blacklistFlags = config.flags, - type = config.type, - format = config.format, - contains = config.contains, - idRange = config.idRange, - amount = config.amount, - safe = config.safe, - auth = config.auth - ) - } - - /** - * Retrieve a [Joke] instance using a [configuration][JokeConfig]. - * - * Sse the [JokeAPI Documentation](https://jokeapi.dev/#joke-endpoint) for more details. - */ - @JvmStatic - @JvmOverloads - @Throws(HttpErrorException::class, JokeException::class) - fun joke(config: JokeConfig = JokeConfig.Builder().build()): Joke { - return joke( - categories = config.categories, - lang = config.language, - blacklistFlags = config.flags, - type = config.type, - contains = config.contains, - idRange = config.idRange, - safe = config.safe, - auth = config.auth, - splitNewLine = config.splitNewLine - ) - } - - /** - * Returns an array of [Joke] instances using a [configuration][JokeConfig]. - * - * Sse the [JokeAPI Documentation](https://jokeapi.dev/#joke-endpoint) for more details. - */ - @JvmStatic - @Throws(HttpErrorException::class, JokeException::class) - fun jokes(config: JokeConfig): Array { - return jokes( - categories = config.categories, - lang = config.language, - blacklistFlags = config.flags, - type = config.type, - contains = config.contains, - idRange = config.idRange, - amount = config.amount, - safe = config.safe, - auth = config.auth, - splitNewLine = config.splitNewLine - ) - } -} - - -/** - * Returns a [Joke] instance. - * - * Sse the [JokeAPI Documentation](https://jokeapi.dev/#joke-endpoint) for more details. - * - * @param splitNewLine Split newline within [Type.SINGLE] joke. - */ -fun joke( - categories: Set = setOf(Category.ANY), - lang: Language = Language.EN, - blacklistFlags: Set = emptySet(), - type: Type = Type.ALL, - contains: String = "", - idRange: IdRange = IdRange(), - safe: Boolean = false, - auth: String = "", - splitNewLine: Boolean = false -): Joke { - val json = JSONObject( - rawJokes( - categories = categories, - lang = lang, - blacklistFlags = blacklistFlags, - type = type, - contains = contains, - idRange = idRange, - safe = safe, - auth = auth - ) - ) - if (json.getBoolean("error")) { - throw parseError(json) - } else { - return parseJoke(json, splitNewLine) - } -} - -/** - * Returns an array of [Joke] instances. - * - * Sse the [JokeAPI Documentation](https://jokeapi.dev/#joke-endpoint) for more details. - * - * @param amount The required amount of jokes to return. - * @param splitNewLine Split newline within [Type.SINGLE] joke. - */ -fun jokes( - amount: Int, - categories: Set = setOf(Category.ANY), - lang: Language = Language.EN, - blacklistFlags: Set = emptySet(), - type: Type = Type.ALL, - contains: String = "", - idRange: IdRange = IdRange(), - safe: Boolean = false, - auth: String = "", - splitNewLine: Boolean = false -): Array { - val json = JSONObject( - rawJokes( - categories = categories, - lang = lang, - blacklistFlags = blacklistFlags, - type = type, - contains = contains, - idRange = idRange, - amount = amount, - safe = safe, - auth = auth - ) - ) - if (json.getBoolean("error")) { - throw parseError(json) - } else { - return if (json.has("amount")) { - val jokes = json.getJSONArray("jokes") - Array(jokes.length()) { i -> parseJoke(jokes.getJSONObject(i), splitNewLine) } - } else { - arrayOf(parseJoke(json, splitNewLine)) - } - } -} - -/** - * Returns one or more jokes. - * - * Sse the [JokeAPI Documentation](https://jokeapi.dev/#joke-endpoint) for more details. - */ -fun rawJokes( - categories: Set = setOf(Category.ANY), - lang: Language = Language.EN, - blacklistFlags: Set = emptySet(), - type: Type = Type.ALL, - format: Format = Format.JSON, - contains: String = "", - idRange: IdRange = IdRange(), - amount: Int = 1, - safe: Boolean = false, - auth: String = "" -): String { - val params = mutableMapOf() - - // Categories - val path = if (categories.isEmpty() || categories.contains(Category.ANY)) { - Category.ANY.value - } else { - categories.stream().map(Category::value).collect(Collectors.joining(",")) - } - - // Language - if (lang != Language.EN) { - params[Parameter.LANG] = lang.value - } - - // Flags - if (blacklistFlags.isNotEmpty()) { - if (blacklistFlags.contains(Flag.ALL)) { - params[Parameter.FLAGS] = Flag.ALL.value - } else { - params[Parameter.FLAGS] = blacklistFlags.stream().map(Flag::value).collect(Collectors.joining(",")) - } - } - - // Type - if (type != Type.ALL) { - params[Parameter.TYPE] = type.value - } - - // Format - if (format != Format.JSON) { - params[Parameter.FORMAT] = format.value - } - - // Contains - if (contains.isNotBlank()) { - params[Parameter.CONTAINS] = contains - } - - // Range - if (idRange.start >= 0) { - if (idRange.end == -1 || idRange.start == idRange.end) { - params[Parameter.RANGE] = idRange.start.toString() - } else { - require(idRange.end > idRange.start) { "Invalid ID Range: ${idRange.start}, ${idRange.end}" } - params[Parameter.RANGE] = "${idRange.start}-${idRange.end}" - } - } - - // Amount - require(amount > 0) { "Invalid Amount: $amount" } - if (amount > 1) { - params[Parameter.AMOUNT] = amount.toString() - } - - // Safe - if (safe) { - params[Parameter.SAFE] = "" - } - - return JokeApi.apiCall("joke", path, params, auth) -} diff --git a/bin/main/net/thauvin/erik/jokeapi/JokeConfig.kt b/bin/main/net/thauvin/erik/jokeapi/JokeConfig.kt deleted file mode 100644 index 544383c..0000000 --- a/bin/main/net/thauvin/erik/jokeapi/JokeConfig.kt +++ /dev/null @@ -1,96 +0,0 @@ -/* - * JokeConfig.kt - * - * Copyright 2022-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.jokeapi - -import net.thauvin.erik.jokeapi.JokeConfig.Builder -import net.thauvin.erik.jokeapi.models.Category -import net.thauvin.erik.jokeapi.models.Flag -import net.thauvin.erik.jokeapi.models.Format -import net.thauvin.erik.jokeapi.models.IdRange -import net.thauvin.erik.jokeapi.models.Language -import net.thauvin.erik.jokeapi.models.Type - -/** - * Joke Configuration. - * - * Use the [Builder] to create a new configuration. - */ -class JokeConfig private constructor( - val categories: Set, - val language: Language, - val flags: Set, - val type: Type, - val format: Format, - val contains: String, - val idRange: IdRange, - val amount: Int, - val safe: Boolean, - val splitNewLine: Boolean, - val auth: String -) { - /** - * [Builds][build] a new configuration. - * - * Sse the [JokeAPI Documentation](https://jokeapi.dev/#joke-endpoint) for more details. - * - * @param splitNewLine Split newline within [Type.SINGLE] joke. - */ - data class Builder( - var categories: Set = setOf(Category.ANY), - var lang: Language = Language.EN, - var blacklistFlags: Set = emptySet(), - var type: Type = Type.ALL, - var format: Format = Format.JSON, - var contains: String = "", - var idRange: IdRange = IdRange(), - var amount: Int = 1, - var safe: Boolean = false, - var splitNewLine: Boolean = false, - var auth: String = "" - ) { - fun categories(categories: Set) = apply { this.categories = categories } - fun lang(language: Language) = apply { lang = language } - fun blacklistFlags(flags: Set) = apply { blacklistFlags = flags } - fun type(type: Type) = apply { this.type = type } - fun format(format: Format) = apply { this.format = format } - fun contains(search: String) = apply { contains = search } - fun idRange(idRange: IdRange) = apply { this.idRange = idRange } - fun amount(amount: Int) = apply { this.amount = amount } - fun safe(safe: Boolean) = apply { this.safe = safe } - fun splitNewLine(splitNewLine: Boolean) = apply { this.splitNewLine = splitNewLine } - fun auth(auth: String) = apply { this.auth = auth } - - fun build() = JokeConfig( - categories, lang, blacklistFlags, type, format, contains, idRange, amount, safe, splitNewLine, auth - ) - } -} diff --git a/bin/main/net/thauvin/erik/jokeapi/JokeUtil.kt b/bin/main/net/thauvin/erik/jokeapi/JokeUtil.kt deleted file mode 100644 index 9d838f8..0000000 --- a/bin/main/net/thauvin/erik/jokeapi/JokeUtil.kt +++ /dev/null @@ -1,173 +0,0 @@ -/* - * JokeUtil.kt - * - * Copyright 2022-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. - */ - -@file:JvmName("JokeUtil") - -package net.thauvin.erik.jokeapi - -import net.thauvin.erik.jokeapi.exceptions.HttpErrorException -import net.thauvin.erik.jokeapi.exceptions.JokeException -import net.thauvin.erik.jokeapi.models.Category -import net.thauvin.erik.jokeapi.models.Flag -import net.thauvin.erik.jokeapi.models.Joke -import net.thauvin.erik.jokeapi.models.Language -import net.thauvin.erik.jokeapi.models.Parameter -import net.thauvin.erik.jokeapi.models.Type -import org.json.JSONObject -import java.io.IOException -import java.net.HttpURLConnection -import java.net.URL -import java.util.logging.Level - -internal fun fetchUrl(url: String, auth: String = ""): String { - if (JokeApi.logger.isLoggable(Level.FINE)) { - JokeApi.logger.fine(url) - } - - val connection = URL(url).openConnection() as HttpURLConnection - connection.setRequestProperty( - "User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/109.0" - ) - if (auth.isNotEmpty()) { - connection.setRequestProperty("Authentication", auth) - } - - if (connection.responseCode in 200..399) { - val body = connection.inputStream.bufferedReader().use { it.readText() } - if (JokeApi.logger.isLoggable(Level.FINE)) { - JokeApi.logger.fine(body) - } - return body - } else { - throw httpError(connection.responseCode) - } -} - -private fun httpError(responseCode: Int): HttpErrorException { - val httpException: HttpErrorException - when (responseCode) { - 400 -> httpException = HttpErrorException( - responseCode, "Bad Request", IOException( - "The request you have sent to JokeAPI is formatted incorrectly and cannot be processed." - ) - ) - - 403 -> httpException = HttpErrorException( - responseCode, "Forbidden", IOException( - "You have been added to the blacklist due to malicious behavior and are not allowed" - + " to send requests to JokeAPI anymore." - ) - ) - - 404 -> httpException = HttpErrorException( - responseCode, "Not Found", IOException("The URL you have requested couldn't be found.") - ) - - 413 -> httpException = HttpErrorException( - responseCode, "URI Too Long", IOException("The URL exceeds the maximum length of 250 characters.") - ) - - 414 -> httpException = HttpErrorException( - responseCode, - "Payload Too Large", - IOException("The payload data sent to the server exceeds the maximum size of 5120 bytes.") - ) - - 429 -> httpException = HttpErrorException( - responseCode, "Too Many Requests", IOException( - "You have exceeded the limit of 120 requests per minute and have to wait a bit" - + " until you are allowed to send requests again." - ) - ) - - 500 -> httpException = HttpErrorException( - responseCode, "Internal Server Error", IOException( - "There was a general internal error within JokeAPI. You can get more info from" - + " the properties in the response text." - ) - ) - - 523 -> httpException = HttpErrorException( - responseCode, "Origin Unreachable", IOException( - "The server is temporarily offline due to maintenance or a dynamic IP update." - + " Please be patient in this case." - ) - ) - - else -> httpException = HttpErrorException(responseCode, "Unknown HTTP Error") - } - - return httpException -} - -internal fun parseError(json: JSONObject): JokeException { - val causedBy = json.getJSONArray("causedBy") - val causes = List(causedBy.length()) { i -> causedBy.getString(i) } - return JokeException( - internalError = json.getBoolean("internalError"), - code = json.getInt("code"), - message = json.getString("message"), - causedBy = causes, - additionalInfo = json.getString("additionalInfo"), - timestamp = json.getLong("timestamp") - ) -} - -internal fun parseJoke(json: JSONObject, splitNewLine: Boolean): Joke { - val jokes = mutableListOf() - if (json.has("setup")) { - jokes.add(json.getString("setup")) - jokes.add(json.getString(("delivery"))) - } else { - if (splitNewLine) { - jokes.addAll(json.getString("joke").split("\n")) - } else { - jokes.add(json.getString("joke")) - } - } - val enabledFlags = mutableSetOf() - val jsonFlags = json.getJSONObject("flags") - Flag.values().filter { it != Flag.ALL }.forEach { - if (jsonFlags.has(it.value) && jsonFlags.getBoolean(it.value)) { - enabledFlags.add(it) - } - } - return Joke( - category = Category.valueOf(json.getString("category").uppercase()), - type = Type.valueOf(json.getString(Parameter.TYPE).uppercase()), - joke = jokes, - flags = enabledFlags, - safe = json.getBoolean("safe"), - id = json.getInt("id"), - lang = Language.valueOf(json.getString(Parameter.LANG).uppercase()) - ) -} - diff --git a/bin/main/net/thauvin/erik/jokeapi/exceptions/HttpErrorException.kt b/bin/main/net/thauvin/erik/jokeapi/exceptions/HttpErrorException.kt deleted file mode 100644 index cd17ca8..0000000 --- a/bin/main/net/thauvin/erik/jokeapi/exceptions/HttpErrorException.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * HttpErrorException.kt - * - * Copyright 2022-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.jokeapi.exceptions - -import java.io.IOException - -/** - * Signals that a server error has occurred. - * - * Sse the [JokeAPI Documentation](https://jokeapi.dev/#status-codes) for more details. - */ -class HttpErrorException @JvmOverloads constructor( - val statusCode: Int, - message: String, - cause: Throwable? = null -) : IOException(message, cause) { - companion object { - private const val serialVersionUID = 1L - } -} diff --git a/bin/main/net/thauvin/erik/jokeapi/exceptions/JokeException.kt b/bin/main/net/thauvin/erik/jokeapi/exceptions/JokeException.kt deleted file mode 100644 index 919216e..0000000 --- a/bin/main/net/thauvin/erik/jokeapi/exceptions/JokeException.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * JokeException.kt - * - * Copyright 2022-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.jokeapi.exceptions - -/** - * Signals that an error has occurred. - * - * Sse the [JokeAPI Documentation](https://jokeapi.dev/#errors) for more details. - */ -class JokeException @JvmOverloads constructor( - val internalError: Boolean, - val code: Int, - message: String, - val causedBy: List, - val additionalInfo: String, - val timestamp: Long, - cause: Throwable? = null -) : RuntimeException(message, cause) { - companion object { - private const val serialVersionUID = 1L - } - - fun debug(): String { - return "JokeException(message=$message, internalError=$internalError, code=$code," + - " causedBy=$causedBy, additionalInfo='$additionalInfo', timestamp=$timestamp)" - } -} diff --git a/bin/main/net/thauvin/erik/jokeapi/models/Category.kt b/bin/main/net/thauvin/erik/jokeapi/models/Category.kt deleted file mode 100644 index 4951d4a..0000000 --- a/bin/main/net/thauvin/erik/jokeapi/models/Category.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Category.kt - * - * Copyright 2022-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.jokeapi.models - -/** - * The supported [categories](https://jokeapi.dev/#categories), use [ANY] for all. - */ -enum class Category(val value: String) { - ANY("Any"), - CHRISTMAS("Christmas"), - DARK("Dark"), - MISC("Misc"), - PROGRAMMING("Programming"), - PUN("Pun"), - SPOOKY("Spooky") -} diff --git a/bin/main/net/thauvin/erik/jokeapi/models/Flag.kt b/bin/main/net/thauvin/erik/jokeapi/models/Flag.kt deleted file mode 100644 index af92e90..0000000 --- a/bin/main/net/thauvin/erik/jokeapi/models/Flag.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Flag.kt - * - * Copyright 2022-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.jokeapi.models - -/** - * The supported [blacklist flags](https://jokeapi.dev/#flags-param), use [ALL] to prevent all. - */ -enum class Flag(val value: String) { - EXPLICIT("explicit"), - NSFW("nsfw"), - POLITICAL("political"), - RACIST("racist"), - RELIGIOUS("religious"), - SEXIST("sexist"), - ALL("${NSFW.value},${RELIGIOUS.value},${POLITICAL.value},${RACIST.value},${SEXIST.value},${EXPLICIT.value}") -} diff --git a/bin/main/net/thauvin/erik/jokeapi/models/Format.kt b/bin/main/net/thauvin/erik/jokeapi/models/Format.kt deleted file mode 100644 index 2678a21..0000000 --- a/bin/main/net/thauvin/erik/jokeapi/models/Format.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Format.kt - * - * Copyright 2022-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.jokeapi.models - -/** - * The supported response [formats](https://jokeapi.dev/#format-param). - */ -enum class Format(val value: String) { - JSON("json"), - - /** Plain Text */ - TXT("txt"), - XML("xml"), - YAML("yaml") -} diff --git a/bin/main/net/thauvin/erik/jokeapi/models/IdRange.kt b/bin/main/net/thauvin/erik/jokeapi/models/IdRange.kt deleted file mode 100644 index 62a6eb6..0000000 --- a/bin/main/net/thauvin/erik/jokeapi/models/IdRange.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * IdRange.kt - * - * Copyright 2022-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.jokeapi.models - -/** - * Specifies a joke [ID or range of IDs](https://jokeapi.dev/#idrange-param). - */ -data class IdRange(val start: Int = -1, val end: Int = -1) diff --git a/bin/main/net/thauvin/erik/jokeapi/models/Joke.kt b/bin/main/net/thauvin/erik/jokeapi/models/Joke.kt deleted file mode 100644 index 0309977..0000000 --- a/bin/main/net/thauvin/erik/jokeapi/models/Joke.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Joke.kt - * - * Copyright 2022-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.jokeapi.models - -/** - * Stores a joke's data. - */ -data class Joke( - val category: Category, - val type: Type, - val joke: List, - val flags: Set, - val id: Int, - val safe: Boolean, - val lang: Language -) diff --git a/bin/main/net/thauvin/erik/jokeapi/models/Language.kt b/bin/main/net/thauvin/erik/jokeapi/models/Language.kt deleted file mode 100644 index 10c00fb..0000000 --- a/bin/main/net/thauvin/erik/jokeapi/models/Language.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Language.kt - * - * Copyright 2022-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.jokeapi.models - -/** - * The supported [languages](https://jokeapi.dev/#lang). - */ -enum class Language(val value: String) { - /** Czech */ - CS("cs"), - - /** German */ - DE("de"), - - /** English */ - EN("en"), - - /** Spanish */ - ES("es"), - - /** French */ - FR("fr"), - - /** Portuguese */ - PT("pt") -} diff --git a/bin/main/net/thauvin/erik/jokeapi/models/Parameter.kt b/bin/main/net/thauvin/erik/jokeapi/models/Parameter.kt deleted file mode 100644 index b9e1106..0000000 --- a/bin/main/net/thauvin/erik/jokeapi/models/Parameter.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Parameter.kt - * - * Copyright 2022-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.jokeapi.models - -/** - * The available [URL Parameters](https://jokeapi.dev/#url-parameters). - */ -object Parameter { - const val AMOUNT = "amount" - const val CONTAINS = "contains" - const val FLAGS = "blacklistFlags" - const val FORMAT = "format" - const val RANGE = "idRange" - const val LANG = "lang" - const val SAFE = "safe-mode" - const val TYPE = "type" - - const val BLACKLIST_FLAGS = FLAGS - const val ID_RANGE = RANGE - const val SAFE_MODE = SAFE - const val SEARCH = CONTAINS -} diff --git a/bin/main/net/thauvin/erik/jokeapi/models/Type.kt b/bin/main/net/thauvin/erik/jokeapi/models/Type.kt deleted file mode 100644 index 59126b4..0000000 --- a/bin/main/net/thauvin/erik/jokeapi/models/Type.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Type.kt - * - * Copyright 2022-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.jokeapi.models - -/** - * The supported [types](https://jokeapi.dev/#type-param), use [ALL] for all. - */ -enum class Type(val value: String) { - SINGLE("single"), - TWOPART("twopart"), - ALL("${SINGLE.value},${TWOPART.value}") -} diff --git a/bin/test/net/thauvin/erik/jokeapi/ApiCallTest.kt b/bin/test/net/thauvin/erik/jokeapi/ApiCallTest.kt deleted file mode 100644 index d9f9b30..0000000 --- a/bin/test/net/thauvin/erik/jokeapi/ApiCallTest.kt +++ /dev/null @@ -1,87 +0,0 @@ -/* - * ApiCallTest.kt - * - * Copyright 2022-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.jokeapi - -import assertk.assertThat -import assertk.assertions.isGreaterThan -import assertk.assertions.startsWith -import net.thauvin.erik.jokeapi.JokeApi.apiCall -import net.thauvin.erik.jokeapi.models.Format -import net.thauvin.erik.jokeapi.models.Language -import net.thauvin.erik.jokeapi.models.Parameter -import org.json.JSONObject -import org.junit.jupiter.api.Assertions.assertFalse -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertAll -import org.junit.jupiter.api.extension.ExtendWith -import kotlin.test.assertContains - -@ExtendWith(BeforeAllTests::class) -internal class ApiCallTest { - @Test - fun `Get Flags`() { - // See https://v2.jokeapi.dev/#flags-endpoint - val response = apiCall(endPoint = "flags") - val json = JSONObject(response) - assertAll("Validate JSON", - { assertFalse(json.getBoolean("error"), "apiCall(flags).error") }, - { assertThat(json.getJSONArray("flags").length(), "apiCall(flags).flags").isGreaterThan(0) }, - { assertThat(json.getLong("timestamp"), "apiCall(flags).timestamp").isGreaterThan(0) }) - } - - @Test - fun `Get Language Code`() { - // See https://v2.jokeapi.dev/#langcode-endpoint - val lang = apiCall( - endPoint = "langcode", path = "french", - params = mapOf(Parameter.FORMAT to Format.YAML.value) - ) - assertContains(lang, "code: \"fr\"", false, "apiCall(langcode, french, yaml)") - } - - @Test - fun `Get Ping Response`() { - // See https://v2.jokeapi.dev/#ping-endpoint - val ping = apiCall(endPoint = "ping", params = mapOf(Parameter.FORMAT to Format.TXT.value)) - assertThat(ping, "apiCall(ping, txt)").startsWith("Pong!") - } - - @Test - fun `Get Supported Language`() { - // See https://v2.jokeapi.dev/languages - val lang = apiCall( - endPoint = "languages", - params = mapOf(Parameter.FORMAT to Format.XML.value, Parameter.LANG to Language.FR.value) - ) - assertThat(lang).startsWith("") - } -} diff --git a/bin/test/net/thauvin/erik/jokeapi/BeforeAllTests.kt b/bin/test/net/thauvin/erik/jokeapi/BeforeAllTests.kt deleted file mode 100644 index de9d48a..0000000 --- a/bin/test/net/thauvin/erik/jokeapi/BeforeAllTests.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * BeforeAllTests.kt - * - * Copyright 2022-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.jokeapi - -import org.junit.jupiter.api.extension.BeforeAllCallback -import org.junit.jupiter.api.extension.ExtensionContext -import java.util.logging.ConsoleHandler -import java.util.logging.Level - -class BeforeAllTests : BeforeAllCallback { - override fun beforeAll(context: ExtensionContext?) { - with(JokeApi.logger) { - addHandler(ConsoleHandler().apply { level = Level.FINE }) - level = Level.FINE - } - } -} - diff --git a/bin/test/net/thauvin/erik/jokeapi/ExceptionsTest.kt b/bin/test/net/thauvin/erik/jokeapi/ExceptionsTest.kt deleted file mode 100644 index adacf75..0000000 --- a/bin/test/net/thauvin/erik/jokeapi/ExceptionsTest.kt +++ /dev/null @@ -1,90 +0,0 @@ -/* - * ExceptionsTest.kt - * - * Copyright 2022-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.jokeapi - -import assertk.all -import assertk.assertThat -import assertk.assertions.index -import assertk.assertions.isEqualTo -import assertk.assertions.isFalse -import assertk.assertions.isGreaterThan -import assertk.assertions.isNotEmpty -import assertk.assertions.isNotNull -import assertk.assertions.isNull -import assertk.assertions.prop -import assertk.assertions.size -import assertk.assertions.startsWith -import net.thauvin.erik.jokeapi.JokeApi.logger -import net.thauvin.erik.jokeapi.exceptions.HttpErrorException -import net.thauvin.erik.jokeapi.exceptions.JokeException -import net.thauvin.erik.jokeapi.models.Category -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.api.extension.ExtendWith -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.ValueSource - -@ExtendWith(BeforeAllTests::class) -internal class ExceptionsTest { - @Test - fun `Validate Joke Exception`() { - val e = assertThrows { - joke(categories = setOf(Category.CHRISTMAS), contains = "foo") - } - logger.fine(e.debug()) - assertThat(e, "joke(${Category.CHRISTMAS},foo)").all { - prop(JokeException::code).isEqualTo(106) - prop(JokeException::internalError).isFalse() - prop(JokeException::message).isEqualTo("No matching joke found") - prop(JokeException::causedBy).size().isEqualTo(1) - prop(JokeException::causedBy).index(0).startsWith("No jokes") - prop(JokeException::additionalInfo).isNotEmpty() - prop(JokeException::timestamp).isGreaterThan(0) - } - } - - @ParameterizedTest - @ValueSource(ints = [400, 404, 403, 413, 414, 429, 500, 523, 666]) - fun `Validate HTTP Exceptions`(code: Int) { - val e = assertThrows { - fetchUrl("https://httpstat.us/$code") - } - assertThat(e, "fetchUrl($code)").all { - prop(HttpErrorException::statusCode).isEqualTo(code) - prop(HttpErrorException::message).isNotNull().isNotEmpty() - if (code < 600) - prop(HttpErrorException::cause).isNotNull().assertThat(Throwable::message).isNotNull() - else - prop(HttpErrorException::cause).isNull() - } - } -} diff --git a/bin/test/net/thauvin/erik/jokeapi/GetJokeTest.kt b/bin/test/net/thauvin/erik/jokeapi/GetJokeTest.kt deleted file mode 100644 index a2b06db..0000000 --- a/bin/test/net/thauvin/erik/jokeapi/GetJokeTest.kt +++ /dev/null @@ -1,211 +0,0 @@ -/* - * GetJokeTest.kt - * - * Copyright 2022-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.jokeapi - -import assertk.all -import assertk.assertThat -import assertk.assertions.any -import assertk.assertions.contains -import assertk.assertions.containsNone -import assertk.assertions.each -import assertk.assertions.isBetween -import assertk.assertions.isEmpty -import assertk.assertions.isEqualTo -import assertk.assertions.isGreaterThan -import assertk.assertions.isGreaterThanOrEqualTo -import assertk.assertions.isIn -import assertk.assertions.isNotEmpty -import assertk.assertions.isNotNull -import assertk.assertions.isTrue -import assertk.assertions.prop -import assertk.assertions.size -import net.thauvin.erik.jokeapi.JokeApi.logger -import net.thauvin.erik.jokeapi.exceptions.JokeException -import net.thauvin.erik.jokeapi.models.Category -import net.thauvin.erik.jokeapi.models.Flag -import net.thauvin.erik.jokeapi.models.IdRange -import net.thauvin.erik.jokeapi.models.Joke -import net.thauvin.erik.jokeapi.models.Language -import net.thauvin.erik.jokeapi.models.Type -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.api.extension.ExtendWith - -@ExtendWith(BeforeAllTests::class) -internal class GetJokeTest { - @Test - fun `Get Joke`() { - val joke = joke() - logger.fine(joke.toString()) - assertThat(joke, "joke()").all { - prop(Joke::joke).isNotEmpty() - prop(Joke::type).isIn(Type.SINGLE, Type.TWOPART) - prop(Joke::id).isGreaterThanOrEqualTo(0) - prop(Joke::lang).isEqualTo(Language.EN) - } - } - - @Test - fun `Get Joke without Blacklist Flags`() { - val joke = joke(blacklistFlags = setOf(Flag.ALL)) - assertThat(joke::flags).isEmpty() - } - - @Test - fun `Get Joke without any Blacklist Flags`() { - val allFlags = Flag.values().filter { it != Flag.ALL }.toSet() - val joke = joke(blacklistFlags = allFlags) - assertThat(joke::flags).isEmpty() - } - - @Test - fun `Get Joke with ID`() { - val id = 172 - val joke = joke(idRange = IdRange(id)) - logger.fine(joke.toString()) - assertThat(joke, "joke($id)").all { - prop(Joke::flags).all { - contains(Flag.EXPLICIT) - contains(Flag.NSFW) - } - prop(Joke::id).isEqualTo(172) - prop(Joke::category).isEqualTo(Category.PUN) - } - } - - @Test - fun `Get Joke with ID Range`() { - val idRange = IdRange(1, 100) - val joke = joke(idRange = idRange) - logger.fine(joke.toString()) - assertThat(joke::id).isBetween(idRange.start, idRange.end) - } - - @Test - fun `Get Joke with invalid ID Range`() { - val idRange = IdRange(100, 1) - val e = assertThrows { joke(idRange = idRange, lang = Language.DE) } - assertThat(e::message).isNotNull().contains("100, 1") - } - - @Test - fun `Get Joke with max ID Range`() { - val idRange = IdRange(1, 30000) - val e = assertThrows { joke(idRange = idRange) } - assertThat(e, "joke{${idRange})").all { - prop(JokeException::additionalInfo).contains("ID range") - } - } - - @Test - fun `Get Joke with two Categories`() { - val joke = joke(categories = setOf(Category.PROGRAMMING, Category.MISC)) - logger.fine(joke.toString()) - assertThat(joke.category, "joke(${Category.PROGRAMMING},${Category.MISC})").isIn( - Category.PROGRAMMING, - Category.MISC - ) - } - - @Test - fun `Get Joke with each Categories`() { - Category.values().filter { it != Category.ANY }.forEach { - val joke = joke(categories = setOf(it)) - logger.fine(joke.toString()) - assertThat(joke::category, "joke($it)").prop(Category::value).isEqualTo(it.value) - } - } - - @Test - fun `Get Joke with each Languages`() { - Language.values().forEach { - val joke = joke(lang = it) - logger.fine(joke.toString()) - assertThat(joke::lang, "joke($it)").prop(Language::value).isEqualTo(it.value) - } - } - - @Test - fun `Get Joke with Split Newline`() { - val joke = joke( - categories = setOf(Category.DARK), type = Type.SINGLE, idRange = IdRange(178), splitNewLine = true - ) - logger.fine(joke.toString()) - assertThat(joke::joke, "joke(splitNewLine=true)").all { - size().isEqualTo(2) - each { - containsNone("\n") - } - } - } - - @Test - fun `Get Safe Joke`() { - val joke = joke(safe = true) - logger.fine(joke.toString()) - assertThat(joke, "joke(safe)").all { - prop(Joke::safe).isTrue() - } - } - - @Test - fun `Get Single Joke`() { - val joke = joke(type = Type.SINGLE) - logger.fine(joke.toString()) - assertThat(joke::type).assertThat(Type.SINGLE) - } - - @Test - fun `Get Two-Parts Joke`() { - val joke = joke(type = Type.TWOPART) - logger.fine(joke.toString()) - assertThat(joke, "joke(${Type.TWOPART})").all { - prop(Joke::type).isEqualTo(Type.TWOPART) - prop(Joke::joke).size().isGreaterThan(1) - } - } - - @Test - fun `Get Joke using Search`() { - val id = 265 - val search = "his wife" - val joke = - joke(contains = search, categories = setOf(Category.PROGRAMMING), idRange = IdRange(id), safe = true) - logger.fine(joke.toString()) - assertThat(joke, "joke($search)").all { - prop(Joke::id).isEqualTo(id) - prop(Joke::joke).any { - it.contains(search) - } - } - } -} diff --git a/bin/test/net/thauvin/erik/jokeapi/GetJokesTest.kt b/bin/test/net/thauvin/erik/jokeapi/GetJokesTest.kt deleted file mode 100644 index 1ab8b60..0000000 --- a/bin/test/net/thauvin/erik/jokeapi/GetJokesTest.kt +++ /dev/null @@ -1,84 +0,0 @@ -/* - * GetJokesTest.kt - * - * Copyright 2022-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.jokeapi - -import assertk.all -import assertk.assertThat -import assertk.assertions.contains -import assertk.assertions.each -import assertk.assertions.index -import assertk.assertions.isEqualTo -import assertk.assertions.isGreaterThanOrEqualTo -import assertk.assertions.isNotNull -import assertk.assertions.isTrue -import assertk.assertions.prop -import assertk.assertions.size -import net.thauvin.erik.jokeapi.models.Joke -import net.thauvin.erik.jokeapi.models.Language -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.api.extension.ExtendWith - -@ExtendWith(BeforeAllTests::class) -internal class GetJokesTest { - @Test - fun `Get Multiple Jokes`() { - val amount = 2 - val jokes = jokes(amount = amount, safe = true, lang = Language.FR) - assertThat(jokes, "jokes").all { - size().isEqualTo(amount) - each { - it.prop(Joke::id).isGreaterThanOrEqualTo(0) - it.prop(Joke::safe).isTrue() - it.prop(Joke::lang).isEqualTo(Language.FR) - } - } - } - - @Test - fun `Get Jokes with Invalid Amount`() { - val e = assertThrows { jokes(amount = -1) } - assertThat(e::message).isNotNull().contains("-1") - } - - @Test - fun `Get One Joke as Multiple`() { - val jokes = jokes(amount = 1, safe = true) - assertThat(jokes, "jokes").all { - size().isEqualTo(1) - index(0).all { - prop(Joke::id).isGreaterThanOrEqualTo(0) - prop(Joke::safe).isTrue() - } - } - } -} diff --git a/bin/test/net/thauvin/erik/jokeapi/GetRawJokesTest.kt b/bin/test/net/thauvin/erik/jokeapi/GetRawJokesTest.kt deleted file mode 100644 index 7bcf1c6..0000000 --- a/bin/test/net/thauvin/erik/jokeapi/GetRawJokesTest.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* - * GetRawJokesTest.kt - * - * Copyright 2022-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.jokeapi - -import assertk.all -import assertk.assertThat -import assertk.assertions.doesNotContain -import assertk.assertions.isNotEmpty -import assertk.assertions.startsWith -import net.thauvin.erik.jokeapi.models.Format -import net.thauvin.erik.jokeapi.models.IdRange -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import kotlin.test.assertContains - -@ExtendWith(BeforeAllTests::class) -internal class GetRawJokesTest { - @Test - fun `Get Raw Joke with TXT`() { - val response = rawJokes(format = Format.TXT) - assertThat(response, "rawJoke(txt)").all { - isNotEmpty() - doesNotContain("Error") - } - } - - @Test - fun `Get Raw Joke with XML`() { - val response = rawJokes(format = Format.XML) - assertThat(response, "rawJoke(xml)").startsWith("\n\n false") - } - - @Test - fun `Get Raw Joke with YAML`() { - val response = rawJokes(format = Format.YAML) - assertThat(response, "rawJoke(yaml)").startsWith("error: false") - } - - @Test - fun `Get Raw Jokes`() { - val response = rawJokes(amount = 2) - assertContains(response, "\"amount\": 2", false, "rawJoke(2)") - } - - @Test - fun `Get Raw Invalid Jokes`() { - val response = rawJokes(contains = "foo", safe = true, amount = 2, idRange = IdRange(160, 161)) - assertContains(response, "\"error\": true", false, "getRawJokes(foo)") - } -} diff --git a/bin/test/net/thauvin/erik/jokeapi/JokeConfigTest.kt b/bin/test/net/thauvin/erik/jokeapi/JokeConfigTest.kt deleted file mode 100644 index 92de2e0..0000000 --- a/bin/test/net/thauvin/erik/jokeapi/JokeConfigTest.kt +++ /dev/null @@ -1,182 +0,0 @@ -/* - * JokeConfigTest.kt - * - * Copyright 2022-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.jokeapi - -import assertk.all -import assertk.assertThat -import assertk.assertions.each -import assertk.assertions.isBetween -import assertk.assertions.isEmpty -import assertk.assertions.isEqualTo -import assertk.assertions.isGreaterThanOrEqualTo -import assertk.assertions.isTrue -import assertk.assertions.prop -import assertk.assertions.size -import net.thauvin.erik.jokeapi.JokeApi.joke -import net.thauvin.erik.jokeapi.JokeApi.jokes -import net.thauvin.erik.jokeapi.JokeApi.getRawJokes -import net.thauvin.erik.jokeapi.JokeApi.logger -import net.thauvin.erik.jokeapi.models.Category -import net.thauvin.erik.jokeapi.models.Flag -import net.thauvin.erik.jokeapi.models.Format -import net.thauvin.erik.jokeapi.models.IdRange -import net.thauvin.erik.jokeapi.models.Joke -import net.thauvin.erik.jokeapi.models.Language -import net.thauvin.erik.jokeapi.models.Type -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import kotlin.test.assertContains - -@ExtendWith(BeforeAllTests::class) -internal class JokeConfigTest { - @Test - fun `Get Joke with Default Builder`() { - val joke = joke() - assertThat(joke, "joke").all { - prop(Joke::id).isGreaterThanOrEqualTo(0) - prop(Joke::lang).isEqualTo(Language.EN) - } - } - - @Test - fun `Get Joke with Builder`() { - val id = 266 - val config = JokeConfig.Builder().apply { - categories(setOf(Category.PROGRAMMING)) - lang(Language.EN) - blacklistFlags(setOf(Flag.ALL)) - type(Type.TWOPART) - idRange(IdRange(id - 2, id + 2)) - safe(true) - }.build() - val joke = joke(config) - logger.fine(joke.toString()) - assertThat(joke, "config").all { - prop(Joke::type).isEqualTo(Type.TWOPART) - prop(Joke::category).isEqualTo(Category.PROGRAMMING) - prop(Joke::joke).size().isEqualTo(2) - prop(Joke::lang).isEqualTo(Language.EN) - prop(Joke::flags).isEmpty() - prop(Joke::id).isBetween(id - 2, id + 2) - } - } - - @Test - fun `Get joke with Builder and Split Newline`() { - val id = 5 - val config = JokeConfig.Builder().apply { - categories(setOf(Category.PROGRAMMING)) - idRange(IdRange(id)) - splitNewLine(true) - }.build() - val joke = joke(config) - logger.fine(joke.toString()) - assertThat(joke, "config").all { - prop(Joke::id).isEqualTo(id) - prop(Joke::joke).size().isEqualTo(2) - } - } - - @Test - fun `Get Raw Joke with Builder`() { - val config = JokeConfig.Builder().apply { - categories(setOf(Category.PROGRAMMING)) - format(Format.TXT) - contains("bar") - amount(2) - safe(true) - }.build() - val joke = getRawJokes(config) - assertContains(joke, "----------------------------------------------", false, "config.amount(2)") - } - - @Test - fun `Get Multiple Jokes with Builder`() { - val amount = 2 - val config = JokeConfig.Builder().apply { - amount(amount) - safe(true) - lang(Language.FR) - }.build() - val jokes = jokes(config) - assertThat(jokes, "jokes").all { - size().isEqualTo(amount) - each { - it.prop(Joke::id).isGreaterThanOrEqualTo(0) - it.prop(Joke::safe).isTrue() - it.prop(Joke::flags).isEmpty() - it.prop(Joke::lang).isEqualTo(Language.FR) - } - } - } - - @Test - fun `Validate Config`() { - val categories = setOf(Category.ANY) - val language = Language.CS - val flags = setOf(Flag.POLITICAL, Flag.RELIGIOUS) - val type = Type.TWOPART - val format = Format.XML - val search = "foo" - val idRange = IdRange(1, 20) - val amount = 10 - val safe = true - val splitNewLine = true - val auth = "token" - val config = JokeConfig.Builder().apply { - categories(categories) - lang(language) - blacklistFlags(flags) - type(type) - format(format) - contains(search) - idRange(idRange) - amount(amount) - safe(safe) - splitNewLine(splitNewLine) - auth(auth) - }.build() - assertThat(config, "config").all { - prop(JokeConfig::categories).isEqualTo(categories) - prop(JokeConfig::language).isEqualTo(language) - prop(JokeConfig::flags).isEqualTo(flags) - prop(JokeConfig::type).isEqualTo(type) - prop(JokeConfig::format).isEqualTo(format) - prop(JokeConfig::contains).isEqualTo(search) - prop(JokeConfig::idRange).isEqualTo(idRange) - prop(JokeConfig::amount).isEqualTo(amount) - prop(JokeConfig::safe).isEqualTo(safe) - prop(JokeConfig::splitNewLine).isEqualTo(splitNewLine) - prop(JokeConfig::auth).isEqualTo(auth) - } - } -} diff --git a/bin/test/net/thauvin/erik/jokeapi/JokeUtilTest.kt b/bin/test/net/thauvin/erik/jokeapi/JokeUtilTest.kt deleted file mode 100644 index 8f8d936..0000000 --- a/bin/test/net/thauvin/erik/jokeapi/JokeUtilTest.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * UtilTest.kt - * - * Copyright 2022-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.jokeapi - -import assertk.assertThat -import assertk.assertions.contains -import org.json.JSONException -import org.json.JSONObject -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows -import org.junit.jupiter.api.extension.ExtendWith - -@ExtendWith(BeforeAllTests::class) -internal class JokeUtilTest { - @Test - fun `Invalid JSON Error`() { - assertThrows { parseError(JSONObject("{}")) } - } - - @Test - fun `Invalid JSON Joke`() { - assertThrows { parseJoke(JSONObject("{}"), false) } - } - - @Test - fun `Validate Authentication Header`() { - val token = "AUTH-TOKEN" - val body = fetchUrl("https://postman-echo.com/get", token) - assertThat(body, "body").contains("\"authentication\": \"$token\"") - } -} diff --git a/bld b/bld deleted file mode 100755 index bfff52f..0000000 --- a/bld +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env sh -java -jar "$(dirname "$0")/lib/bld/bld-wrapper.jar" "$0" --build net.thauvin.erik.JokeApiBuild "$@" diff --git a/bld.bat b/bld.bat deleted file mode 100644 index 9f7473c..0000000 --- a/bld.bat +++ /dev/null @@ -1,4 +0,0 @@ -@echo off -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -java -jar "%DIRNAME%/lib/bld/bld-wrapper.jar" "%0" --build net.thauvin.erik.JokeApiBuild %* diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..fbea59b --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,185 @@ +import org.gradle.api.tasks.testing.logging.TestExceptionFormat +import org.gradle.api.tasks.testing.logging.TestLogEvent +import org.jetbrains.dokka.gradle.DokkaTask +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + id("com.github.ben-manes.versions") version "0.48.0" + id("io.gitlab.arturbosch.detekt") version "1.23.1" + id("java") + id("maven-publish") + id("org.jetbrains.dokka") version "1.9.0" + id("org.jetbrains.kotlinx.kover") version "0.7.3" + id("org.sonarqube") version "4.3.1.3277" + id("signing") + kotlin("jvm") version "1.9.10" +} + +description = "Retrieve jokes from Sv443's JokeAPI" +group = "net.thauvin.erik" +version = "0.9.0" + +val deployDir = "deploy" +val gitHub = "ethauvin/$name" +val mavenUrl = "https://github.com/$gitHub" +val publicationName = "mavenJava" + +repositories { + mavenCentral() + maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") } +} + +dependencies { + implementation(platform(kotlin("bom"))) + + implementation("net.thauvin.erik.urlencoder:urlencoder-lib:1.4.0") + implementation("org.json:json:20230618") + + testImplementation(kotlin("test")) + testImplementation("org.junit.jupiter:junit-jupiter:5.10.0") + testImplementation("com.willowtreeapps.assertk:assertk-jvm:0.27.0") +} + +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + withSourcesJar() +} + +koverReport { + defaults { + xml { + onCheck = true + } + html { + onCheck = true + } + } +} + +sonarqube { + properties { + property("sonar.projectKey", "ethauvin_$name") + property("sonar.organization", "ethauvin-github") + property("sonar.host.url", "https://sonarcloud.io") + property("sonar.sourceEncoding", "UTF-8") + property("sonar.coverage.jacoco.xmlReportPaths", "${project.buildDir}/reports/kover/report.xml") + } +} + +val javadocJar by tasks.creating(Jar::class) { + dependsOn(tasks.dokkaJavadoc) + from(tasks.dokkaJavadoc) + archiveClassifier.set("javadoc") +} + +tasks { + test { + useJUnitPlatform() + } + + withType().configureEach { + kotlinOptions.jvmTarget = java.targetCompatibility.toString() + } + + withType { + testLogging { + exceptionFormat = TestExceptionFormat.FULL + events = setOf(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) + } + } + + withType().configureEach { + this.jvmTarget = java.targetCompatibility.toString() + } + + withType().configureEach { + this.jvmTarget = java.targetCompatibility.toString() + } + + withType { + destination = file("$projectDir/pom.xml") + } + + clean { + doLast { + project.delete(fileTree(deployDir)) + } + } + + withType().configureEach { + dokkaSourceSets { + named("main") { + moduleName.set("Joke API") + } + } + } + + val copyToDeploy by registering(Copy::class) { + from(configurations.runtimeClasspath) { + exclude("annotations-*.jar") + } + from(jar) + into(deployDir) + } + + register("deploy") { + description = "Copies all needed files to the $deployDir directory." + group = PublishingPlugin.PUBLISH_TASK_GROUP + dependsOn(clean, build, jar) + outputs.dir(deployDir) + inputs.files(copyToDeploy) + mustRunAfter(clean) + } +} + +publishing { + publications { + create(publicationName) { + from(components["java"]) + artifact(javadocJar) + pom { + name.set(project.name) + description.set(project.description) + url.set(mavenUrl) + licenses { + license { + name.set("BSD 3-Clause") + url.set("https://opensource.org/licenses/BSD-3-Clause") + } + } + developers { + developer { + id.set("ethauvin") + name.set("Erik C. Thauvin") + email.set("erik@thauvin.net") + url.set("https://erik.thauvin.net/") + } + } + scm { + connection.set("scm:git:https://github.com/$gitHub.git") + developerConnection.set("scm:git:git@github.com:$gitHub.git") + url.set(mavenUrl) + } + issueManagement { + system.set("GitHub") + url.set("$mavenUrl/issues") + } + } + } + } + repositories { + maven { + name = "ossrh" + url = if (project.version.toString().contains("SNAPSHOT")) + uri("https://oss.sonatype.org/content/repositories/snapshots/") else + uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/") + credentials(PasswordCredentials::class) + } + } +} + +signing { + useGpgCmd() + sign(publishing.publications[publicationName]) +} diff --git a/detekt-baseline.xml b/detekt-baseline.xml index 1a99819..a53ce7c 100644 --- a/detekt-baseline.xml +++ b/detekt-baseline.xml @@ -1,12 +1,12 @@ - + - + - LongParameterList:JokeApi.kt$( amount: Int, categories: Set<Category> = setOf(Category.ANY), lang: Language = Language.EN, blacklistFlags: Set<Flag> = emptySet(), type: Type = Type.ALL, contains: String = "", idRange: IdRange = IdRange(), safe: Boolean = false, auth: String = "", splitNewLine: Boolean = false ) - LongParameterList:JokeApi.kt$( categories: Set<Category> = setOf(Category.ANY), lang: Language = Language.EN, blacklistFlags: Set<Flag> = emptySet(), type: Type = Type.ALL, contains: String = "", idRange: IdRange = IdRange(), safe: Boolean = false, auth: String = "", splitNewLine: Boolean = false ) - LongParameterList:JokeApi.kt$( categories: Set<Category> = setOf(Category.ANY), lang: Language = Language.EN, blacklistFlags: Set<Flag> = emptySet(), type: Type = Type.ALL, format: Format = Format.JSON, contains: String = "", idRange: IdRange = IdRange(), amount: Int = 1, safe: Boolean = false, auth: String = "" ) - LongParameterList:JokeConfig.kt$JokeConfig$( val categories: Set<Category>, val language: Language, val flags: Set<Flag>, val type: Type, val format: Format, val contains: String, val idRange: IdRange, val amount: Int, val safe: Boolean, val splitNewLine: Boolean, val auth: String ) - LongParameterList:JokeException.kt$JokeException$( val internalError: Boolean, val code: Int, message: String, val causedBy: List<String>, val additionalInfo: String, val timestamp: Long, cause: Throwable? = null ) + LongParameterList:JokeApi.kt$( amount: Int, categories: Set<Category> = setOf(Category.ANY), lang: Language = Language.EN, blacklistFlags: Set<Flag> = emptySet(), type: Type = Type.ALL, contains: String = "", idRange: IdRange = IdRange(), safe: Boolean = false, auth: String = "", splitNewLine: Boolean = false ) + LongParameterList:JokeApi.kt$( categories: Set<Category> = setOf(Category.ANY), lang: Language = Language.EN, blacklistFlags: Set<Flag> = emptySet(), type: Type = Type.ALL, contains: String = "", idRange: IdRange = IdRange(), safe: Boolean = false, auth: String = "", splitNewLine: Boolean = false ) + LongParameterList:JokeApi.kt$( categories: Set<Category> = setOf(Category.ANY), lang: Language = Language.EN, blacklistFlags: Set<Flag> = emptySet(), type: Type = Type.ALL, format: Format = Format.JSON, contains: String = "", idRange: IdRange = IdRange(), amount: Int = 1, safe: Boolean = false, auth: String = "" ) + LongParameterList:JokeConfig.kt$JokeConfig$( val categories: Set<Category>, val language: Language, val flags: Set<Flag>, val type: Type, val format: Format, val contains: String, val idRange: IdRange, val amount: Int, val safe: Boolean, val splitNewLine: Boolean, val auth: String ) + LongParameterList:JokeException.kt$JokeException$( val internalError: Boolean, val code: Int, message: String, val causedBy: List<String>, val additionalInfo: String, val timestamp: Long, cause: Throwable? = null ) MagicNumber:JokeUtil.kt$200 MagicNumber:JokeUtil.kt$399 MagicNumber:JokeUtil.kt$400 @@ -18,15 +18,6 @@ MagicNumber:JokeUtil.kt$500 MagicNumber:JokeUtil.kt$523 TooManyFunctions:JokeConfig.kt$JokeConfig$Builder - WildcardImport:ExceptionsTest.kt$import assertk.assertions.* - WildcardImport:GetJokeTest.kt$import assertk.assertions.* - WildcardImport:GetJokeTest.kt$import net.thauvin.erik.jokeapi.models.* - WildcardImport:GetJokesTest.kt$import assertk.assertions.* - WildcardImport:GetRawJokesTest.kt$import assertk.assertions.* WildcardImport:JokeApi.kt$import net.thauvin.erik.jokeapi.models.* - WildcardImport:JokeConfig.kt$import net.thauvin.erik.jokeapi.models.* - WildcardImport:JokeConfigTest.kt$import assertk.assertions.* - WildcardImport:JokeConfigTest.kt$import net.thauvin.erik.jokeapi.models.* - WildcardImport:JokeUtil.kt$import net.thauvin.erik.jokeapi.models.* diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..7fc6f1f --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7f93135 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ac72c34 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..0adc8e1 --- /dev/null +++ b/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..93e3f59 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/lib/bld/bld-wrapper.jar b/lib/bld/bld-wrapper.jar deleted file mode 100644 index 1eb86cf..0000000 Binary files a/lib/bld/bld-wrapper.jar and /dev/null differ diff --git a/lib/bld/bld-wrapper.properties b/lib/bld/bld-wrapper.properties deleted file mode 100644 index fc9463a..0000000 --- a/lib/bld/bld-wrapper.properties +++ /dev/null @@ -1,10 +0,0 @@ -bld.downloadExtensionJavadoc=false -bld.downloadExtensionSources=true -bld.downloadLocation= -bld.extension-detekt=com.uwyn.rife2:bld-detekt:0.9.10-SNAPSHOT -bld.extension-dokka=com.uwyn.rife2:bld-dokka:1.0.4-SNAPSHOT -bld.extension-jacoco=com.uwyn.rife2:bld-jacoco-report:0.9.10-SNAPSHOT -bld.extension-kotlin=com.uwyn.rife2:bld-kotlin:1.1.0-SNAPSHOT -bld.repositories=MAVEN_LOCAL,MAVEN_CENTRAL,RIFE2_SNAPSHOTS,RIFE2_RELEASES -bld.sourceDirectories= -bld.version=2.2.1 diff --git a/pom.xml b/pom.xml index e480d48..31a633b 100644 --- a/pom.xml +++ b/pom.xml @@ -1,12 +1,16 @@ - + + + + + + 4.0.0 net.thauvin.erik jokeapi - 1.0.1-SNAPSHOT + 0.9.0 jokeapi - Retrieve jokes from Sv443's JokeAPI + Retrieve jokes from Sv443's JokeAPI https://github.com/ethauvin/jokeapi @@ -14,26 +18,6 @@ https://opensource.org/licenses/BSD-3-Clause - - - org.jetbrains.kotlin - kotlin-stdlib - 2.1.20 - compile - - - org.json - json - 20250107 - compile - - - net.thauvin.erik.urlencoder - urlencoder-lib-jvm - 1.6.0 - compile - - ethauvin @@ -47,4 +31,39 @@ scm:git:git@github.com:ethauvin/jokeapi.git https://github.com/ethauvin/jokeapi + + GitHub + https://github.com/ethauvin/jokeapi/issues + + + + + org.jetbrains.kotlin + kotlin-bom + 1.9.10 + pom + import + + + + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + 1.9.10 + compile + + + net.thauvin.erik.urlencoder + urlencoder-lib-jvm + 1.4.0 + runtime + + + org.json + json + 20230618 + runtime + + diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..dc0111b --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,3 @@ + +rootProject.name = "jokeapi" + diff --git a/sonar-project.properties b/sonar-project.properties deleted file mode 100644 index 5fe9b51..0000000 --- a/sonar-project.properties +++ /dev/null @@ -1,7 +0,0 @@ -sonar.organization=ethauvin-github -sonar.projectKey=ethauvin_jokeapi -sonar.coverage.jacoco.xmlReportPaths=build/reports/jacoco/test/jacocoTestReport.xml -sonar.sources=src/main/kotlin/ -sonar.tests=src/test/kotlin/ -sonar.java.binaries=build/main,build/test -sonar.java.libraries=lib/compile/*.jar diff --git a/src/bld/java/net/thauvin/erik/JokeApiBuild.java b/src/bld/java/net/thauvin/erik/JokeApiBuild.java deleted file mode 100644 index 62b9d9a..0000000 --- a/src/bld/java/net/thauvin/erik/JokeApiBuild.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * JokeApiBuild.java - * - * Copyright 2022-2025 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; - -import rife.bld.BuildCommand; -import rife.bld.Project; -import rife.bld.extension.CompileKotlinOperation; -import rife.bld.extension.DetektOperation; -import rife.bld.extension.DokkaOperation; -import rife.bld.extension.JacocoReportOperation; -import rife.bld.extension.dokka.LoggingLevel; -import rife.bld.extension.dokka.OutputFormat; -import rife.bld.extension.kotlin.CompileOptions; -import rife.bld.operations.exceptions.ExitStatusException; -import rife.bld.publish.PomBuilder; -import rife.bld.publish.PublishDeveloper; -import rife.bld.publish.PublishLicense; -import rife.bld.publish.PublishScm; -import rife.tools.exceptions.FileUtilsErrorException; - -import java.io.File; -import java.io.IOException; -import java.util.List; -import java.util.logging.ConsoleHandler; -import java.util.logging.Level; -import java.util.logging.Logger; - -import static rife.bld.dependencies.Repository.*; -import static rife.bld.dependencies.Scope.compile; -import static rife.bld.dependencies.Scope.test; - -public class JokeApiBuild extends Project { - final File srcMainKotlin = new File(srcMainDirectory(), "kotlin"); - - public JokeApiBuild() { - pkg = "net.thauvin.erik"; - name = "jokeapi"; - version = version(1, 0, 1, "SNAPSHOT"); - - javaRelease = 11; - downloadSources = true; - autoDownloadPurge = true; - repositories = List.of(MAVEN_LOCAL, MAVEN_CENTRAL); - - final var kotlin = version(2, 1, 20); - scope(compile) - .include(dependency("org.jetbrains.kotlin", "kotlin-stdlib", kotlin)) - .include(dependency("org.json", "json", "20250107")) - .include(dependency("net.thauvin.erik.urlencoder", "urlencoder-lib-jvm", version(1, 6, 0))); - scope(test) - .include(dependency("org.jetbrains.kotlin", "kotlin-test-junit5", kotlin)) - .include(dependency("org.junit.jupiter", "junit-jupiter", version(5, 12, 1))) - .include(dependency("org.junit.platform", "junit-platform-console-standalone", version(1, 12, 1))) - .include(dependency("org.junit.platform", "junit-platform-launcher", version(1, 12, 1))) - .include(dependency("com.willowtreeapps.assertk", "assertk-jvm", version(0, 28, 1))); - - publishOperation() - .repository(version.isSnapshot() ? repository(SONATYPE_SNAPSHOTS_LEGACY.location()) - .withCredentials(property("sonatype.user"), property("sonatype.password")) - : repository(SONATYPE_RELEASES_LEGACY.location()) - .withCredentials(property("sonatype.user"), property("sonatype.password"))) - .repository(repository("github")) - .info() - .groupId(pkg) - .artifactId(name) - .description("Retrieve jokes from Sv443's JokeAPI") - .url("https://github.com/ethauvin/" + name) - .developer( - new PublishDeveloper() - .id("ethauvin") - .name("Erik C. Thauvin") - .email("erik@thauvin.net") - .url("https://erik.thauvin.net/") - ) - .license( - new PublishLicense() - .name("BSD 3-Clause") - .url("https://opensource.org/licenses/BSD-3-Clause") - ) - .scm( - new PublishScm() - .connection("scm:git:https://github.com/ethauvin/" + name + ".git") - .developerConnection("scm:git:git@github.com:ethauvin/" + name + ".git") - .url("https://github.com/ethauvin/" + name) - ) - .signKey(property("sign.key")) - .signPassphrase(property("sign.passphrase")); - - jarSourcesOperation().sourceDirectories(srcMainKotlin); - } - - public static void main(String[] args) { - // Enable detailed logging for the extensions - var level = Level.ALL; - var logger = Logger.getLogger("rife.bld.extension"); - var consoleHandler = new ConsoleHandler(); - - consoleHandler.setLevel(level); - logger.addHandler(consoleHandler); - logger.setLevel(level); - logger.setUseParentHandlers(false); - - new JokeApiBuild().start(args); - } - - @BuildCommand(summary = "Compiles the Kotlin project") - @Override - public void compile() throws Exception { - new CompileKotlinOperation() - .fromProject(this) - .compileOptions(new CompileOptions().verbose(true)) - .execute(); - } - - @BuildCommand(summary = "Checks source with Detekt") - public void detekt() throws ExitStatusException, IOException, InterruptedException { - new DetektOperation() - .fromProject(this) - .execute(); - } - - @BuildCommand(value = "detekt-baseline", summary = "Creates the Detekt baseline") - public void detektBaseline() throws ExitStatusException, IOException, InterruptedException { - new DetektOperation() - .fromProject(this) - .baseline("detekt-baseline.xml") - .createBaseline(true) - .execute(); - } - - @BuildCommand(summary = "Generates JaCoCo Reports") - public void jacoco() throws Exception { - new JacocoReportOperation() - .fromProject(this) - .sourceFiles(srcMainKotlin) - .execute(); - } - - @Override - public void javadoc() throws ExitStatusException, IOException, InterruptedException { - new DokkaOperation() - .fromProject(this) - .loggingLevel(LoggingLevel.INFO) - .moduleName("JokeApi") - .moduleVersion(version.toString()) - .outputDir(new File(buildDirectory(), "javadoc")) - .outputFormat(OutputFormat.JAVADOC) - .execute(); - } - - @Override - public void publish() throws Exception { - super.publish(); - pomRoot(); - } - - @Override - public void publishLocal() throws Exception { - super.publishLocal(); - pomRoot(); - } - - @BuildCommand(value = "pom-root", summary = "Generates the POM file in the root directory") - public void pomRoot() throws FileUtilsErrorException { - PomBuilder.generateInto(publishOperation().fromProject(this).info(), dependencies(), - new File(workDirectory, "pom.xml")); - } -} diff --git a/src/main/kotlin/net/thauvin/erik/jokeapi/JokeApi.kt b/src/main/kotlin/net/thauvin/erik/jokeapi/JokeApi.kt index 474aa27..b4df9aa 100644 --- a/src/main/kotlin/net/thauvin/erik/jokeapi/JokeApi.kt +++ b/src/main/kotlin/net/thauvin/erik/jokeapi/JokeApi.kt @@ -1,7 +1,7 @@ /* * JokeApi.kt * - * Copyright 2022-2025 Erik C. Thauvin (erik@thauvin.net) + * Copyright 2022-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: @@ -45,16 +45,13 @@ import java.util.stream.Collectors object JokeApi { private const val API_URL = "https://v2.jokeapi.dev/" - /** - * The logger instance. - */ @JvmStatic val logger: Logger by lazy { Logger.getLogger(JokeApi::class.java.simpleName) } /** * Makes a direct API call. * - * See the [JokeAPI Documentation](https://jokeapi.dev/#endpoints) for more details. + * Sse the [JokeAPI Documentation](https://jokeapi.dev/#endpoints) for more details. */ @JvmStatic @JvmOverloads @@ -64,7 +61,7 @@ object JokeApi { path: String = "", params: Map = emptyMap(), auth: String = "" - ): JokeResponse { + ): String { val urlBuilder = StringBuilder("$API_URL$endPoint") if (path.isNotEmpty()) { @@ -98,11 +95,11 @@ object JokeApi { */ @JvmStatic @Throws(HttpErrorException::class) - fun getRawJokes(config: JokeConfig): JokeResponse { + fun getRawJokes(config: JokeConfig): String { return rawJokes( categories = config.categories, - lang = config.lang, - blacklistFlags = config.blacklistFlags, + lang = config.language, + blacklistFlags = config.flags, type = config.type, format = config.format, contains = config.contains, @@ -124,8 +121,8 @@ object JokeApi { fun joke(config: JokeConfig = JokeConfig.Builder().build()): Joke { return joke( categories = config.categories, - lang = config.lang, - blacklistFlags = config.blacklistFlags, + lang = config.language, + blacklistFlags = config.flags, type = config.type, contains = config.contains, idRange = config.idRange, @@ -145,8 +142,8 @@ object JokeApi { fun jokes(config: JokeConfig): Array { return jokes( categories = config.categories, - lang = config.lang, - blacklistFlags = config.blacklistFlags, + lang = config.language, + blacklistFlags = config.flags, type = config.type, contains = config.contains, idRange = config.idRange, @@ -164,32 +161,6 @@ object JokeApi { * * Sse the [JokeAPI Documentation](https://jokeapi.dev/#joke-endpoint) for more details. * - * @param categories JokeAPI has a first, coarse filter that just categorizes the jokes depending on what the joke is - * about or who the joke is directed at. A joke about programming will be in the [Category.PROGRAMMING] category, dark - * humor will be in the [Category.DARK] category and so on. If you want jokes from all categories, you can instead use - * [Category.ANY], which will make JokeAPI randomly choose a category. - * @param lang There are two types of languages; system languages and joke languages. Both are separate from each other. - * All system messages like errors can have a certain system language, while jokes can only have a joke language. - * It is possible, that system languages don't yet exist for your language while jokes already do. - * If no suitable system language is found, JokeAPI will default to English. - * @param blacklistFlags Blacklist Flags (or just "Flags") are a more fine layer of filtering. Multiple flags can be - * set on each joke, and they tell you something about the offensiveness of each joke. - * @param type Each joke comes with one of two types: [Type.SINGLE] or [Type.TWOPART]. If a joke is of type - * [Type.TWOPART], it has a setup string and a delivery string, which are both part of the joke. They are separated - * because you might want to present the users the delivery after a timeout or in a different section of the UI. - * A joke of type [Type.SINGLE] only has a single string, which is the entire joke. - * @param contains If the search string filter is used, only jokes that contain the specified string will be returned. - * @param idRange If this filter is used, you will only get jokes that are within the provided range of IDs. - * You don't necessarily need to provide an ID range though, a single ID will work just fine as well. - * For example, an ID range of 0-9 will mean you will only get one of the first 10 jokes, while an ID range of 5 will - * mean you will only get the 6th joke. - * @param safe Safe Mode. If enabled, JokeAPI will try its best to serve only jokes that are considered safe for - * everyone. Unsafe jokes are those who can be considered explicit in any way, either through the used language, its - * references or its [flags][blacklistFlags]. Jokes from the category [Category.DARK] are also generally marked as - * unsafe. - * @param auth JokeAPI has a way of whitelisting certain clients. This is achieved through an API token. - * At the moment, you will only receive one of these tokens temporarily if something breaks or if you are a business - * and need more than 120 requests per minute. * @param splitNewLine Split newline within [Type.SINGLE] joke. */ fun joke( @@ -213,7 +184,7 @@ fun joke( idRange = idRange, safe = safe, auth = auth - ).data + ) ) if (json.getBoolean("error")) { throw parseError(json) @@ -227,35 +198,7 @@ fun joke( * * Sse the [JokeAPI Documentation](https://jokeapi.dev/#joke-endpoint) for more details. * - * @param amount This filter allows you to set a certain amount of jokes to receive in a single call. Setting the - * filter to an invalid number will result in the API defaulting to serving a single joke. Setting it to a number - * larger than 10 will make JokeAPI default to the maximum (10). - * @param categories JokeAPI has a first, coarse filter that just categorizes the jokes depending on what the joke is - * about or who the joke is directed at. A joke about programming will be in the [Category.PROGRAMMING] category, dark - * humor will be in the [Category.DARK] category and so on. If you want jokes from all categories, you can instead use - * [Category.ANY], which will make JokeAPI randomly choose a category. - * @param lang There are two types of languages; system languages and joke languages. Both are separate from each other. - * All system messages like errors can have a certain system language, while jokes can only have a joke language. - * It is possible, that system languages don't yet exist for your language while jokes already do. - * If no suitable system language is found, JokeAPI will default to English. - * @param blacklistFlags Blacklist Flags (or just "Flags") are a more fine layer of filtering. Multiple flags can be - * set on each joke, and they tell you something about the offensiveness of each joke. - * @param type Each joke comes with one of two types: [Type.SINGLE] or [Type.TWOPART]. If a joke is of type - * [Type.TWOPART], it has a setup string and a delivery string, which are both part of the joke. They are separated - * because you might want to present the users the delivery after a timeout or in a different section of the UI. - * A joke of type [Type.SINGLE] only has a single string, which is the entire joke. - * @param contains If the search string filter is used, only jokes that contain the specified string will be returned. - * @param idRange If this filter is used, you will only get jokes that are within the provided range of IDs. - * You don't necessarily need to provide an ID range though, a single ID will work just fine as well. - * For example, an ID range of 0-9 will mean you will only get one of the first 10 jokes, while an ID range of 5 will - * mean you will only get the 6th joke. - * @param safe Safe Mode. If enabled, JokeAPI will try its best to serve only jokes that are considered safe for - * everyone. Unsafe jokes are those who can be considered explicit in any way, either through the used language, its - * references or its [flags][blacklistFlags]. Jokes from the category [Category.DARK] are also generally marked as - * unsafe. - * @param auth JokeAPI has a way of whitelisting certain clients. This is achieved through an API token. - * At the moment, you will only receive one of these tokens temporarily if something breaks or if you are a business - * and need more than 120 requests per minute. + * @param amount The required amount of jokes to return. * @param splitNewLine Split newline within [Type.SINGLE] joke. */ fun jokes( @@ -281,7 +224,7 @@ fun jokes( amount = amount, safe = safe, auth = auth - ).data + ) ) if (json.getBoolean("error")) { throw parseError(json) @@ -298,42 +241,8 @@ fun jokes( /** * Returns one or more jokes. * - * See the [JokeAPI Documentation](https://jokeapi.dev/#joke-endpoint) for more details. - * - * @param categories JokeAPI has a first, coarse filter that just categorizes the jokes depending on what the joke is - * about or who the joke is directed at. A joke about programming will be in the [Category.PROGRAMMING] category, dark - * humor will be in the [Category.DARK] category and so on. If you want jokes from all categories, you can instead use - * [Category.ANY], which will make JokeAPI randomly choose a category. - * @param lang There are two types of languages; system languages and joke languages. Both are separate from each other. - * All system messages like errors can have a certain system language, while jokes can only have a joke language. - * It is possible, that system languages don't yet exist for your language while jokes already do. - * If no suitable system language is found, JokeAPI will default to English. - * @param blacklistFlags Blacklist Flags (or just "Flags") are a more fine layer of filtering. Multiple flags can be - * set on each joke, and they tell you something about the offensiveness of each joke. - * @param type Each joke comes with one of two types: [Type.SINGLE] or [Type.TWOPART]. If a joke is of type - * [Type.TWOPART], it has a setup string and a delivery string, which are both part of the joke. They are separated - * because you might want to present the users the delivery after a timeout or in a different section of the UI. - * A joke of type [Type.SINGLE] only has a single string, which is the entire joke. - * @param contains If the search string filter is used, only jokes that contain the specified string will be returned. - * @param format Response Formats (or just "Formats") are a way to get your data in a different file format. - * Maybe your environment or language doesn't support JSON natively. In that case, JokeAPI is able to convert the - * JSON-formatted joke to a different format for you. - * @param idRange If this filter is used, you will only get jokes that are within the provided range of IDs. - * You don't necessarily need to provide an ID range though, a single ID will work just fine as well. - * For example, an ID range of 0-9 will mean you will only get one of the first 10 jokes, while an ID range of 5 will - * mean you will only get the 6th joke. - * @param amount This filter allows you to set a certain amount of jokes to receive in a single call. Setting the - * filter to an invalid number will result in the API defaulting to serving a single joke. Setting it to a number - * larger than 10 will make JokeAPI default to the maximum (10). - * @param safe Safe Mode. If enabled, JokeAPI will try its best to serve only jokes that are considered safe for - * everyone. Unsafe jokes are those who can be considered explicit in any way, either through the used language, its - * references or its [flags][blacklistFlags]. Jokes from the category [Category.DARK] are also generally marked as - * unsafe. - * @param auth JokeAPI has a way of whitelisting certain clients. This is achieved through an API token. - * At the moment, you will only receive one of these tokens temporarily if something breaks or if you are a business - * and need more than 120 requests per minute. + * Sse the [JokeAPI Documentation](https://jokeapi.dev/#joke-endpoint) for more details. */ -@Throws(HttpErrorException::class) fun rawJokes( categories: Set = setOf(Category.ANY), lang: Language = Language.EN, @@ -345,7 +254,7 @@ fun rawJokes( amount: Int = 1, safe: Boolean = false, auth: String = "" -): JokeResponse { +): String { val params = mutableMapOf() // Categories diff --git a/src/main/kotlin/net/thauvin/erik/jokeapi/JokeConfig.kt b/src/main/kotlin/net/thauvin/erik/jokeapi/JokeConfig.kt index a4d4901..544383c 100644 --- a/src/main/kotlin/net/thauvin/erik/jokeapi/JokeConfig.kt +++ b/src/main/kotlin/net/thauvin/erik/jokeapi/JokeConfig.kt @@ -1,7 +1,7 @@ /* * JokeConfig.kt * - * Copyright 2022-2025 Erik C. Thauvin (erik@thauvin.net) + * Copyright 2022-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: @@ -32,26 +32,31 @@ package net.thauvin.erik.jokeapi import net.thauvin.erik.jokeapi.JokeConfig.Builder -import net.thauvin.erik.jokeapi.models.* +import net.thauvin.erik.jokeapi.models.Category +import net.thauvin.erik.jokeapi.models.Flag +import net.thauvin.erik.jokeapi.models.Format +import net.thauvin.erik.jokeapi.models.IdRange +import net.thauvin.erik.jokeapi.models.Language +import net.thauvin.erik.jokeapi.models.Type /** * Joke Configuration. * * Use the [Builder] to create a new configuration. */ -class JokeConfig private constructor(builder: Builder) { - val categories = builder.categories - val lang = builder.lang - val blacklistFlags = builder.blacklistFlags - val type = builder.type - val format = builder.format - val contains = builder.contains - val idRange = builder.idRange - val amount = builder.amount - val safe = builder.safe - val splitNewLine = builder.splitNewLine - val auth = builder.auth - +class JokeConfig private constructor( + val categories: Set, + val language: Language, + val flags: Set, + val type: Type, + val format: Format, + val contains: String, + val idRange: IdRange, + val amount: Int, + val safe: Boolean, + val splitNewLine: Boolean, + val auth: String +) { /** * [Builds][build] a new configuration. * @@ -72,86 +77,20 @@ class JokeConfig private constructor(builder: Builder) { var splitNewLine: Boolean = false, var auth: String = "" ) { - /** - * JokeAPI has a first, coarse filter that just categorizes the jokes depending on what the joke is - * about or who the joke is directed at. A joke about programming will be in the [Category.PROGRAMMING] - * category, dark humor will be in the [Category.DARK] category and so on. If you want jokes from all - * categories, you can instead use [Category.ANY], which will make JokeAPI randomly choose a category. - */ - fun categories(categories: Set): Builder = apply { this.categories = categories } + fun categories(categories: Set) = apply { this.categories = categories } + fun lang(language: Language) = apply { lang = language } + fun blacklistFlags(flags: Set) = apply { blacklistFlags = flags } + fun type(type: Type) = apply { this.type = type } + fun format(format: Format) = apply { this.format = format } + fun contains(search: String) = apply { contains = search } + fun idRange(idRange: IdRange) = apply { this.idRange = idRange } + fun amount(amount: Int) = apply { this.amount = amount } + fun safe(safe: Boolean) = apply { this.safe = safe } + fun splitNewLine(splitNewLine: Boolean) = apply { this.splitNewLine = splitNewLine } + fun auth(auth: String) = apply { this.auth = auth } - /** - * There are two types of languages; system languages and joke languages. Both are separate from each other. - * All system messages like errors can have a certain system language, while jokes can only have a joke - * language. It is possible, that system languages don't yet exist for your language while jokes already do. - * If no suitable system language is found, JokeAPI will default to English. - */ - fun lang(language: Language): Builder = apply { lang = language } - - /** - * Blacklist Flags (or just "Flags") are a more fine layer of filtering. Multiple flags can be - * set on each joke, and they tell you something about the offensiveness of each joke. - */ - fun blacklistFlags(flags: Set): Builder = apply { blacklistFlags = flags } - - /** - * Each joke comes with one of two types: [Type.SINGLE] or [Type.TWOPART]. If a joke is of type - * [Type.TWOPART], it has a setup string and a delivery string, which are both part of the joke. They are - * separated because you might want to present the users the delivery after a timeout or in a different section - * of the UI. A joke of type [Type.SINGLE] only has a single string, which is the entire joke. - */ - fun type(type: Type): Builder = apply { this.type = type } - - /** - * Response Formats (or just "Formats") are a way to get your data in a different file format. - * Maybe your environment or language doesn't support JSON natively. In that case, JokeAPI is able to convert - * the JSON-formatted joke to a different format for you. - */ - fun format(format: Format): Builder = apply { this.format = format } - - /** - * If the search string filter is used, only jokes that contain the specified string will be returned. - */ - fun contains(search: String): Builder = apply { contains = search } - - /** - * If this filter is used, you will only get jokes that are within the provided range of IDs. - * You don't necessarily need to provide an ID range though, a single ID will work just fine as well. - * For example, an ID range of 0-9 will mean you will only get one of the first 10 jokes, while an ID range - * of 5 will mean you will only get the 6th joke. - */ - fun idRange(idRange: IdRange): Builder = apply { this.idRange = idRange } - - /** - * This filter allows you to set a certain amount of jokes to receive in a single call. Setting the - * filter to an invalid number will result in the API defaulting to serving a single joke. Setting it to a - * number larger than 10 will make JokeAPI default to the maximum (10). - */ - fun amount(amount: Int): Builder = apply { this.amount = amount } - - /** - * Safe Mode. If enabled, JokeAPI will try its best to serve only jokes that are considered safe for - * everyone. Unsafe jokes are those who can be considered explicit in any way, either through the used language, - * its references or its [flags][blacklistFlags]. Jokes from the category [Category.DARK] are also generally - * marked as unsafe. - */ - fun safe(safe: Boolean): Builder = apply { this.safe = safe } - - /** - * Split newline within [Type.SINGLE] joke. - */ - fun splitNewLine(splitNewLine: Boolean): Builder = apply { this.splitNewLine = splitNewLine } - - /** - * JokeAPI has a way of whitelisting certain clients. This is achieved through an API token. - * At the moment, you will only receive one of these tokens temporarily if something breaks or if you are a - * business and need more than 120 requests per minute. - */ - fun auth(auth: String): Builder = apply { this.auth = auth } - - /** - * Builds a new configuration. - */ - fun build() = JokeConfig(this) + fun build() = JokeConfig( + categories, lang, blacklistFlags, type, format, contains, idRange, amount, safe, splitNewLine, auth + ) } } diff --git a/src/main/kotlin/net/thauvin/erik/jokeapi/JokeUtil.kt b/src/main/kotlin/net/thauvin/erik/jokeapi/JokeUtil.kt index 651844c..9d838f8 100644 --- a/src/main/kotlin/net/thauvin/erik/jokeapi/JokeUtil.kt +++ b/src/main/kotlin/net/thauvin/erik/jokeapi/JokeUtil.kt @@ -1,7 +1,7 @@ /* * JokeUtil.kt * - * Copyright 2022-2025 Erik C. Thauvin (erik@thauvin.net) + * Copyright 2022-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: @@ -35,41 +35,39 @@ package net.thauvin.erik.jokeapi import net.thauvin.erik.jokeapi.exceptions.HttpErrorException import net.thauvin.erik.jokeapi.exceptions.JokeException -import net.thauvin.erik.jokeapi.models.* +import net.thauvin.erik.jokeapi.models.Category +import net.thauvin.erik.jokeapi.models.Flag +import net.thauvin.erik.jokeapi.models.Joke +import net.thauvin.erik.jokeapi.models.Language +import net.thauvin.erik.jokeapi.models.Parameter +import net.thauvin.erik.jokeapi.models.Type import org.json.JSONObject import java.io.IOException import java.net.HttpURLConnection -import java.net.URI +import java.net.URL import java.util.logging.Level -/** - * Fetch a URL. - */ -internal fun fetchUrl(url: String, auth: String = ""): JokeResponse { +internal fun fetchUrl(url: String, auth: String = ""): String { if (JokeApi.logger.isLoggable(Level.FINE)) { JokeApi.logger.fine(url) } - val connection = URI(url).toURL().openConnection() as HttpURLConnection - try { - connection.setRequestProperty( - "User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:130.0) Gecko/20100101 Firefox/130.0" - ) - if (auth.isNotEmpty()) { - connection.setRequestProperty("Authentication", auth) - } + val connection = URL(url).openConnection() as HttpURLConnection + connection.setRequestProperty( + "User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/109.0" + ) + if (auth.isNotEmpty()) { + connection.setRequestProperty("Authentication", auth) + } - val isSuccess = connection.responseCode in 200..399 - val stream = if (isSuccess) connection.inputStream else connection.errorStream - val body = stream.bufferedReader().use { it.readText() } - if (!isSuccess && (body.isBlank() || connection.contentType.contains("text/html"))) { - throw httpError(connection.responseCode) - } else if (JokeApi.logger.isLoggable(Level.FINE)) { + if (connection.responseCode in 200..399) { + val body = connection.inputStream.bufferedReader().use { it.readText() } + if (JokeApi.logger.isLoggable(Level.FINE)) { JokeApi.logger.fine(body) } - return JokeResponse(connection.responseCode, body) - } finally { - connection.disconnect() + return body + } else { + throw httpError(connection.responseCode) } } @@ -130,9 +128,6 @@ private fun httpError(responseCode: Int): HttpErrorException { return httpException } -/** - * Parse Error. - */ internal fun parseError(json: JSONObject): JokeException { val causedBy = json.getJSONArray("causedBy") val causes = List(causedBy.length()) { i -> causedBy.getString(i) } @@ -146,9 +141,6 @@ internal fun parseError(json: JSONObject): JokeException { ) } -/** - * Parse Joke. - */ internal fun parseJoke(json: JSONObject, splitNewLine: Boolean): Joke { val jokes = mutableListOf() if (json.has("setup")) { @@ -163,7 +155,7 @@ internal fun parseJoke(json: JSONObject, splitNewLine: Boolean): Joke { } val enabledFlags = mutableSetOf() val jsonFlags = json.getJSONObject("flags") - Flag.entries.filter { it != Flag.ALL }.forEach { + Flag.values().filter { it != Flag.ALL }.forEach { if (jsonFlags.has(it.value) && jsonFlags.getBoolean(it.value)) { enabledFlags.add(it) } diff --git a/src/main/kotlin/net/thauvin/erik/jokeapi/exceptions/HttpErrorException.kt b/src/main/kotlin/net/thauvin/erik/jokeapi/exceptions/HttpErrorException.kt index f2e8529..cd17ca8 100644 --- a/src/main/kotlin/net/thauvin/erik/jokeapi/exceptions/HttpErrorException.kt +++ b/src/main/kotlin/net/thauvin/erik/jokeapi/exceptions/HttpErrorException.kt @@ -1,7 +1,7 @@ /* * HttpErrorException.kt * - * Copyright 2022-2025 Erik C. Thauvin (erik@thauvin.net) + * Copyright 2022-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: diff --git a/src/main/kotlin/net/thauvin/erik/jokeapi/exceptions/JokeException.kt b/src/main/kotlin/net/thauvin/erik/jokeapi/exceptions/JokeException.kt index ac77344..919216e 100644 --- a/src/main/kotlin/net/thauvin/erik/jokeapi/exceptions/JokeException.kt +++ b/src/main/kotlin/net/thauvin/erik/jokeapi/exceptions/JokeException.kt @@ -1,7 +1,7 @@ /* * JokeException.kt * - * Copyright 2022-2025 Erik C. Thauvin (erik@thauvin.net) + * Copyright 2022-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: @@ -29,8 +29,6 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -@file:Suppress("ConstPropertyName") - package net.thauvin.erik.jokeapi.exceptions /** diff --git a/src/main/kotlin/net/thauvin/erik/jokeapi/models/Category.kt b/src/main/kotlin/net/thauvin/erik/jokeapi/models/Category.kt index cfb008e..4951d4a 100644 --- a/src/main/kotlin/net/thauvin/erik/jokeapi/models/Category.kt +++ b/src/main/kotlin/net/thauvin/erik/jokeapi/models/Category.kt @@ -1,7 +1,7 @@ /* * Category.kt * - * Copyright 2022-2025 Erik C. Thauvin (erik@thauvin.net) + * Copyright 2022-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: diff --git a/src/main/kotlin/net/thauvin/erik/jokeapi/models/Flag.kt b/src/main/kotlin/net/thauvin/erik/jokeapi/models/Flag.kt index be2e21f..af92e90 100644 --- a/src/main/kotlin/net/thauvin/erik/jokeapi/models/Flag.kt +++ b/src/main/kotlin/net/thauvin/erik/jokeapi/models/Flag.kt @@ -1,7 +1,7 @@ /* * Flag.kt * - * Copyright 2022-2025 Erik C. Thauvin (erik@thauvin.net) + * Copyright 2022-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: diff --git a/src/main/kotlin/net/thauvin/erik/jokeapi/models/Format.kt b/src/main/kotlin/net/thauvin/erik/jokeapi/models/Format.kt index 1beb9d3..2678a21 100644 --- a/src/main/kotlin/net/thauvin/erik/jokeapi/models/Format.kt +++ b/src/main/kotlin/net/thauvin/erik/jokeapi/models/Format.kt @@ -1,7 +1,7 @@ /* * Format.kt * - * Copyright 2022-2025 Erik C. Thauvin (erik@thauvin.net) + * Copyright 2022-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: diff --git a/src/main/kotlin/net/thauvin/erik/jokeapi/models/IdRange.kt b/src/main/kotlin/net/thauvin/erik/jokeapi/models/IdRange.kt index 73d45ec..62a6eb6 100644 --- a/src/main/kotlin/net/thauvin/erik/jokeapi/models/IdRange.kt +++ b/src/main/kotlin/net/thauvin/erik/jokeapi/models/IdRange.kt @@ -1,7 +1,7 @@ /* * IdRange.kt * - * Copyright 2022-2025 Erik C. Thauvin (erik@thauvin.net) + * Copyright 2022-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: diff --git a/src/main/kotlin/net/thauvin/erik/jokeapi/models/Joke.kt b/src/main/kotlin/net/thauvin/erik/jokeapi/models/Joke.kt index c2124ae..0309977 100644 --- a/src/main/kotlin/net/thauvin/erik/jokeapi/models/Joke.kt +++ b/src/main/kotlin/net/thauvin/erik/jokeapi/models/Joke.kt @@ -1,7 +1,7 @@ /* * Joke.kt * - * Copyright 2022-2025 Erik C. Thauvin (erik@thauvin.net) + * Copyright 2022-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: diff --git a/src/main/kotlin/net/thauvin/erik/jokeapi/models/JokeResponse.kt b/src/main/kotlin/net/thauvin/erik/jokeapi/models/JokeResponse.kt deleted file mode 100644 index d34f2c3..0000000 --- a/src/main/kotlin/net/thauvin/erik/jokeapi/models/JokeResponse.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * JokeResponse.kt - * - * Copyright 2022-2025 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.jokeapi.models - -/** - * The Joke API response. - * - * @property statusCode The HTTP status code. - * @property data The response body text. - */ -data class JokeResponse(val statusCode: Int, val data: String) diff --git a/src/main/kotlin/net/thauvin/erik/jokeapi/models/Language.kt b/src/main/kotlin/net/thauvin/erik/jokeapi/models/Language.kt index 3ee166e..10c00fb 100644 --- a/src/main/kotlin/net/thauvin/erik/jokeapi/models/Language.kt +++ b/src/main/kotlin/net/thauvin/erik/jokeapi/models/Language.kt @@ -1,7 +1,7 @@ /* * Language.kt * - * Copyright 2022-2025 Erik C. Thauvin (erik@thauvin.net) + * Copyright 2022-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: diff --git a/src/main/kotlin/net/thauvin/erik/jokeapi/models/Parameter.kt b/src/main/kotlin/net/thauvin/erik/jokeapi/models/Parameter.kt index 8962b2a..b9e1106 100644 --- a/src/main/kotlin/net/thauvin/erik/jokeapi/models/Parameter.kt +++ b/src/main/kotlin/net/thauvin/erik/jokeapi/models/Parameter.kt @@ -1,7 +1,7 @@ /* * Parameter.kt * - * Copyright 2022-2025 Erik C. Thauvin (erik@thauvin.net) + * Copyright 2022-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: @@ -34,7 +34,6 @@ package net.thauvin.erik.jokeapi.models /** * The available [URL Parameters](https://jokeapi.dev/#url-parameters). */ -@Suppress("unused") object Parameter { const val AMOUNT = "amount" const val CONTAINS = "contains" diff --git a/src/main/kotlin/net/thauvin/erik/jokeapi/models/Type.kt b/src/main/kotlin/net/thauvin/erik/jokeapi/models/Type.kt index 4fd80fe..59126b4 100644 --- a/src/main/kotlin/net/thauvin/erik/jokeapi/models/Type.kt +++ b/src/main/kotlin/net/thauvin/erik/jokeapi/models/Type.kt @@ -1,7 +1,7 @@ /* * Type.kt * - * Copyright 2022-2025 Erik C. Thauvin (erik@thauvin.net) + * Copyright 2022-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: diff --git a/src/test/kotlin/net/thauvin/erik/jokeapi/ApiCallTest.kt b/src/test/kotlin/net/thauvin/erik/jokeapi/ApiCallTest.kt index 6153825..d9f9b30 100644 --- a/src/test/kotlin/net/thauvin/erik/jokeapi/ApiCallTest.kt +++ b/src/test/kotlin/net/thauvin/erik/jokeapi/ApiCallTest.kt @@ -1,7 +1,7 @@ /* * ApiCallTest.kt * - * Copyright 2022-2025 Erik C. Thauvin (erik@thauvin.net) + * Copyright 2022-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: @@ -32,7 +32,6 @@ package net.thauvin.erik.jokeapi import assertk.assertThat -import assertk.assertions.isEqualTo import assertk.assertions.isGreaterThan import assertk.assertions.startsWith import net.thauvin.erik.jokeapi.JokeApi.apiCall @@ -52,9 +51,8 @@ internal class ApiCallTest { fun `Get Flags`() { // See https://v2.jokeapi.dev/#flags-endpoint val response = apiCall(endPoint = "flags") - val json = JSONObject(response.data) - assertAll( - "Validate JSON", + val json = JSONObject(response) + assertAll("Validate JSON", { assertFalse(json.getBoolean("error"), "apiCall(flags).error") }, { assertThat(json.getJSONArray("flags").length(), "apiCall(flags).flags").isGreaterThan(0) }, { assertThat(json.getLong("timestamp"), "apiCall(flags).timestamp").isGreaterThan(0) }) @@ -67,16 +65,14 @@ internal class ApiCallTest { endPoint = "langcode", path = "french", params = mapOf(Parameter.FORMAT to Format.YAML.value) ) - assertThat(lang.statusCode).isEqualTo(200) - assertContains(lang.data, "code: \"fr\"", false, "apiCall(langcode, french, yaml)") + assertContains(lang, "code: \"fr\"", false, "apiCall(langcode, french, yaml)") } @Test fun `Get Ping Response`() { // See https://v2.jokeapi.dev/#ping-endpoint val ping = apiCall(endPoint = "ping", params = mapOf(Parameter.FORMAT to Format.TXT.value)) - assertThat(ping.statusCode).isEqualTo(200) - assertThat(ping.data).startsWith("Pong!") + assertThat(ping, "apiCall(ping, txt)").startsWith("Pong!") } @Test @@ -86,7 +82,6 @@ internal class ApiCallTest { endPoint = "languages", params = mapOf(Parameter.FORMAT to Format.XML.value, Parameter.LANG to Language.FR.value) ) - assertThat(lang.statusCode).isEqualTo(200) - assertThat(lang.data).startsWith("") + assertThat(lang).startsWith("") } } diff --git a/src/test/kotlin/net/thauvin/erik/jokeapi/BeforeAllTests.kt b/src/test/kotlin/net/thauvin/erik/jokeapi/BeforeAllTests.kt index 50ce4b2..de9d48a 100644 --- a/src/test/kotlin/net/thauvin/erik/jokeapi/BeforeAllTests.kt +++ b/src/test/kotlin/net/thauvin/erik/jokeapi/BeforeAllTests.kt @@ -1,7 +1,7 @@ /* * BeforeAllTests.kt * - * Copyright 2022-2025 Erik C. Thauvin (erik@thauvin.net) + * Copyright 2022-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: diff --git a/src/test/kotlin/net/thauvin/erik/jokeapi/ExceptionsTest.kt b/src/test/kotlin/net/thauvin/erik/jokeapi/ExceptionsTest.kt index eb6837a..adacf75 100644 --- a/src/test/kotlin/net/thauvin/erik/jokeapi/ExceptionsTest.kt +++ b/src/test/kotlin/net/thauvin/erik/jokeapi/ExceptionsTest.kt @@ -1,7 +1,7 @@ /* * ExceptionsTest.kt * - * Copyright 2022-2025 Erik C. Thauvin (erik@thauvin.net) + * Copyright 2022-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: @@ -33,7 +33,16 @@ package net.thauvin.erik.jokeapi import assertk.all import assertk.assertThat -import assertk.assertions.* +import assertk.assertions.index +import assertk.assertions.isEqualTo +import assertk.assertions.isFalse +import assertk.assertions.isGreaterThan +import assertk.assertions.isNotEmpty +import assertk.assertions.isNotNull +import assertk.assertions.isNull +import assertk.assertions.prop +import assertk.assertions.size +import assertk.assertions.startsWith import net.thauvin.erik.jokeapi.JokeApi.logger import net.thauvin.erik.jokeapi.exceptions.HttpErrorException import net.thauvin.erik.jokeapi.exceptions.JokeException @@ -41,6 +50,8 @@ import net.thauvin.erik.jokeapi.models.Category import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource @ExtendWith(BeforeAllTests::class) internal class ExceptionsTest { @@ -61,20 +72,19 @@ internal class ExceptionsTest { } } - @Test - fun `Validate HTTP Exceptions`() { - val locs = ArrayList>() - locs.add(Pair("https://apichallenges.herokuapp.com/secret/note", 401)) - locs.add(Pair("https://apichallenges.herokuapp.com/todo", 404)) - - for ((url, code) in locs) { - val e = assertThrows { - fetchUrl(url) - } - assertThat(e, "fetchUrl($code)").all { - prop(HttpErrorException::statusCode).isEqualTo(code) - prop(HttpErrorException::message).isNotNull().isNotEmpty() - } + @ParameterizedTest + @ValueSource(ints = [400, 404, 403, 413, 414, 429, 500, 523, 666]) + fun `Validate HTTP Exceptions`(code: Int) { + val e = assertThrows { + fetchUrl("https://httpstat.us/$code") + } + assertThat(e, "fetchUrl($code)").all { + prop(HttpErrorException::statusCode).isEqualTo(code) + prop(HttpErrorException::message).isNotNull().isNotEmpty() + if (code < 600) + prop(HttpErrorException::cause).isNotNull().assertThat(Throwable::message).isNotNull() + else + prop(HttpErrorException::cause).isNull() } } } diff --git a/src/test/kotlin/net/thauvin/erik/jokeapi/GetJokeTest.kt b/src/test/kotlin/net/thauvin/erik/jokeapi/GetJokeTest.kt index e5a7d39..a2b06db 100644 --- a/src/test/kotlin/net/thauvin/erik/jokeapi/GetJokeTest.kt +++ b/src/test/kotlin/net/thauvin/erik/jokeapi/GetJokeTest.kt @@ -1,7 +1,7 @@ /* * GetJokeTest.kt * - * Copyright 2022-2025 Erik C. Thauvin (erik@thauvin.net) + * Copyright 2022-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: @@ -33,10 +33,29 @@ package net.thauvin.erik.jokeapi import assertk.all import assertk.assertThat -import assertk.assertions.* +import assertk.assertions.any +import assertk.assertions.contains +import assertk.assertions.containsNone +import assertk.assertions.each +import assertk.assertions.isBetween +import assertk.assertions.isEmpty +import assertk.assertions.isEqualTo +import assertk.assertions.isGreaterThan +import assertk.assertions.isGreaterThanOrEqualTo +import assertk.assertions.isIn +import assertk.assertions.isNotEmpty +import assertk.assertions.isNotNull +import assertk.assertions.isTrue +import assertk.assertions.prop +import assertk.assertions.size import net.thauvin.erik.jokeapi.JokeApi.logger import net.thauvin.erik.jokeapi.exceptions.JokeException -import net.thauvin.erik.jokeapi.models.* +import net.thauvin.erik.jokeapi.models.Category +import net.thauvin.erik.jokeapi.models.Flag +import net.thauvin.erik.jokeapi.models.IdRange +import net.thauvin.erik.jokeapi.models.Joke +import net.thauvin.erik.jokeapi.models.Language +import net.thauvin.erik.jokeapi.models.Type import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.extension.ExtendWith @@ -63,19 +82,22 @@ internal class GetJokeTest { @Test fun `Get Joke without any Blacklist Flags`() { - val allFlags = Flag.entries.filter { it != Flag.ALL }.toSet() + val allFlags = Flag.values().filter { it != Flag.ALL }.toSet() val joke = joke(blacklistFlags = allFlags) assertThat(joke::flags).isEmpty() } @Test fun `Get Joke with ID`() { - val id = 201 + val id = 172 val joke = joke(idRange = IdRange(id)) logger.fine(joke.toString()) assertThat(joke, "joke($id)").all { - prop(Joke::flags).contains(Flag.RELIGIOUS); - prop(Joke::id).isEqualTo(id) + prop(Joke::flags).all { + contains(Flag.EXPLICIT) + contains(Flag.NSFW) + } + prop(Joke::id).isEqualTo(172) prop(Joke::category).isEqualTo(Category.PUN) } } @@ -116,7 +138,7 @@ internal class GetJokeTest { @Test fun `Get Joke with each Categories`() { - Category.entries.filter { it != Category.ANY }.forEach { + Category.values().filter { it != Category.ANY }.forEach { val joke = joke(categories = setOf(it)) logger.fine(joke.toString()) assertThat(joke::category, "joke($it)").prop(Category::value).isEqualTo(it.value) @@ -125,7 +147,7 @@ internal class GetJokeTest { @Test fun `Get Joke with each Languages`() { - Language.entries.forEach { + Language.values().forEach { val joke = joke(lang = it) logger.fine(joke.toString()) assertThat(joke::lang, "joke($it)").prop(Language::value).isEqualTo(it.value) @@ -134,10 +156,12 @@ internal class GetJokeTest { @Test fun `Get Joke with Split Newline`() { - val joke = joke(type = Type.SINGLE, idRange = IdRange(18), splitNewLine = true) + val joke = joke( + categories = setOf(Category.DARK), type = Type.SINGLE, idRange = IdRange(178), splitNewLine = true + ) logger.fine(joke.toString()) assertThat(joke::joke, "joke(splitNewLine=true)").all { - size().isGreaterThanOrEqualTo(2) + size().isEqualTo(2) each { containsNone("\n") } @@ -172,12 +196,13 @@ internal class GetJokeTest { @Test fun `Get Joke using Search`() { - val search = "UDP joke" + val id = 265 + val search = "his wife" val joke = - joke(contains = search, categories = setOf(Category.PROGRAMMING), safe = true) + joke(contains = search, categories = setOf(Category.PROGRAMMING), idRange = IdRange(id), safe = true) logger.fine(joke.toString()) assertThat(joke, "joke($search)").all { - prop(Joke::id).isEqualTo(0) + prop(Joke::id).isEqualTo(id) prop(Joke::joke).any { it.contains(search) } diff --git a/src/test/kotlin/net/thauvin/erik/jokeapi/GetJokesTest.kt b/src/test/kotlin/net/thauvin/erik/jokeapi/GetJokesTest.kt index ea49211..1ab8b60 100644 --- a/src/test/kotlin/net/thauvin/erik/jokeapi/GetJokesTest.kt +++ b/src/test/kotlin/net/thauvin/erik/jokeapi/GetJokesTest.kt @@ -1,7 +1,7 @@ /* * GetJokesTest.kt * - * Copyright 2022-2025 Erik C. Thauvin (erik@thauvin.net) + * Copyright 2022-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: @@ -33,7 +33,15 @@ package net.thauvin.erik.jokeapi import assertk.all import assertk.assertThat -import assertk.assertions.* +import assertk.assertions.contains +import assertk.assertions.each +import assertk.assertions.index +import assertk.assertions.isEqualTo +import assertk.assertions.isGreaterThanOrEqualTo +import assertk.assertions.isNotNull +import assertk.assertions.isTrue +import assertk.assertions.prop +import assertk.assertions.size import net.thauvin.erik.jokeapi.models.Joke import net.thauvin.erik.jokeapi.models.Language import org.junit.jupiter.api.Test diff --git a/src/test/kotlin/net/thauvin/erik/jokeapi/GetRawJokesTest.kt b/src/test/kotlin/net/thauvin/erik/jokeapi/GetRawJokesTest.kt index aa85337..7bcf1c6 100644 --- a/src/test/kotlin/net/thauvin/erik/jokeapi/GetRawJokesTest.kt +++ b/src/test/kotlin/net/thauvin/erik/jokeapi/GetRawJokesTest.kt @@ -1,7 +1,7 @@ /* * GetRawJokesTest.kt * - * Copyright 2022-2025 Erik C. Thauvin (erik@thauvin.net) + * Copyright 2022-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: @@ -33,60 +33,47 @@ package net.thauvin.erik.jokeapi import assertk.all import assertk.assertThat -import assertk.assertions.* +import assertk.assertions.doesNotContain +import assertk.assertions.isNotEmpty +import assertk.assertions.startsWith import net.thauvin.erik.jokeapi.models.Format import net.thauvin.erik.jokeapi.models.IdRange -import net.thauvin.erik.jokeapi.models.JokeResponse import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith +import kotlin.test.assertContains @ExtendWith(BeforeAllTests::class) internal class GetRawJokesTest { @Test fun `Get Raw Joke with TXT`() { val response = rawJokes(format = Format.TXT) - assertThat(response).all { - prop("statusCode", JokeResponse::statusCode).isEqualTo(200) - prop("data", JokeResponse::data).all { - isNotEmpty() - doesNotContain("Error") - } + assertThat(response, "rawJoke(txt)").all { + isNotEmpty() + doesNotContain("Error") } } @Test fun `Get Raw Joke with XML`() { val response = rawJokes(format = Format.XML) - assertThat(response).all { - prop("statusCode", JokeResponse::statusCode).isEqualTo(200) - prop("data", JokeResponse::data).startsWith("\n\n false") - } + assertThat(response, "rawJoke(xml)").startsWith("\n\n false") } @Test fun `Get Raw Joke with YAML`() { val response = rawJokes(format = Format.YAML) - assertThat(response).all { - prop("statusCode", JokeResponse::statusCode).isEqualTo(200) - prop("data", JokeResponse::data).startsWith("error: false") - } + assertThat(response, "rawJoke(yaml)").startsWith("error: false") } @Test fun `Get Raw Jokes`() { val response = rawJokes(amount = 2) - assertThat(response).all { - prop("statusCode", JokeResponse::statusCode).isEqualTo(200) - prop("data", JokeResponse::data).isNotEmpty() - } + assertContains(response, "\"amount\": 2", false, "rawJoke(2)") } @Test fun `Get Raw Invalid Jokes`() { val response = rawJokes(contains = "foo", safe = true, amount = 2, idRange = IdRange(160, 161)) - assertThat(response).all { - prop("statusCode", JokeResponse::statusCode).isEqualTo(400) - prop("data", JokeResponse::data).contains("\"error\": true") - } + assertContains(response, "\"error\": true", false, "getRawJokes(foo)") } } diff --git a/src/test/kotlin/net/thauvin/erik/jokeapi/JokeConfigTest.kt b/src/test/kotlin/net/thauvin/erik/jokeapi/JokeConfigTest.kt index a4d4e0c..92de2e0 100644 --- a/src/test/kotlin/net/thauvin/erik/jokeapi/JokeConfigTest.kt +++ b/src/test/kotlin/net/thauvin/erik/jokeapi/JokeConfigTest.kt @@ -1,7 +1,7 @@ /* * JokeConfigTest.kt * - * Copyright 2022-2025 Erik C. Thauvin (erik@thauvin.net) + * Copyright 2022-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: @@ -33,12 +33,25 @@ package net.thauvin.erik.jokeapi import assertk.all import assertk.assertThat -import assertk.assertions.* -import net.thauvin.erik.jokeapi.JokeApi.getRawJokes +import assertk.assertions.each +import assertk.assertions.isBetween +import assertk.assertions.isEmpty +import assertk.assertions.isEqualTo +import assertk.assertions.isGreaterThanOrEqualTo +import assertk.assertions.isTrue +import assertk.assertions.prop +import assertk.assertions.size import net.thauvin.erik.jokeapi.JokeApi.joke import net.thauvin.erik.jokeapi.JokeApi.jokes +import net.thauvin.erik.jokeapi.JokeApi.getRawJokes import net.thauvin.erik.jokeapi.JokeApi.logger -import net.thauvin.erik.jokeapi.models.* +import net.thauvin.erik.jokeapi.models.Category +import net.thauvin.erik.jokeapi.models.Flag +import net.thauvin.erik.jokeapi.models.Format +import net.thauvin.erik.jokeapi.models.IdRange +import net.thauvin.erik.jokeapi.models.Joke +import net.thauvin.erik.jokeapi.models.Language +import net.thauvin.erik.jokeapi.models.Type import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import kotlin.test.assertContains @@ -102,9 +115,8 @@ internal class JokeConfigTest { amount(2) safe(true) }.build() - val jokes = getRawJokes(config) - assertThat(jokes.statusCode).isEqualTo(200) - assertContains(jokes.data, "----------------------------------------------", false, "config.amount(2)") + val joke = getRawJokes(config) + assertContains(joke, "----------------------------------------------", false, "config.amount(2)") } @Test @@ -155,8 +167,8 @@ internal class JokeConfigTest { }.build() assertThat(config, "config").all { prop(JokeConfig::categories).isEqualTo(categories) - prop(JokeConfig::lang).isEqualTo(language) - prop(JokeConfig::blacklistFlags).isEqualTo(flags) + prop(JokeConfig::language).isEqualTo(language) + prop(JokeConfig::flags).isEqualTo(flags) prop(JokeConfig::type).isEqualTo(type) prop(JokeConfig::format).isEqualTo(format) prop(JokeConfig::contains).isEqualTo(search) diff --git a/src/test/kotlin/net/thauvin/erik/jokeapi/JokeUtilTest.kt b/src/test/kotlin/net/thauvin/erik/jokeapi/JokeUtilTest.kt index d50b97a..8f8d936 100644 --- a/src/test/kotlin/net/thauvin/erik/jokeapi/JokeUtilTest.kt +++ b/src/test/kotlin/net/thauvin/erik/jokeapi/JokeUtilTest.kt @@ -1,7 +1,7 @@ /* - * JokeUtilTest.kt + * UtilTest.kt * - * Copyright 2022-2025 Erik C. Thauvin (erik@thauvin.net) + * Copyright 2022-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: @@ -33,7 +33,6 @@ package net.thauvin.erik.jokeapi import assertk.assertThat import assertk.assertions.contains -import assertk.assertions.isEqualTo import org.json.JSONException import org.json.JSONObject import org.junit.jupiter.api.Test @@ -55,8 +54,7 @@ internal class JokeUtilTest { @Test fun `Validate Authentication Header`() { val token = "AUTH-TOKEN" - val response = fetchUrl("https://postman-echo.com/get", token) - assertThat(response.statusCode).isEqualTo(200) - assertThat(response.data, "body").contains("\"authentication\": \"$token\"") + val body = fetchUrl("https://postman-echo.com/get", token) + assertThat(body, "body").contains("\"authentication\": \"$token\"") } }