-
-
-
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
deleted file mode 100644
index 9f84d53..0000000
--- a/.vscode/launch.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "version": "0.2.0",
- "configurations": [
- {
- "type": "java",
- "name": "Run Tests",
- "request": "launch",
- "mainClass": "net.thauvin.erik.JokeapiTest"
- }
- ]
-}
diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index 133aa45..0000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "java.project.sourcePaths": [
- "src/main/java",
- "src/main/resources",
- "src/test/java",
- "src/bld/java"
- ],
- "java.configuration.updateBuildConfiguration": "automatic",
- "java.project.referencedLibraries": [
- "${HOME}/.bld/dist/bld-1.7.5.jar",
- "lib/compile/*.jar",
- "lib/runtime/*.jar",
- "lib/test/*.jar"
- ]
-}
diff --git a/LICENSE.txt b/LICENSE.txt
index 4331a4d..82ecd17 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,4 +1,4 @@
-Copyright 2022-2023 Erik C. Thauvin (erik@thauvin.net)
+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:
diff --git a/README.md b/README.md
index 5308277..bb86972 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,6 @@
[](https://opensource.org/licenses/BSD-3-Clause)
-[](https://kotlinlang.org/)
+[](https://kotlinlang.org/)
+[](https://rife2.com/bld)
[](https://github.com/ethauvin/jokeapi/releases/latest)
[](https://central.sonatype.com/artifact/net.thauvin.erik/jokeapi)
[](https://oss.sonatype.org/content/repositories/snapshots/net/thauvin/erik/jokeapi/)
@@ -15,7 +16,7 @@ A simple library to retrieve jokes from [Sv443's JokeAPI](https://v2.jokeapi.dev
## Examples (TL;DR)
```kotlin
-import net.thauvin.erik.jokeapi.getJoke
+import net.thauvin.erik.jokeapi.joke
val joke = joke()
val safe = joke(safe = true)
@@ -94,10 +95,10 @@ joke.getJoke().forEach(System.out::println);
To use with [bld](https://rife2.com/bld), include the following dependency in your build file:
```java
-repositories = List.of(MAVEN_CENTRAL);
+repositories = List.of(MAVEN_CENTRAL, SONATYPE_SNAPSHOTS_LEGACY);
scope(compile)
- .include(dependency("net.thauvin.erik:cryptoprice:1.0.1"));
+ .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.
@@ -111,7 +112,7 @@ repositories {
}
dependencies {
- implementation("net.thauvin.erik:jokeapi:0.9.0")
+ implementation("net.thauvin.erik:jokeapi:1.0.0")
}
```
@@ -123,9 +124,10 @@ You can also retrieve one or more raw (unprocessed) jokes in all [supported form
For example for YAML:
```kotlin
-var joke = getRawJokes(format = Format.YAML, idRange = IdRange(22))
-println(joke)
+var jokes = getRawJokes(format = Format.YAML, idRange = IdRange(22))
+println(jokes.data)
```
+
```yaml
error: false
category: "Programming"
@@ -141,8 +143,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
@@ -152,15 +154,37 @@ 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 lang = JokeApi.apiCall(
+val response = JokeApi.apiCall(
endPoint = "langcode",
path = "french",
params = mapOf(Parameter.FORMAT to Format.YAML.value)
)
-println(lang)
+if (response.statusCode == 200) {
+ println(response.data)
+}
```
+
```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/detekt-baseline.xml b/detekt-baseline.xml
index 50acbf1..1a99819 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$200MagicNumber:JokeUtil.kt$399MagicNumber:JokeUtil.kt$400
@@ -22,6 +22,7 @@
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.*
diff --git a/lib/bld/bld-wrapper.jar b/lib/bld/bld-wrapper.jar
index 431fce9..1eb86cf 100644
Binary files a/lib/bld/bld-wrapper.jar and b/lib/bld/bld-wrapper.jar differ
diff --git a/lib/bld/bld-wrapper.properties b/lib/bld/bld-wrapper.properties
index d2d7adf..fc9463a 100644
--- a/lib/bld/bld-wrapper.properties
+++ b/lib/bld/bld-wrapper.properties
@@ -1,9 +1,10 @@
bld.downloadExtensionJavadoc=false
bld.downloadExtensionSources=true
-bld.extension-jacoco=com.uwyn.rife2:bld-jacoco-report:0.9.1
-bld.extensions=com.uwyn.rife2:bld-kotlin:0.9.0-SNAPSHOT
-bld.extension-detekt=com.uwyn.rife2:bld-detekt:0.9.0-SNAPSHOT
-bld.repositories=MAVEN_LOCAL,MAVEN_CENTRAL,RIFE2_SNAPSHOTS,RIFE2_RELEASES
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=1.7.5
+bld.version=2.2.1
diff --git a/pom.xml b/pom.xml
index f2be8ba..e480d48 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
4.0.0net.thauvin.erikjokeapi
- 0.9.1
+ 1.0.1-SNAPSHOTjokeapiRetrieve jokes from Sv443's JokeAPIhttps://github.com/ethauvin/jokeapi
@@ -18,37 +18,19 @@
org.jetbrains.kotlinkotlin-stdlib
- 1.9.21
- compile
-
-
- org.jetbrains.kotlin
- kotlin-stdlib-common
- 1.9.21
- compile
-
-
- org.jetbrains.kotlin
- kotlin-stdlib-jdk7
- 1.9.21
- compile
-
-
- org.jetbrains.kotlin
- kotlin-stdlib-jdk8
- 1.9.21
+ 2.1.20compileorg.jsonjson
- 20231013
+ 20250107compilenet.thauvin.erik.urlencoderurlencoder-lib-jvm
- 1.4.0
+ 1.6.0compile
diff --git a/src/bld/java/net/thauvin/erik/JokeApiBuild.java b/src/bld/java/net/thauvin/erik/JokeApiBuild.java
index 2d216a8..62b9d9a 100644
--- a/src/bld/java/net/thauvin/erik/JokeApiBuild.java
+++ b/src/bld/java/net/thauvin/erik/JokeApiBuild.java
@@ -1,7 +1,7 @@
/*
* JokeApiBuild.java
*
- * Copyright 2022-2023 Erik C. Thauvin (erik@thauvin.net)
+ * 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:
@@ -35,10 +35,11 @@ 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.DokkaOperation;
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;
@@ -49,41 +50,45 @@ 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(0, 9, 1);
+ version = version(1, 0, 1, "SNAPSHOT");
javaRelease = 11;
downloadSources = true;
autoDownloadPurge = true;
repositories = List.of(MAVEN_LOCAL, MAVEN_CENTRAL);
- final var kotlin = version(1, 9, 21);
+ final var kotlin = version(2, 1, 20);
scope(compile)
.include(dependency("org.jetbrains.kotlin", "kotlin-stdlib", kotlin))
- .include(dependency("org.jetbrains.kotlin", "kotlin-stdlib-common", kotlin))
- .include(dependency("org.jetbrains.kotlin", "kotlin-stdlib-jdk7", kotlin))
- .include(dependency("org.jetbrains.kotlin", "kotlin-stdlib-jdk8", kotlin))
- .include(dependency("org.json", "json", "20231013"))
- .include(dependency("net.thauvin.erik.urlencoder", "urlencoder-lib-jvm", version(1, 4, 0)));
+ .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", version(1, 9, 21)))
- .include(dependency("org.junit.jupiter", "junit-jupiter", version(5, 10, 1)))
- .include(dependency("org.junit.platform", "junit-platform-console-standalone", version(1, 10, 1)))
- .include(dependency("com.willowtreeapps.assertk", "assertk-jvm", version(0, 27, 0)));
+ .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)
@@ -110,18 +115,29 @@ public class JokeApiBuild extends Project {
.signKey(property("sign.key"))
.signPassphrase(property("sign.passphrase"));
- jarSourcesOperation().sourceDirectories(new File(srcMainDirectory(), "kotlin"));
+ 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 IOException {
+ public void compile() throws Exception {
new CompileKotlinOperation()
.fromProject(this)
+ .compileOptions(new CompileOptions().verbose(true))
.execute();
}
@@ -142,9 +158,10 @@ public class JokeApiBuild extends Project {
}
@BuildCommand(summary = "Generates JaCoCo Reports")
- public void jacoco() throws IOException {
+ public void jacoco() throws Exception {
new JacocoReportOperation()
.fromProject(this)
+ .sourceFiles(srcMainKotlin)
.execute();
}
@@ -166,6 +183,12 @@ public class JokeApiBuild extends Project {
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(),
diff --git a/src/main/kotlin/net/thauvin/erik/jokeapi/JokeApi.kt b/src/main/kotlin/net/thauvin/erik/jokeapi/JokeApi.kt
index b4df9aa..474aa27 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-2023 Erik C. Thauvin (erik@thauvin.net)
+ * 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:
@@ -45,13 +45,16 @@ 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.
*
- * Sse the [JokeAPI Documentation](https://jokeapi.dev/#endpoints) for more details.
+ * See the [JokeAPI Documentation](https://jokeapi.dev/#endpoints) for more details.
*/
@JvmStatic
@JvmOverloads
@@ -61,7 +64,7 @@ object JokeApi {
path: String = "",
params: Map = emptyMap(),
auth: String = ""
- ): String {
+ ): JokeResponse {
val urlBuilder = StringBuilder("$API_URL$endPoint")
if (path.isNotEmpty()) {
@@ -95,11 +98,11 @@ object JokeApi {
*/
@JvmStatic
@Throws(HttpErrorException::class)
- fun getRawJokes(config: JokeConfig): String {
+ fun getRawJokes(config: JokeConfig): JokeResponse {
return rawJokes(
categories = config.categories,
- lang = config.language,
- blacklistFlags = config.flags,
+ lang = config.lang,
+ blacklistFlags = config.blacklistFlags,
type = config.type,
format = config.format,
contains = config.contains,
@@ -121,8 +124,8 @@ object JokeApi {
fun joke(config: JokeConfig = JokeConfig.Builder().build()): Joke {
return joke(
categories = config.categories,
- lang = config.language,
- blacklistFlags = config.flags,
+ lang = config.lang,
+ blacklistFlags = config.blacklistFlags,
type = config.type,
contains = config.contains,
idRange = config.idRange,
@@ -142,8 +145,8 @@ object JokeApi {
fun jokes(config: JokeConfig): Array {
return jokes(
categories = config.categories,
- lang = config.language,
- blacklistFlags = config.flags,
+ lang = config.lang,
+ blacklistFlags = config.blacklistFlags,
type = config.type,
contains = config.contains,
idRange = config.idRange,
@@ -161,6 +164,32 @@ 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(
@@ -184,7 +213,7 @@ fun joke(
idRange = idRange,
safe = safe,
auth = auth
- )
+ ).data
)
if (json.getBoolean("error")) {
throw parseError(json)
@@ -198,7 +227,35 @@ fun joke(
*
* Sse the [JokeAPI Documentation](https://jokeapi.dev/#joke-endpoint) for more details.
*
- * @param amount The required amount of jokes to return.
+ * @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 splitNewLine Split newline within [Type.SINGLE] joke.
*/
fun jokes(
@@ -224,7 +281,7 @@ fun jokes(
amount = amount,
safe = safe,
auth = auth
- )
+ ).data
)
if (json.getBoolean("error")) {
throw parseError(json)
@@ -241,8 +298,42 @@ fun jokes(
/**
* Returns one or more jokes.
*
- * Sse the [JokeAPI Documentation](https://jokeapi.dev/#joke-endpoint) for more details.
+ * 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.
*/
+@Throws(HttpErrorException::class)
fun rawJokes(
categories: Set = setOf(Category.ANY),
lang: Language = Language.EN,
@@ -254,7 +345,7 @@ fun rawJokes(
amount: Int = 1,
safe: Boolean = false,
auth: String = ""
-): String {
+): JokeResponse {
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 4537d13..a4d4901 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-2023 Erik C. Thauvin (erik@thauvin.net)
+ * 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:
@@ -39,19 +39,19 @@ import net.thauvin.erik.jokeapi.models.*
*
* 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
-) {
+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
+
/**
* [Builds][build] a new configuration.
*
@@ -72,20 +72,86 @@ class JokeConfig private constructor(
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 }
+ /**
+ * 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 build() = JokeConfig(
- categories, lang, blacklistFlags, type, format, contains, idRange, amount, safe, splitNewLine, 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)
}
}
diff --git a/src/main/kotlin/net/thauvin/erik/jokeapi/JokeUtil.kt b/src/main/kotlin/net/thauvin/erik/jokeapi/JokeUtil.kt
index ff9f3e6..651844c 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-2023 Erik C. Thauvin (erik@thauvin.net)
+ * 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:
@@ -39,30 +39,37 @@ import net.thauvin.erik.jokeapi.models.*
import org.json.JSONObject
import java.io.IOException
import java.net.HttpURLConnection
-import java.net.URL
+import java.net.URI
import java.util.logging.Level
-internal fun fetchUrl(url: String, auth: String = ""): String {
+/**
+ * Fetch a URL.
+ */
+internal fun fetchUrl(url: String, auth: String = ""): JokeResponse {
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)
- }
+ 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)
+ }
- if (connection.responseCode in 200..399) {
- val body = connection.inputStream.bufferedReader().use { it.readText() }
- if (JokeApi.logger.isLoggable(Level.FINE)) {
+ 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)) {
JokeApi.logger.fine(body)
}
- return body
- } else {
- throw httpError(connection.responseCode)
+ return JokeResponse(connection.responseCode, body)
+ } finally {
+ connection.disconnect()
}
}
@@ -123,6 +130,9 @@ 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) }
@@ -136,6 +146,9 @@ internal fun parseError(json: JSONObject): JokeException {
)
}
+/**
+ * Parse Joke.
+ */
internal fun parseJoke(json: JSONObject, splitNewLine: Boolean): Joke {
val jokes = mutableListOf()
if (json.has("setup")) {
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 815afcc..f2e8529 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-2023 Erik C. Thauvin (erik@thauvin.net)
+ * 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:
@@ -44,7 +44,6 @@ class HttpErrorException @JvmOverloads constructor(
cause: Throwable? = null
) : IOException(message, cause) {
companion object {
- @Suppress("ConstPropertyName")
private const val serialVersionUID = 1L
}
}
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 16d4ec8..ac77344 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-2023 Erik C. Thauvin (erik@thauvin.net)
+ * 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:
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 4951d4a..cfb008e 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-2023 Erik C. Thauvin (erik@thauvin.net)
+ * 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:
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 af92e90..be2e21f 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-2023 Erik C. Thauvin (erik@thauvin.net)
+ * 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:
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 2678a21..1beb9d3 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-2023 Erik C. Thauvin (erik@thauvin.net)
+ * 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:
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 62a6eb6..73d45ec 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-2023 Erik C. Thauvin (erik@thauvin.net)
+ * 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:
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 0309977..c2124ae 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-2023 Erik C. Thauvin (erik@thauvin.net)
+ * 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:
diff --git a/src/main/kotlin/net/thauvin/erik/jokeapi/models/JokeResponse.kt b/src/main/kotlin/net/thauvin/erik/jokeapi/models/JokeResponse.kt
new file mode 100644
index 0000000..d34f2c3
--- /dev/null
+++ b/src/main/kotlin/net/thauvin/erik/jokeapi/models/JokeResponse.kt
@@ -0,0 +1,39 @@
+/*
+ * 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 10c00fb..3ee166e 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-2023 Erik C. Thauvin (erik@thauvin.net)
+ * 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:
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 b9e1106..8962b2a 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-2023 Erik C. Thauvin (erik@thauvin.net)
+ * 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:
@@ -34,6 +34,7 @@ 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 59126b4..4fd80fe 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-2023 Erik C. Thauvin (erik@thauvin.net)
+ * 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:
diff --git a/src/test/kotlin/net/thauvin/erik/jokeapi/ApiCallTest.kt b/src/test/kotlin/net/thauvin/erik/jokeapi/ApiCallTest.kt
index d9f9b30..6153825 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-2023 Erik C. Thauvin (erik@thauvin.net)
+ * 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:
@@ -32,6 +32,7 @@
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
@@ -51,8 +52,9 @@ internal class ApiCallTest {
fun `Get Flags`() {
// See https://v2.jokeapi.dev/#flags-endpoint
val response = apiCall(endPoint = "flags")
- val json = JSONObject(response)
- assertAll("Validate JSON",
+ val json = JSONObject(response.data)
+ 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) })
@@ -65,14 +67,16 @@ internal class ApiCallTest {
endPoint = "langcode", path = "french",
params = mapOf(Parameter.FORMAT to Format.YAML.value)
)
- assertContains(lang, "code: \"fr\"", false, "apiCall(langcode, french, yaml)")
+ assertThat(lang.statusCode).isEqualTo(200)
+ assertContains(lang.data, "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!")
+ assertThat(ping.statusCode).isEqualTo(200)
+ assertThat(ping.data).startsWith("Pong!")
}
@Test
@@ -82,6 +86,7 @@ internal class ApiCallTest {
endPoint = "languages",
params = mapOf(Parameter.FORMAT to Format.XML.value, Parameter.LANG to Language.FR.value)
)
- assertThat(lang).startsWith("")
+ assertThat(lang.statusCode).isEqualTo(200)
+ assertThat(lang.data).startsWith("")
}
}
diff --git a/src/test/kotlin/net/thauvin/erik/jokeapi/BeforeAllTests.kt b/src/test/kotlin/net/thauvin/erik/jokeapi/BeforeAllTests.kt
index de9d48a..50ce4b2 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-2023 Erik C. Thauvin (erik@thauvin.net)
+ * 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:
diff --git a/src/test/kotlin/net/thauvin/erik/jokeapi/ExceptionsTest.kt b/src/test/kotlin/net/thauvin/erik/jokeapi/ExceptionsTest.kt
index 3932afd..eb6837a 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-2023 Erik C. Thauvin (erik@thauvin.net)
+ * 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:
@@ -41,8 +41,6 @@ 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 {
@@ -63,19 +61,20 @@ internal class ExceptionsTest {
}
}
- @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()
+ @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()
+ }
}
}
}
diff --git a/src/test/kotlin/net/thauvin/erik/jokeapi/GetJokeTest.kt b/src/test/kotlin/net/thauvin/erik/jokeapi/GetJokeTest.kt
index c08ce39..e5a7d39 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-2023 Erik C. Thauvin (erik@thauvin.net)
+ * 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:
@@ -70,15 +70,12 @@ internal class GetJokeTest {
@Test
fun `Get Joke with ID`() {
- val id = 172
+ val id = 201
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::flags).contains(Flag.RELIGIOUS);
+ prop(Joke::id).isEqualTo(id)
prop(Joke::category).isEqualTo(Category.PUN)
}
}
@@ -137,12 +134,10 @@ internal class GetJokeTest {
@Test
fun `Get Joke with Split Newline`() {
- val joke = joke(
- categories = setOf(Category.DARK), type = Type.SINGLE, idRange = IdRange(178), splitNewLine = true
- )
+ val joke = joke(type = Type.SINGLE, idRange = IdRange(18), splitNewLine = true)
logger.fine(joke.toString())
assertThat(joke::joke, "joke(splitNewLine=true)").all {
- size().isEqualTo(2)
+ size().isGreaterThanOrEqualTo(2)
each {
containsNone("\n")
}
@@ -177,13 +172,12 @@ internal class GetJokeTest {
@Test
fun `Get Joke using Search`() {
- val id = 265
- val search = "his wife"
+ val search = "UDP joke"
val joke =
- joke(contains = search, categories = setOf(Category.PROGRAMMING), idRange = IdRange(id), safe = true)
+ joke(contains = search, categories = setOf(Category.PROGRAMMING), safe = true)
logger.fine(joke.toString())
assertThat(joke, "joke($search)").all {
- prop(Joke::id).isEqualTo(id)
+ prop(Joke::id).isEqualTo(0)
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 2e07a2d..ea49211 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-2023 Erik C. Thauvin (erik@thauvin.net)
+ * 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:
diff --git a/src/test/kotlin/net/thauvin/erik/jokeapi/GetRawJokesTest.kt b/src/test/kotlin/net/thauvin/erik/jokeapi/GetRawJokesTest.kt
index 7bcf1c6..aa85337 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-2023 Erik C. Thauvin (erik@thauvin.net)
+ * 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:
@@ -33,47 +33,60 @@ package net.thauvin.erik.jokeapi
import assertk.all
import assertk.assertThat
-import assertk.assertions.doesNotContain
-import assertk.assertions.isNotEmpty
-import assertk.assertions.startsWith
+import assertk.assertions.*
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, "rawJoke(txt)").all {
- isNotEmpty()
- doesNotContain("Error")
+ assertThat(response).all {
+ prop("statusCode", JokeResponse::statusCode).isEqualTo(200)
+ prop("data", JokeResponse::data).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")
+ assertThat(response).all {
+ prop("statusCode", JokeResponse::statusCode).isEqualTo(200)
+ prop("data", JokeResponse::data).startsWith("\n\n false")
+ }
}
@Test
fun `Get Raw Joke with YAML`() {
val response = rawJokes(format = Format.YAML)
- assertThat(response, "rawJoke(yaml)").startsWith("error: false")
+ assertThat(response).all {
+ prop("statusCode", JokeResponse::statusCode).isEqualTo(200)
+ prop("data", JokeResponse::data).startsWith("error: false")
+ }
}
@Test
fun `Get Raw Jokes`() {
val response = rawJokes(amount = 2)
- assertContains(response, "\"amount\": 2", false, "rawJoke(2)")
+ assertThat(response).all {
+ prop("statusCode", JokeResponse::statusCode).isEqualTo(200)
+ prop("data", JokeResponse::data).isNotEmpty()
+ }
}
@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)")
+ assertThat(response).all {
+ prop("statusCode", JokeResponse::statusCode).isEqualTo(400)
+ prop("data", JokeResponse::data).contains("\"error\": true")
+ }
}
}
diff --git a/src/test/kotlin/net/thauvin/erik/jokeapi/JokeConfigTest.kt b/src/test/kotlin/net/thauvin/erik/jokeapi/JokeConfigTest.kt
index e617dfc..a4d4e0c 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-2023 Erik C. Thauvin (erik@thauvin.net)
+ * 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:
@@ -102,8 +102,9 @@ internal class JokeConfigTest {
amount(2)
safe(true)
}.build()
- val joke = getRawJokes(config)
- assertContains(joke, "----------------------------------------------", false, "config.amount(2)")
+ val jokes = getRawJokes(config)
+ assertThat(jokes.statusCode).isEqualTo(200)
+ assertContains(jokes.data, "----------------------------------------------", false, "config.amount(2)")
}
@Test
@@ -154,8 +155,8 @@ internal class JokeConfigTest {
}.build()
assertThat(config, "config").all {
prop(JokeConfig::categories).isEqualTo(categories)
- prop(JokeConfig::language).isEqualTo(language)
- prop(JokeConfig::flags).isEqualTo(flags)
+ prop(JokeConfig::lang).isEqualTo(language)
+ prop(JokeConfig::blacklistFlags).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 4b390c8..d50b97a 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
*
- * Copyright 2022-2023 Erik C. Thauvin (erik@thauvin.net)
+ * 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:
@@ -33,6 +33,7 @@ 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
@@ -54,7 +55,8 @@ internal class JokeUtilTest {
@Test
fun `Validate Authentication Header`() {
val token = "AUTH-TOKEN"
- val body = fetchUrl("https://postman-echo.com/get", token)
- assertThat(body, "body").contains("\"authentication\": \"$token\"")
+ val response = fetchUrl("https://postman-echo.com/get", token)
+ assertThat(response.statusCode).isEqualTo(200)
+ assertThat(response.data, "body").contains("\"authentication\": \"$token\"")
}
}