Moved Kotlin-only functions to top-level
This commit is contained in:
parent
9435df289a
commit
de30b3f638
13 changed files with 525 additions and 366 deletions
|
@ -9,7 +9,7 @@ A simple Kotlin/Java library to retrieve jokes from [Sv443's JokeAPI](https://v2
|
||||||
## Examples (TL;DR)
|
## Examples (TL;DR)
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
import net.thauvin.erik.jokeapi.JokeApi.Companion.getJoke
|
import net.thauvin.erik.jokeapi.getJoke
|
||||||
|
|
||||||
val joke = getJoke()
|
val joke = getJoke()
|
||||||
val safe = getJoke(safe = true)
|
val safe = getJoke(safe = true)
|
||||||
|
@ -124,9 +124,7 @@ var config = new JokeConfig.Builder()
|
||||||
.safe(true)
|
.safe(true)
|
||||||
.build();
|
.build();
|
||||||
var joke = JokeApi.getJoke(config);
|
var joke = JokeApi.getJoke(config);
|
||||||
for (var j : joke.getJoke()) {
|
joke.getJoke().forEach(System.out::println);
|
||||||
System.out.println(j);
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Extending
|
## Extending
|
||||||
|
@ -136,7 +134,7 @@ 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):
|
For example to retrieve the French [language code](https://v2.jokeapi.dev/#langcode-endpoint):
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
val lang = apiCall(
|
val lang = JokeApi.apiCall(
|
||||||
endPoint = "langcode",
|
endPoint = "langcode",
|
||||||
path = "french",
|
path = "french",
|
||||||
params = mapOf(Parameter.FORMAT to Format.YAML.value)
|
params = mapOf(Parameter.FORMAT to Format.YAML.value)
|
||||||
|
|
|
@ -2,24 +2,22 @@
|
||||||
<SmellBaseline>
|
<SmellBaseline>
|
||||||
<ManuallySuppressedIssues/>
|
<ManuallySuppressedIssues/>
|
||||||
<CurrentIssues>
|
<CurrentIssues>
|
||||||
<ID>ComplexMethod:JokeApi.kt$JokeApi.Companion$@JvmStatic @Throws(HttpErrorException::class, IOException::class, IllegalArgumentException::class) fun getRawJokes( categories: Set<Category> = setOf(Category.ANY), language: Language = Language.ENGLISH, flags: Set<Flag> = emptySet(), type: Type = Type.ALL, format: Format = Format.JSON, search: String = "", idRange: IdRange = IdRange(), amount: Int = 1, safe: Boolean = false, ): String</ID>
|
<ID>ComplexMethod:JokeApi.kt$fun getRawJokes( categories: Set<Category> = setOf(Category.ANY), language: Language = Language.ENGLISH, flags: Set<Flag> = emptySet(), type: Type = Type.ALL, format: Format = Format.JSON, search: String = "", idRange: IdRange = IdRange(), amount: Int = 1, safe: Boolean = false, auth: String = "" ): String</ID>
|
||||||
<ID>LongParameterList:JokeApi.kt$JokeApi.Companion$( amount: Int, categories: Set<Category> = setOf(Category.ANY), language: Language = Language.ENGLISH, flags: Set<Flag> = emptySet(), type: Type = Type.ALL, search: String = "", idRange: IdRange = IdRange(), safe: Boolean = false, splitNewLine: Boolean = false )</ID>
|
<ID>LongParameterList:JokeApi.kt$( amount: Int, categories: Set<Category> = setOf(Category.ANY), language: Language = Language.ENGLISH, flags: Set<Flag> = emptySet(), type: Type = Type.ALL, search: String = "", idRange: IdRange = IdRange(), safe: Boolean = false, splitNewLine: Boolean = false, auth: String = "" )</ID>
|
||||||
<ID>LongParameterList:JokeApi.kt$JokeApi.Companion$( categories: Set<Category> = setOf(Category.ANY), language: Language = Language.ENGLISH, flags: Set<Flag> = emptySet(), type: Type = Type.ALL, format: Format = Format.JSON, search: String = "", idRange: IdRange = IdRange(), amount: Int = 1, safe: Boolean = false, )</ID>
|
<ID>LongParameterList:JokeApi.kt$( categories: Set<Category> = setOf(Category.ANY), language: Language = Language.ENGLISH, flags: Set<Flag> = emptySet(), type: Type = Type.ALL, format: Format = Format.JSON, search: String = "", idRange: IdRange = IdRange(), amount: Int = 1, safe: Boolean = false, auth: String = "" )</ID>
|
||||||
<ID>LongParameterList:JokeApi.kt$JokeApi.Companion$( categories: Set<Category> = setOf(Category.ANY), language: Language = Language.ENGLISH, flags: Set<Flag> = emptySet(), type: Type = Type.ALL, search: String = "", idRange: IdRange = IdRange(), safe: Boolean = false, splitNewLine: Boolean = false )</ID>
|
<ID>LongParameterList:JokeApi.kt$( categories: Set<Category> = setOf(Category.ANY), language: Language = Language.ENGLISH, flags: Set<Flag> = emptySet(), type: Type = Type.ALL, search: String = "", idRange: IdRange = IdRange(), safe: Boolean = false, splitNewLine: Boolean = false, auth: String = "" )</ID>
|
||||||
<ID>LongParameterList:JokeConfig.kt$JokeConfig$( val categories: Set<Category>, val language: Language, val flags: Set<Flag>, val type: Type, val format: Format, val search: String, val idRange: IdRange, val amount: Int = 1, val safe: Boolean, val splitNewLine: Boolean, )</ID>
|
<ID>LongParameterList:JokeConfig.kt$JokeConfig$( val categories: Set<Category>, val language: Language, val flags: Set<Flag>, val type: Type, val format: Format, val search: String, val idRange: IdRange, val amount: Int, val safe: Boolean, val splitNewLine: Boolean, val auth: String )</ID>
|
||||||
<ID>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 )</ID>
|
<ID>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 )</ID>
|
||||||
<ID>MagicNumber:JokeApi.kt$JokeApi.Companion$200</ID>
|
<ID>MagicNumber:util.kt$200</ID>
|
||||||
<ID>MagicNumber:JokeApi.kt$JokeApi.Companion$399</ID>
|
<ID>MagicNumber:util.kt$399</ID>
|
||||||
<ID>MagicNumber:JokeApi.kt$JokeApi.Companion$400</ID>
|
<ID>MagicNumber:util.kt$400</ID>
|
||||||
<ID>MagicNumber:JokeApi.kt$JokeApi.Companion$403</ID>
|
<ID>MagicNumber:util.kt$403</ID>
|
||||||
<ID>MagicNumber:JokeApi.kt$JokeApi.Companion$404</ID>
|
<ID>MagicNumber:util.kt$404</ID>
|
||||||
<ID>MagicNumber:JokeApi.kt$JokeApi.Companion$413</ID>
|
<ID>MagicNumber:util.kt$413</ID>
|
||||||
<ID>MagicNumber:JokeApi.kt$JokeApi.Companion$414</ID>
|
<ID>MagicNumber:util.kt$414</ID>
|
||||||
<ID>MagicNumber:JokeApi.kt$JokeApi.Companion$429</ID>
|
<ID>MagicNumber:util.kt$429</ID>
|
||||||
<ID>MagicNumber:JokeApi.kt$JokeApi.Companion$500</ID>
|
<ID>MagicNumber:util.kt$500</ID>
|
||||||
<ID>MagicNumber:JokeApi.kt$JokeApi.Companion$523</ID>
|
<ID>MagicNumber:util.kt$523</ID>
|
||||||
<ID>TooManyFunctions:JokeApi.kt$JokeApi$Companion</ID>
|
|
||||||
<ID>TooManyFunctions:JokeConfig.kt$JokeConfig$Builder</ID>
|
<ID>TooManyFunctions:JokeConfig.kt$JokeConfig$Builder</ID>
|
||||||
<ID>UtilityClassWithPublicConstructor:JokeApi.kt$JokeApi</ID>
|
|
||||||
</CurrentIssues>
|
</CurrentIssues>
|
||||||
</SmellBaseline>
|
</SmellBaseline>
|
||||||
|
|
2
pom.xml
2
pom.xml
|
@ -10,7 +10,7 @@
|
||||||
<artifactId>jokeapi</artifactId>
|
<artifactId>jokeapi</artifactId>
|
||||||
<version>0.9-SNAPSHOT</version>
|
<version>0.9-SNAPSHOT</version>
|
||||||
<name>jokeapi</name>
|
<name>jokeapi</name>
|
||||||
<description>Kotlin/Java Wrapper for Sv443's JokeApi</description>
|
<description>Kotlin/Java Wrapper for Sv443's JokeAPI</description>
|
||||||
<url>https://github.com/ethauvin/jokeapi</url>
|
<url>https://github.com/ethauvin/jokeapi</url>
|
||||||
<licenses>
|
<licenses>
|
||||||
<license>
|
<license>
|
||||||
|
|
|
@ -43,22 +43,17 @@ import net.thauvin.erik.jokeapi.models.Language
|
||||||
import net.thauvin.erik.jokeapi.models.Parameter
|
import net.thauvin.erik.jokeapi.models.Parameter
|
||||||
import net.thauvin.erik.jokeapi.models.Type
|
import net.thauvin.erik.jokeapi.models.Type
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.io.IOException
|
|
||||||
import java.net.HttpURLConnection
|
|
||||||
import java.net.URL
|
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.util.logging.Level
|
|
||||||
import java.util.logging.Logger
|
import java.util.logging.Logger
|
||||||
import java.util.stream.Collectors
|
import java.util.stream.Collectors
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements the [Sv443's JokeAPI](https://jokeapi.dev/).
|
* Implements the [Sv443's JokeAPI](https://jokeapi.dev/).
|
||||||
*/
|
*/
|
||||||
class JokeApi {
|
class JokeApi private constructor() {
|
||||||
companion object {
|
companion object {
|
||||||
private const val API_URL = "https://v2.jokeapi.dev/"
|
private const val API_URL = "https://v2.jokeapi.dev/"
|
||||||
private const val JOKE_ENDPOINT = "joke"
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
val logger: Logger by lazy { Logger.getLogger(JokeApi::class.java.simpleName) }
|
val logger: Logger by lazy { Logger.getLogger(JokeApi::class.java.simpleName) }
|
||||||
|
@ -70,8 +65,13 @@ class JokeApi {
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
@Throws(HttpErrorException::class, IOException::class)
|
@Throws(HttpErrorException::class)
|
||||||
fun apiCall(endPoint: String, path: String = "", params: Map<String, String> = emptyMap()): String {
|
fun apiCall(
|
||||||
|
endPoint: String,
|
||||||
|
path: String = "",
|
||||||
|
params: Map<String, String> = emptyMap(),
|
||||||
|
auth: String = ""
|
||||||
|
): String {
|
||||||
val urlBuilder = StringBuilder("$API_URL$endPoint")
|
val urlBuilder = StringBuilder("$API_URL$endPoint")
|
||||||
|
|
||||||
if (path.isNotEmpty()) {
|
if (path.isNotEmpty()) {
|
||||||
|
@ -98,99 +98,16 @@ class JokeApi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fetchUrl(urlBuilder.toString())
|
return fetchUrl(urlBuilder.toString(), auth)
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns one or more jokes.
|
|
||||||
*
|
|
||||||
* Sse the [JokeAPI Documentation](https://jokeapi.dev/#joke-endpoint) for more details.
|
|
||||||
* @see [getJoke]
|
|
||||||
*/
|
|
||||||
@JvmStatic
|
|
||||||
@Throws(HttpErrorException::class, IOException::class, IllegalArgumentException::class)
|
|
||||||
fun getRawJokes(
|
|
||||||
categories: Set<Category> = setOf(Category.ANY),
|
|
||||||
language: Language = Language.ENGLISH,
|
|
||||||
flags: Set<Flag> = emptySet(),
|
|
||||||
type: Type = Type.ALL,
|
|
||||||
format: Format = Format.JSON,
|
|
||||||
search: String = "",
|
|
||||||
idRange: IdRange = IdRange(),
|
|
||||||
amount: Int = 1,
|
|
||||||
safe: Boolean = false,
|
|
||||||
): String {
|
|
||||||
val params = mutableMapOf<String, String>()
|
|
||||||
|
|
||||||
// Categories
|
|
||||||
val path = if (!categories.contains(Category.ANY)) {
|
|
||||||
categories.stream().map(Category::value).collect(Collectors.joining(","))
|
|
||||||
} else {
|
|
||||||
Category.ANY.value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Language
|
|
||||||
if (language != Language.ENGLISH) {
|
|
||||||
params[Parameter.LANG] = language.value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flags
|
|
||||||
if (flags.isNotEmpty()) {
|
|
||||||
if (flags.contains(Flag.ALL)) {
|
|
||||||
params[Parameter.FLAGS] = Flag.ALL.value
|
|
||||||
} else {
|
|
||||||
params[Parameter.FLAGS] = flags.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 (search.isNotBlank()) {
|
|
||||||
params[Parameter.CONTAINS] = search
|
|
||||||
}
|
|
||||||
|
|
||||||
// Range
|
|
||||||
if (idRange.start >= 0) {
|
|
||||||
if (idRange.end == -1 || idRange.start == idRange.end) {
|
|
||||||
params[Parameter.RANGE] = idRange.start.toString()
|
|
||||||
} else if (idRange.end > idRange.start) {
|
|
||||||
params[Parameter.RANGE] = "${idRange.start}-${idRange.end}"
|
|
||||||
} else {
|
|
||||||
throw IllegalArgumentException("Invalid ID Range: ${idRange.start}, ${idRange.end}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Amount
|
|
||||||
if (amount > 1) {
|
|
||||||
params[Parameter.AMOUNT] = amount.toString()
|
|
||||||
} else if (amount <= 0) {
|
|
||||||
throw IllegalArgumentException("Invalid Amount: $amount")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Safe
|
|
||||||
if (safe) {
|
|
||||||
params[Parameter.SAFE] = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return apiCall(JOKE_ENDPOINT, path, params)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns one or more jokes using a [configuration][JokeConfig].
|
* Returns one or more jokes using a [configuration][JokeConfig].
|
||||||
*
|
*
|
||||||
* Sse the [JokeAPI Documentation](https://jokeapi.dev/#joke-endpoint) for more details.
|
* See the [JokeAPI Documentation](https://jokeapi.dev/#joke-endpoint) for more details.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Throws(HttpErrorException::class, IOException::class, IllegalArgumentException::class)
|
@Throws(HttpErrorException::class)
|
||||||
fun getRawJokes(config: JokeConfig): String {
|
fun getRawJokes(config: JokeConfig): String {
|
||||||
return getRawJokes(
|
return getRawJokes(
|
||||||
categories = config.categories,
|
categories = config.categories,
|
||||||
|
@ -201,181 +118,20 @@ class JokeApi {
|
||||||
search = config.search,
|
search = config.search,
|
||||||
idRange = config.idRange,
|
idRange = config.idRange,
|
||||||
amount = config.amount,
|
amount = config.amount,
|
||||||
safe = config.safe
|
safe = config.safe,
|
||||||
|
auth = config.auth
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(HttpErrorException::class, IOException::class)
|
|
||||||
internal fun fetchUrl(url: String): String {
|
|
||||||
if (logger.isLoggable(Level.FINE)) {
|
|
||||||
logger.fine(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
val connection = URL(url).openConnection() as HttpURLConnection
|
|
||||||
connection.setRequestProperty(
|
|
||||||
"User-Agent", "Mozilla/5.0 (Linux x86_64; rv:105.0) Gecko/20100101 Firefox/105.0"
|
|
||||||
)
|
|
||||||
|
|
||||||
if (connection.responseCode in 200..399) {
|
|
||||||
val body = connection.inputStream.bufferedReader().readText()
|
|
||||||
if (logger.isLoggable(Level.FINE)) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a [Joke] instance.
|
|
||||||
*
|
|
||||||
* Sse the [JokeAPI Documentation](https://jokeapi.dev/#joke-endpoint) for more details.
|
|
||||||
*
|
|
||||||
* @param splitNewLine Split newline within [Type.SINGLE] joke.
|
|
||||||
* @see [getRawJokes]
|
|
||||||
*/
|
|
||||||
@JvmStatic
|
|
||||||
@Throws(JokeException::class, HttpErrorException::class, IOException::class, IllegalArgumentException::class)
|
|
||||||
fun getJoke(
|
|
||||||
categories: Set<Category> = setOf(Category.ANY),
|
|
||||||
language: Language = Language.ENGLISH,
|
|
||||||
flags: Set<Flag> = emptySet(),
|
|
||||||
type: Type = Type.ALL,
|
|
||||||
search: String = "",
|
|
||||||
idRange: IdRange = IdRange(),
|
|
||||||
safe: Boolean = false,
|
|
||||||
splitNewLine: Boolean = false
|
|
||||||
): Joke {
|
|
||||||
val json = JSONObject(
|
|
||||||
getRawJokes(
|
|
||||||
categories = categories,
|
|
||||||
language = language,
|
|
||||||
flags = flags,
|
|
||||||
type = type,
|
|
||||||
search = search,
|
|
||||||
idRange = idRange,
|
|
||||||
safe = safe
|
|
||||||
)
|
|
||||||
)
|
|
||||||
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.
|
|
||||||
* @see [getRawJokes]
|
|
||||||
*/
|
|
||||||
@JvmStatic
|
|
||||||
@Throws(JokeException::class, HttpErrorException::class, IOException::class, IllegalArgumentException::class)
|
|
||||||
fun getJokes(
|
|
||||||
amount: Int,
|
|
||||||
categories: Set<Category> = setOf(Category.ANY),
|
|
||||||
language: Language = Language.ENGLISH,
|
|
||||||
flags: Set<Flag> = emptySet(),
|
|
||||||
type: Type = Type.ALL,
|
|
||||||
search: String = "",
|
|
||||||
idRange: IdRange = IdRange(),
|
|
||||||
safe: Boolean = false,
|
|
||||||
splitNewLine: Boolean = false
|
|
||||||
): Array<Joke> {
|
|
||||||
val json = JSONObject(
|
|
||||||
getRawJokes(
|
|
||||||
categories = categories,
|
|
||||||
language = language,
|
|
||||||
flags = flags,
|
|
||||||
type = type,
|
|
||||||
search = search,
|
|
||||||
idRange = idRange,
|
|
||||||
amount = amount,
|
|
||||||
safe = safe
|
|
||||||
)
|
|
||||||
)
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve a [Joke] instance using a [configuration][JokeConfig].
|
* Retrieve a [Joke] instance using a [configuration][JokeConfig].
|
||||||
*
|
*
|
||||||
* Sse the [JokeAPI Documentation](https://jokeapi.dev/#joke-endpoint) for more details.
|
* Sse the [JokeAPI Documentation](https://jokeapi.dev/#joke-endpoint) for more details.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Throws(JokeException::class, HttpErrorException::class, IOException::class, IllegalArgumentException::class)
|
@JvmOverloads
|
||||||
fun getJoke(config: JokeConfig): Joke {
|
@Throws(HttpErrorException::class, JokeException::class)
|
||||||
|
fun getJoke(config: JokeConfig = JokeConfig.Builder().build()): Joke {
|
||||||
return getJoke(
|
return getJoke(
|
||||||
categories = config.categories,
|
categories = config.categories,
|
||||||
language = config.language,
|
language = config.language,
|
||||||
|
@ -384,7 +140,8 @@ class JokeApi {
|
||||||
search = config.search,
|
search = config.search,
|
||||||
idRange = config.idRange,
|
idRange = config.idRange,
|
||||||
safe = config.safe,
|
safe = config.safe,
|
||||||
splitNewLine = config.splitNewLine
|
splitNewLine = config.splitNewLine,
|
||||||
|
auth = config.auth
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -394,7 +151,7 @@ class JokeApi {
|
||||||
* Sse the [JokeAPI Documentation](https://jokeapi.dev/#joke-endpoint) for more details.
|
* Sse the [JokeAPI Documentation](https://jokeapi.dev/#joke-endpoint) for more details.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Throws(JokeException::class, HttpErrorException::class, IOException::class, IllegalArgumentException::class)
|
@Throws(HttpErrorException::class, JokeException::class)
|
||||||
fun getJokes(config: JokeConfig): Array<Joke> {
|
fun getJokes(config: JokeConfig): Array<Joke> {
|
||||||
return getJokes(
|
return getJokes(
|
||||||
categories = config.categories,
|
categories = config.categories,
|
||||||
|
@ -405,51 +162,173 @@ class JokeApi {
|
||||||
idRange = config.idRange,
|
idRange = config.idRange,
|
||||||
amount = config.amount,
|
amount = config.amount,
|
||||||
safe = config.safe,
|
safe = config.safe,
|
||||||
splitNewLine = config.splitNewLine
|
splitNewLine = config.splitNewLine,
|
||||||
)
|
auth = config.auth
|
||||||
}
|
|
||||||
|
|
||||||
private fun parseError(json: JSONObject): JokeException {
|
|
||||||
val causedBy = json.getJSONArray("causedBy")
|
|
||||||
val causes = List<String>(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")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun parseJoke(json: JSONObject, splitNewLine: Boolean): Joke {
|
|
||||||
val jokes = mutableListOf<String>()
|
|
||||||
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<Flag>()
|
|
||||||
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"),
|
|
||||||
language = Language.valueOf(json.getString(Parameter.LANG).uppercase())
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 getJoke(
|
||||||
|
categories: Set<Category> = setOf(Category.ANY),
|
||||||
|
language: Language = Language.ENGLISH,
|
||||||
|
flags: Set<Flag> = emptySet(),
|
||||||
|
type: Type = Type.ALL,
|
||||||
|
search: String = "",
|
||||||
|
idRange: IdRange = IdRange(),
|
||||||
|
safe: Boolean = false,
|
||||||
|
splitNewLine: Boolean = false,
|
||||||
|
auth: String = ""
|
||||||
|
): Joke {
|
||||||
|
val json = JSONObject(
|
||||||
|
getRawJokes(
|
||||||
|
categories = categories,
|
||||||
|
language = language,
|
||||||
|
flags = flags,
|
||||||
|
type = type,
|
||||||
|
search = search,
|
||||||
|
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 getJokes(
|
||||||
|
amount: Int,
|
||||||
|
categories: Set<Category> = setOf(Category.ANY),
|
||||||
|
language: Language = Language.ENGLISH,
|
||||||
|
flags: Set<Flag> = emptySet(),
|
||||||
|
type: Type = Type.ALL,
|
||||||
|
search: String = "",
|
||||||
|
idRange: IdRange = IdRange(),
|
||||||
|
safe: Boolean = false,
|
||||||
|
splitNewLine: Boolean = false,
|
||||||
|
auth: String = ""
|
||||||
|
): Array<Joke> {
|
||||||
|
val json = JSONObject(
|
||||||
|
getRawJokes(
|
||||||
|
categories = categories,
|
||||||
|
language = language,
|
||||||
|
flags = flags,
|
||||||
|
type = type,
|
||||||
|
search = search,
|
||||||
|
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 getRawJokes(
|
||||||
|
categories: Set<Category> = setOf(Category.ANY),
|
||||||
|
language: Language = Language.ENGLISH,
|
||||||
|
flags: Set<Flag> = emptySet(),
|
||||||
|
type: Type = Type.ALL,
|
||||||
|
format: Format = Format.JSON,
|
||||||
|
search: String = "",
|
||||||
|
idRange: IdRange = IdRange(),
|
||||||
|
amount: Int = 1,
|
||||||
|
safe: Boolean = false,
|
||||||
|
auth: String = ""
|
||||||
|
): String {
|
||||||
|
val params = mutableMapOf<String, String>()
|
||||||
|
|
||||||
|
// Categories
|
||||||
|
val path = if (!categories.contains(Category.ANY)) {
|
||||||
|
categories.stream().map(Category::value).collect(Collectors.joining(","))
|
||||||
|
} else {
|
||||||
|
Category.ANY.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Language
|
||||||
|
if (language != Language.ENGLISH) {
|
||||||
|
params[Parameter.LANG] = language.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flags
|
||||||
|
if (flags.isNotEmpty()) {
|
||||||
|
if (flags.contains(Flag.ALL)) {
|
||||||
|
params[Parameter.FLAGS] = Flag.ALL.value
|
||||||
|
} else {
|
||||||
|
params[Parameter.FLAGS] = flags.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 (search.isNotBlank()) {
|
||||||
|
params[Parameter.CONTAINS] = search
|
||||||
|
}
|
||||||
|
|
||||||
|
// Range
|
||||||
|
if (idRange.start >= 0) {
|
||||||
|
if (idRange.end == -1 || idRange.start == idRange.end) {
|
||||||
|
params[Parameter.RANGE] = idRange.start.toString()
|
||||||
|
} else if (idRange.end > idRange.start) {
|
||||||
|
params[Parameter.RANGE] = "${idRange.start}-${idRange.end}"
|
||||||
|
} else {
|
||||||
|
throw IllegalArgumentException("Invalid ID Range: ${idRange.start}, ${idRange.end}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Amount
|
||||||
|
if (amount > 1) {
|
||||||
|
params[Parameter.AMOUNT] = amount.toString()
|
||||||
|
} else if (amount <= 0) {
|
||||||
|
throw IllegalArgumentException("Invalid Amount: $amount")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safe
|
||||||
|
if (safe) {
|
||||||
|
params[Parameter.SAFE] = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return JokeApi.apiCall("joke", path, params, auth)
|
||||||
|
}
|
||||||
|
|
|
@ -53,9 +53,10 @@ class JokeConfig private constructor(
|
||||||
val format: Format,
|
val format: Format,
|
||||||
val search: String,
|
val search: String,
|
||||||
val idRange: IdRange,
|
val idRange: IdRange,
|
||||||
val amount: Int = 1,
|
val amount: Int,
|
||||||
val safe: Boolean,
|
val safe: Boolean,
|
||||||
val splitNewLine: Boolean,
|
val splitNewLine: Boolean,
|
||||||
|
val auth: String
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* [Builds][build] a new configuration.
|
* [Builds][build] a new configuration.
|
||||||
|
@ -74,7 +75,8 @@ class JokeConfig private constructor(
|
||||||
var idRange: IdRange = IdRange(),
|
var idRange: IdRange = IdRange(),
|
||||||
var amount: Int = 1,
|
var amount: Int = 1,
|
||||||
var safe: Boolean = false,
|
var safe: Boolean = false,
|
||||||
var splitNewLine: Boolean = false
|
var splitNewLine: Boolean = false,
|
||||||
|
var auth: String = ""
|
||||||
) {
|
) {
|
||||||
fun categories(categories: Set<Category>) = apply { this.categories = categories }
|
fun categories(categories: Set<Category>) = apply { this.categories = categories }
|
||||||
fun language(language: Language) = apply { this.language = language }
|
fun language(language: Language) = apply { this.language = language }
|
||||||
|
@ -86,9 +88,10 @@ class JokeConfig private constructor(
|
||||||
fun amount(amount: Int) = apply { this.amount = amount }
|
fun amount(amount: Int) = apply { this.amount = amount }
|
||||||
fun safe(safe: Boolean) = apply { this.safe = safe }
|
fun safe(safe: Boolean) = apply { this.safe = safe }
|
||||||
fun splitNewLine(splitNewLine: Boolean) = apply { this.splitNewLine = splitNewLine }
|
fun splitNewLine(splitNewLine: Boolean) = apply { this.splitNewLine = splitNewLine }
|
||||||
|
fun auth(auth: String) = apply { this.auth = auth }
|
||||||
|
|
||||||
fun build() = JokeConfig(
|
fun build() = JokeConfig(
|
||||||
categories, language, flags, type, format, search, idRange, amount, safe, splitNewLine
|
categories, language, flags, type, format, search, idRange, amount, safe, splitNewLine, auth
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ class JokeException @JvmOverloads constructor(
|
||||||
val additionalInfo: String,
|
val additionalInfo: String,
|
||||||
val timestamp: Long,
|
val timestamp: Long,
|
||||||
cause: Throwable? = null
|
cause: Throwable? = null
|
||||||
) : Exception(message, cause) {
|
) : RuntimeException(message, cause) {
|
||||||
companion object {
|
companion object {
|
||||||
private const val serialVersionUID = 1L
|
private const val serialVersionUID = 1L
|
||||||
}
|
}
|
||||||
|
|
172
src/main/kotlin/net/thauvin/erik/jokeapi/util.kt
Normal file
172
src/main/kotlin/net/thauvin/erik/jokeapi/util.kt
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
/*
|
||||||
|
* util.kt
|
||||||
|
*
|
||||||
|
* Copyright (c) 2022, Erik C. Thauvin (erik@thauvin.net)
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* Neither the name of this project nor the names of its contributors may be
|
||||||
|
* used to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.thauvin.erik.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 (Linux x86_64; rv:105.0) Gecko/20100101 Firefox/105.0"
|
||||||
|
)
|
||||||
|
if (auth.isNotEmpty()) {
|
||||||
|
connection.setRequestProperty("Authentication", auth)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connection.responseCode in 200..399) {
|
||||||
|
val body = connection.inputStream.bufferedReader().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<String>(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<String>()
|
||||||
|
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<Flag>()
|
||||||
|
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"),
|
||||||
|
language = Language.valueOf(json.getString(Parameter.LANG).uppercase())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -44,8 +44,6 @@ import assertk.assertions.isNull
|
||||||
import assertk.assertions.prop
|
import assertk.assertions.prop
|
||||||
import assertk.assertions.size
|
import assertk.assertions.size
|
||||||
import assertk.assertions.startsWith
|
import assertk.assertions.startsWith
|
||||||
import net.thauvin.erik.jokeapi.JokeApi.Companion.fetchUrl
|
|
||||||
import net.thauvin.erik.jokeapi.JokeApi.Companion.getJoke
|
|
||||||
import net.thauvin.erik.jokeapi.JokeApi.Companion.logger
|
import net.thauvin.erik.jokeapi.JokeApi.Companion.logger
|
||||||
import net.thauvin.erik.jokeapi.exceptions.HttpErrorException
|
import net.thauvin.erik.jokeapi.exceptions.HttpErrorException
|
||||||
import net.thauvin.erik.jokeapi.exceptions.JokeException
|
import net.thauvin.erik.jokeapi.exceptions.JokeException
|
||||||
|
@ -59,8 +57,6 @@ import java.util.logging.ConsoleHandler
|
||||||
import java.util.logging.Level
|
import java.util.logging.Level
|
||||||
|
|
||||||
internal class ExceptionsTest {
|
internal class ExceptionsTest {
|
||||||
private val httpStat = "https://httpstat.us"
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Validate Joke Exception`() {
|
fun `Validate Joke Exception`() {
|
||||||
val e = assertThrows<JokeException> {
|
val e = assertThrows<JokeException> {
|
||||||
|
@ -78,29 +74,19 @@ internal class ExceptionsTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest(name = "HTTP Status Code: {0}")
|
@ParameterizedTest
|
||||||
@ValueSource(ints = [400, 404, 403, 413, 414, 429, 500, 523])
|
@ValueSource(ints = [400, 404, 403, 413, 414, 429, 500, 523, 666])
|
||||||
fun `Validate HTTP Error Exceptions`(input: Int) {
|
fun `Validate HTTP Exceptions`(code: Int) {
|
||||||
val e = assertThrows<HttpErrorException> {
|
val e = assertThrows<HttpErrorException> {
|
||||||
fetchUrl("$httpStat/$input")
|
fetchUrl("https://httpstat.us/$code")
|
||||||
}
|
}
|
||||||
assertThat(e, "fetchUrl($httpStat/$input)").all {
|
assertThat(e, "fetchUrl($code)").all {
|
||||||
prop(HttpErrorException::statusCode).isEqualTo(input)
|
prop(HttpErrorException::statusCode).isEqualTo(code)
|
||||||
prop(HttpErrorException::message).isNotNull().isNotEmpty()
|
prop(HttpErrorException::message).isNotNull().isNotEmpty()
|
||||||
prop(HttpErrorException::cause).isNotNull().assertThat(Throwable::message).isNotNull()
|
if (code < 600)
|
||||||
}
|
prop(HttpErrorException::cause).isNotNull().assertThat(Throwable::message).isNotNull()
|
||||||
}
|
else
|
||||||
|
prop(HttpErrorException::cause).isNull()
|
||||||
@Test
|
|
||||||
fun `Fetch Invalid URL`() {
|
|
||||||
val statusCode = 999
|
|
||||||
val e = assertThrows<HttpErrorException> {
|
|
||||||
fetchUrl("$httpStat/$statusCode")
|
|
||||||
}
|
|
||||||
assertThat(e, "fetchUrl($httpStat/$statusCode).statusCode").all {
|
|
||||||
prop(HttpErrorException::statusCode).isEqualTo(statusCode)
|
|
||||||
prop(HttpErrorException::message).isNotNull().isNotEmpty()
|
|
||||||
prop(HttpErrorException::cause).isNull()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,6 @@ import assertk.assertions.isNotNull
|
||||||
import assertk.assertions.isTrue
|
import assertk.assertions.isTrue
|
||||||
import assertk.assertions.prop
|
import assertk.assertions.prop
|
||||||
import assertk.assertions.size
|
import assertk.assertions.size
|
||||||
import net.thauvin.erik.jokeapi.JokeApi.Companion.getJoke
|
|
||||||
import net.thauvin.erik.jokeapi.JokeApi.Companion.logger
|
import net.thauvin.erik.jokeapi.JokeApi.Companion.logger
|
||||||
import net.thauvin.erik.jokeapi.exceptions.JokeException
|
import net.thauvin.erik.jokeapi.exceptions.JokeException
|
||||||
import net.thauvin.erik.jokeapi.models.Category
|
import net.thauvin.erik.jokeapi.models.Category
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* GetJokeTest.kt
|
* GetJokeTests.kt
|
||||||
*
|
*
|
||||||
* Copyright (c) 2022, Erik C. Thauvin (erik@thauvin.net)
|
* Copyright (c) 2022, Erik C. Thauvin (erik@thauvin.net)
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
|
@ -44,7 +44,6 @@ import assertk.assertions.isNotNull
|
||||||
import assertk.assertions.isTrue
|
import assertk.assertions.isTrue
|
||||||
import assertk.assertions.prop
|
import assertk.assertions.prop
|
||||||
import assertk.assertions.size
|
import assertk.assertions.size
|
||||||
import net.thauvin.erik.jokeapi.JokeApi.Companion.getJokes
|
|
||||||
import net.thauvin.erik.jokeapi.JokeApi.Companion.logger
|
import net.thauvin.erik.jokeapi.JokeApi.Companion.logger
|
||||||
import net.thauvin.erik.jokeapi.models.Joke
|
import net.thauvin.erik.jokeapi.models.Joke
|
||||||
import net.thauvin.erik.jokeapi.models.Language
|
import net.thauvin.erik.jokeapi.models.Language
|
||||||
|
@ -79,10 +78,6 @@ internal class GetJokesTest {
|
||||||
@Test
|
@Test
|
||||||
fun `Get One Joke as Multiple`() {
|
fun `Get One Joke as Multiple`() {
|
||||||
val jokes = getJokes(amount = 1, safe = true)
|
val jokes = getJokes(amount = 1, safe = true)
|
||||||
jokes.forEach {
|
|
||||||
println(it.joke.joinToString("\n"))
|
|
||||||
println("-".repeat(46))
|
|
||||||
}
|
|
||||||
assertThat(jokes, "jokes").all {
|
assertThat(jokes, "jokes").all {
|
||||||
size().isEqualTo(1)
|
size().isEqualTo(1)
|
||||||
index(0).all {
|
index(0).all {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* GetRawJokeTest.kt
|
* GetRawJokesTest.kt
|
||||||
*
|
*
|
||||||
* Copyright (c) 2022, Erik C. Thauvin (erik@thauvin.net)
|
* Copyright (c) 2022, Erik C. Thauvin (erik@thauvin.net)
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
|
@ -37,9 +37,9 @@ import assertk.assertThat
|
||||||
import assertk.assertions.doesNotContain
|
import assertk.assertions.doesNotContain
|
||||||
import assertk.assertions.isNotEmpty
|
import assertk.assertions.isNotEmpty
|
||||||
import assertk.assertions.startsWith
|
import assertk.assertions.startsWith
|
||||||
import net.thauvin.erik.jokeapi.JokeApi.Companion.getRawJokes
|
|
||||||
import net.thauvin.erik.jokeapi.JokeApi.Companion.logger
|
import net.thauvin.erik.jokeapi.JokeApi.Companion.logger
|
||||||
import net.thauvin.erik.jokeapi.models.Format
|
import net.thauvin.erik.jokeapi.models.Format
|
||||||
|
import net.thauvin.erik.jokeapi.models.IdRange
|
||||||
import org.junit.jupiter.api.BeforeAll
|
import org.junit.jupiter.api.BeforeAll
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import java.util.logging.ConsoleHandler
|
import java.util.logging.ConsoleHandler
|
||||||
|
@ -74,6 +74,12 @@ internal class GetRawJokesTest {
|
||||||
assertContains(response, "\"amount\": 2", false, "getRawJoke(2)")
|
assertContains(response, "\"amount\": 2", false, "getRawJoke(2)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Get Raw Invalid Jokes`() {
|
||||||
|
val response = getRawJokes(search = "foo", safe = true, amount = 2, idRange = IdRange(160, 161))
|
||||||
|
assertContains(response, "\"error\": true", false, "getRawJokes(foo)")
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
|
|
|
@ -60,6 +60,15 @@ import java.util.logging.Level
|
||||||
import kotlin.test.assertContains
|
import kotlin.test.assertContains
|
||||||
|
|
||||||
class JokeConfigTest {
|
class JokeConfigTest {
|
||||||
|
@Test
|
||||||
|
fun `Get Joke with Default Builder`() {
|
||||||
|
val joke = getJoke()
|
||||||
|
assertThat(joke, "joke").all {
|
||||||
|
prop(Joke::id).isGreaterThanOrEqualTo(0)
|
||||||
|
prop(Joke::language).isEqualTo(Language.EN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Get Joke with Builder`() {
|
fun `Get Joke with Builder`() {
|
||||||
val id = 266
|
val id = 266
|
||||||
|
@ -132,6 +141,47 @@ class JokeConfigTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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)
|
||||||
|
language(language)
|
||||||
|
flags(flags)
|
||||||
|
type(type)
|
||||||
|
format(format)
|
||||||
|
search(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::search).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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
|
|
73
src/test/kotlin/net/thauvin/erik/jokeapi/UtilTest.kt
Normal file
73
src/test/kotlin/net/thauvin/erik/jokeapi/UtilTest.kt
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* FetchUrlTest.kt
|
||||||
|
*
|
||||||
|
* Copyright (c) 2022, Erik C. Thauvin (erik@thauvin.net)
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* Neither the name of this project nor the names of its contributors may be
|
||||||
|
* used to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.thauvin.erik.jokeapi
|
||||||
|
|
||||||
|
import assertk.assertThat
|
||||||
|
import assertk.assertions.contains
|
||||||
|
import org.json.JSONException
|
||||||
|
import org.json.JSONObject
|
||||||
|
import org.junit.jupiter.api.BeforeAll
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.assertThrows
|
||||||
|
import java.util.logging.ConsoleHandler
|
||||||
|
import java.util.logging.Level
|
||||||
|
|
||||||
|
class UtilTest {
|
||||||
|
@Test
|
||||||
|
fun `Invalid JSON Error`() {
|
||||||
|
assertThrows<JSONException> { parseError(JSONObject("{}")) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Invalid JSON Joke`() {
|
||||||
|
assertThrows<JSONException> { 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\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
@BeforeAll
|
||||||
|
fun beforeAll() {
|
||||||
|
with(JokeApi.logger) {
|
||||||
|
addHandler(ConsoleHandler().apply { level = Level.FINE })
|
||||||
|
level = Level.FINE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue