Skip to content

Commit eeb4fb5

Browse files
committed
add ScryfallError.kt and refactor api to use raise
1 parent e5e182c commit eeb4fb5

File tree

11 files changed

+94
-34
lines changed

11 files changed

+94
-34
lines changed

.idea/misc.xml

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli/src/main/kotlin/CliViewModel.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ class CliViewModel(
1010
private val importer: CollectionImporter,
1111
private val database: DatabaseHelper,
1212
private val api: ScryfallApi,
13-
private val composeLogger: ComposeLogger,
13+
composeLogger: ComposeLogger,
1414
) {
1515
val logs = composeLogger.logs
16+
// TODO Replace with raise and expose results to UI
1617
suspend fun translateCsv(filePath: String) {
1718
val succ = either {
1819
val imported = importer.parseCardCastle(filePath).bind()

collection-import/src/commonMain/kotlin/Import.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.mfriend.collection
22

33
import arrow.core.Either
44
import arrow.core.raise.either
5+
import arrow.core.raise.withError
56
import client.ScryfallApi
67
import com.github.doyaaaaaken.kotlincsv.dsl.csvReader
78
import com.github.doyaaaaaken.kotlincsv.dsl.csvWriter
@@ -17,10 +18,8 @@ interface CollectionImporter {
1718

1819
class CollectionImporterImpl(private val client: ScryfallApi) : CollectionImporter {
1920
override suspend fun parseCardCastle(fileName: String): Either<String, List<CardEntry>> = either {
20-
val setMap = client.sets()
21-
.map { sets ->
22-
sets.associate { it.name to it.code }
23-
}.bind()
21+
val setMap = withError({ it.details }) { client.setsRaise() }
22+
.associate { it.name to it.code }
2423

2524
return@either Either.catch {
2625
csvReader().openAsync(fileName) {

discord-bot/src/main/kotlin/DiscordBot.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import org.koin.core.context.GlobalContext.startKoin
77
suspend fun main() {
88
ScryfallApiImpl().use { scryfallApi ->
99
val x = either {
10-
scryfallApi.searchCardRaise("")
10+
scryfallApi.cardNamedRaise("black lotus")
1111
}
1212
println(x)
1313
}

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ sqlDelight = { id = "app.cash.sqldelight", version.ref = "sqlDelight" }
4141
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
4242
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
4343
compose-runtime = { id = "org.jetbrains.compose", version.ref = "jetbrains-compose" }
44-
ksp = { id = "com.google.devtools.ksp", version = "2.1.20-2.0.0" }
44+
ksp = { id = "com.google.devtools.ksp", version = "2.2.0-Beta2-2.0.1" }
4545

4646
[bundles]
4747
base = [

scryfall/src/commonMain/kotlin/client/ScryfallApi.kt

Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ package client
33
import arrow.core.Either
44
import arrow.core.left
55
import arrow.core.raise.Raise
6+
import arrow.core.raise.either
7+
import arrow.core.raise.withError
68
import arrow.core.right
79
import bind
810
import co.touchlab.kermit.Logger
9-
import ensure
1011
import io.ktor.client.call.body
1112
import io.ktor.client.request.HttpRequestBuilder
1213
import io.ktor.client.request.get
@@ -17,11 +18,27 @@ import io.ktor.http.isSuccess
1718
import io.ktor.http.takeFrom
1819
import models.CardDto
1920
import models.ListResp
21+
import models.ScryfallError
2022
import models.SetDto
23+
import raise
2124

2225
interface ScryfallApi : AutoCloseable {
26+
context(_: Raise<ScryfallError>)
27+
suspend fun cardNamedRaise(name: String): CardDto
28+
29+
context(_: Raise<ScryfallError>)
30+
suspend fun searchCardRaise(searchParam: String): List<CardDto>
31+
32+
context(_: Raise<ScryfallError>)
33+
suspend fun setsRaise(): List<SetDto>
34+
35+
@Deprecated("Use raise variant", replaceWith = ReplaceWith("cardNamedRaise(name)"))
2336
suspend fun cardNamed(name: String): Either<String, CardDto>
37+
38+
@Deprecated("Use raise variant", replaceWith = ReplaceWith("searchCardRaise(searchParam)"))
2439
suspend fun searchCard(searchParam: String): Either<String, List<CardDto>>
40+
41+
@Deprecated("Use raise variant", replaceWith = ReplaceWith("setsRaise()"))
2542
suspend fun sets(): Either<String, List<SetDto>>
2643
}
2744

@@ -37,34 +54,46 @@ inline fun <T> catch(block: () -> T): T {
3754

3855
class ScryfallApiImpl : ScryfallApi, AutoCloseable {
3956
private val client = newKtorClient()
40-
override suspend fun cardNamed(name: String): Either<String, CardDto> {
41-
val response = client.get {
42-
scryfall("$CardApiBase$FindNamed")
43-
parameter("fuzzy", name)
57+
58+
// TODO handle exceptions/responses that dont fit ScryfallError
59+
context(_: Raise<ScryfallError>)
60+
private suspend inline fun <reified T> HttpResponse.bodyOrError(): T {
61+
return if (status.isSuccess()) {
62+
body<T>()
63+
} else {
64+
raise(body<ScryfallError>())
4465
}
66+
}
4567

46-
return response.toEither()
68+
/**
69+
* Gets a single card object from the search or raises a [ScryfallError]
70+
* If the error response doesnt fit [ScryfallError] throws an exception
71+
*/
72+
context(_: Raise<ScryfallError>)
73+
override suspend fun cardNamedRaise(name: String): CardDto {
74+
return client.get {
75+
scryfall("$CardApiBase$FindNamed")
76+
parameter("fuzzy", name)
77+
}.bodyOrError()
4778
}
4879

80+
4981
// This NEEDS to be called or it will hold up the couroutine scope of suspend fun main
5082
override fun close() {
5183
client.close()
5284
}
5385

54-
context(_: Raise<Throwable>)
55-
suspend fun searchCardRaise(searchParam: String): List<CardDto> {
56-
return catch {
57-
val response = client.get {
58-
scryfall("$CardApiBase$Search")
59-
parameter("q", searchParam)
60-
}
61-
ensure(response.status.isSuccess()) {
62-
IllegalStateException(response.body<String>())
63-
}
64-
response.body<ListResp<CardDto>>().data
65-
}
66-
}
86+
context(_: Raise<ScryfallError>)
87+
override suspend fun searchCardRaise(searchParam: String): List<CardDto> = client.get {
88+
scryfall("$CardApiBase$Search")
89+
parameter("q", searchParam)
90+
}.bodyOrError<ListResp<CardDto>>().data
91+
92+
context(_: Raise<ScryfallError>)
93+
override suspend fun setsRaise(): List<SetDto> =
94+
client.get { scryfall("sets") }.bodyOrError<ListResp<SetDto>>().data
6795

96+
@Deprecated("Use raise variant", replaceWith = ReplaceWith("searchCardRaise(searchParam)"))
6897
override suspend fun searchCard(searchParam: String): Either<String, List<CardDto>> {
6998
val resp: HttpResponse = client.get {
7099
scryfall("$CardApiBase$Search")
@@ -77,13 +106,12 @@ class ScryfallApiImpl : ScryfallApi, AutoCloseable {
77106
.map { it.data }
78107
}
79108

80-
override suspend fun sets(): Either<String, List<SetDto>> {
81-
val resp: HttpResponse = client.get {
82-
scryfall("sets")
83-
}
84-
return resp
85-
.toEither<ListResp<SetDto>>()
86-
.map { it.data }
109+
@Deprecated("Use raise variant", replaceWith = ReplaceWith("setsRaise()"))
110+
override suspend fun sets(): Either<String, List<SetDto>> = either { withError({ it.details }) { setsRaise() } }
111+
112+
@Deprecated("Use raise variant", replaceWith = ReplaceWith("cardNamedRaise(name)"))
113+
override suspend fun cardNamed(name: String): Either<String, CardDto> = either {
114+
withError({ it.details }) { cardNamedRaise(name) }
87115
}
88116

89117
companion object {

scryfall/src/commonMain/kotlin/client/newKtorClient.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ internal fun newKtorClient(logLevel: LogLevel = LogLevel.NONE): HttpClient = Htt
1818

1919
private val defaultJson = Json {
2020
ignoreUnknownKeys = true
21+
classDiscriminator = "object"
2122
}

scryfall/src/commonMain/kotlin/models/CardDto.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import kotlinx.serialization.SerialName
44
import kotlinx.serialization.Serializable
55

66
@Serializable
7+
@SerialName("card")
78
data class CardDto(
89
val name: String,
910
val set: String,

scryfall/src/commonMain/kotlin/models/ListResp.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import kotlinx.serialization.SerialName
44
import kotlinx.serialization.Serializable
55

66
@Serializable
7+
@SerialName("list")
78
internal data class ListResp<out T>(
89
val data: List<T>,
910
@SerialName("has_more")
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package models
2+
3+
4+
import kotlinx.serialization.SerialName
5+
import kotlinx.serialization.Serializable
6+
7+
8+
// https://scryfall.com/docs/api/errors
9+
@Serializable
10+
@SerialName("error")
11+
data class ScryfallError(
12+
// String version of status code
13+
@SerialName("code")
14+
val code: String,
15+
// Http status code
16+
@SerialName("status")
17+
val status: Int,
18+
// Human-readable message
19+
@SerialName("details")
20+
val details: String,
21+
// Non fatal warnings with the response
22+
@SerialName("warnings")
23+
val warnings: List<String>,
24+
// This is a type param, can be used for polymorphic serialization
25+
// Looks like options are: error, set, list, card, ruling
26+
@SerialName("object")
27+
val responseType: String,
28+
val type: String? = null,
29+
)

0 commit comments

Comments
 (0)