Skip to content

Commit d91cfb7

Browse files
authored
Merge pull request #16 from johnjohndoe/engelsystem-repositories
Add SimpleEngelsystemRepository in separate "engelsystem-repositories" library artifact.
2 parents 80a3ca4 + 8465220 commit d91cfb7

16 files changed

Lines changed: 693 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
### Changes
88

9+
* Update deployment to handle both modules.
10+
* **New:** Add `SimpleEngelsystemRepository` in separate "engelsystem-repositories" library artifact.
911
* **Breaking changes:** Let `EngelsystemService#getShifts` return `Response<List<Shift>>`.
1012
* Add `eTag` and `lastModifiedAt` parameters.
1113
* Expose `retrofit2.Response` in library API.

README.md

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,67 @@ A Kotlin library containing a parser and models for the Engelsystem:
1111

1212
## Usage
1313

14+
The library is published as two separate artifacts: `engelsystem-base` and `engelsystem-repositories`.
15+
You can use either of them depending on your needs.
16+
17+
#### Usage of `engelsystem-base`
18+
19+
The `engelsystem-base` artifact returns a `Response<List<Shift>>` type from the suspending
20+
`EngelsystemService#getShifts()` function.
21+
1422
```kotlin
15-
val engelsystemApi: EngelsystemApi = Api
16-
engelsystemApi.provideEngelsystemService(BASE_URL, okHttpClient)
17-
.getShifts(
18-
eTag = "", // Pass an empty string or a previous ETag value for caching
19-
lastModifiedAt = "", // Pass an empty string or a previous Last-Modified value for caching
20-
path = URL_PART_PATH,
21-
apiKey = API_KEY,
22-
)
23+
val api: EngelsystemApi = Api
24+
val service: EngelsystemService = api.provideEngelsystemService(BASE_URL, okHttpClient)
25+
service.getShifts(
26+
eTag = "", // Pass an empty string or a previous ETag value for caching
27+
lastModifiedAt = "", // Pass an empty string or a previous Last-Modified value for caching
28+
path = URL_PART_PATH,
29+
apiKey = API_KEY,
30+
)
31+
if (response.isSuccessful) {
32+
val shifts = response.body()
33+
val responseETag = response.headers()["ETag"]
34+
val responseLastModifiedAt = response.headers()["Last-Modified"]
35+
} else {
36+
val errorCode = response.code()
37+
val errorMessage = response.message()
38+
}
2339
```
2440

41+
The `engelsystem-repositories` artifact returns a `Flow<GetShiftsState>` type from the suspending
42+
`SimpleEngelsystemRepository#getShiftsState()` function.
43+
44+
```kotlin
45+
val api: EngelsystemApi = Api
46+
val repository = SimpleEngelsystemRepository(
47+
callFactory = OkHttpClient.Builder().build(),
48+
api = api,
49+
)
50+
51+
repository.getShiftsState(
52+
requestETag = "", // Pass an empty string or a previous ETag value for caching
53+
requestLastModifiedAt = "", // Pass an empty string or a previous Last-Modified value for caching
54+
baseUrl = BASE_URL,
55+
path = URL_PART_PATH,
56+
apiKey = API_KEY,
57+
)
58+
.collectLatest { state: GetShiftsState ->
59+
when (state) {
60+
is Success -> {
61+
val shifts = state.shifts
62+
val responseETag = state.responseETag
63+
val responseLastModifiedAt = state.responseLastModifiedAt
64+
}
65+
is Error -> {
66+
val httpStatusCode = state.httpStatusCode
67+
val errorMessage = state.errorMessage
68+
}
69+
is Failure -> {
70+
val throwable = state.throwable
71+
}
72+
}
73+
}
74+
```
2575

2676
### Time stamps
2777

@@ -48,12 +98,13 @@ allprojects {
4898
}
4999
```
50100

51-
and to your application module `build.gradle`:
101+
and one of the following dependencies to your application module `build.gradle`:
52102

53103

54104
```groovy
55105
dependencies {
56106
implementation "info.metadude.kotlin.library.engelsystem:engelsystem-base:$version"
107+
implementation "info.metadude.kotlin.library.engelsystem:engelsystem-repositories:$version"
57108
}
58109
```
59110

buildSrc/src/main/kotlin/info/metadude/kotlin/library/engelsystem/Dependencies.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,29 @@ object Libs {
2323
const val junitJupiter = "5.12.2"
2424
const val junitPlatformLauncher = "1.12.2"
2525
const val kotlinCoroutines = "1.10.2"
26+
const val mockitoKotlin = "5.4.0"
2627
const val moshi = "1.15.2"
2728
const val okhttp = "4.12.0"
2829
const val retrofit = "2.11.0"
2930
const val threetenbp = "1.7.1"
3031
const val truth = "1.4.4"
32+
const val turbine = "1.2.0"
3133
}
3234

3335
const val junitJupiterApi = "org.junit.jupiter:junit-jupiter-api:${Versions.junitJupiter}"
3436
const val junitJupiterEngine = "org.junit.jupiter:junit-jupiter-engine:${Versions.junitJupiter}"
3537
const val junitPlatformLauncher = "org.junit.platform:junit-platform-launcher:${Versions.junitPlatformLauncher}"
3638
const val kotlinCoroutinesCore = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.kotlinCoroutines}"
3739
const val kotlinCoroutinesTest = "org.jetbrains.kotlinx:kotlinx-coroutines-test:${Versions.kotlinCoroutines}"
40+
const val mockitoKotlin = "org.mockito.kotlin:mockito-kotlin:${Versions.mockitoKotlin}"
3841
const val moshi = "com.squareup.moshi:moshi:${Versions.moshi}"
3942
const val moshiKotlinCodegen = "com.squareup.moshi:moshi-kotlin-codegen:${Versions.moshi}"
4043
const val okhttp = "com.squareup.okhttp3:okhttp:${Versions.okhttp}"
4144
const val okhttpLoggingInterceptor = "com.squareup.okhttp3:logging-interceptor:${Versions.okhttp}"
45+
const val okhttpMockWebServer = "com.squareup.okhttp3:mockwebserver:${Versions.okhttp}"
4246
const val retrofit = "com.squareup.retrofit2:retrofit:${Versions.retrofit}"
4347
const val retrofitMoshi = "com.squareup.retrofit2:converter-moshi:${Versions.retrofit}"
4448
const val threetenbp = "org.threeten:threetenbp:${Versions.threetenbp}"
4549
const val truth = "com.google.truth:truth:${Versions.truth}"
50+
const val turbine = "app.cash.turbine:turbine:${Versions.turbine}"
4651
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import info.metadude.kotlin.library.engelsystem.Libs
2+
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
3+
import org.gradle.api.tasks.testing.logging.TestLogEvent
4+
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
5+
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
6+
7+
apply plugin: "java-library"
8+
apply plugin: "kotlin"
9+
10+
compileJava {
11+
sourceCompatibility = JavaVersion.VERSION_17
12+
targetCompatibility = JavaVersion.VERSION_17
13+
}
14+
15+
tasks.withType(KotlinCompile).configureEach {
16+
compilerOptions {
17+
jvmTarget = JvmTarget.JVM_17
18+
}
19+
}
20+
21+
dependencies {
22+
api project(":engelsystem-base")
23+
implementation Libs.kotlinCoroutinesCore
24+
api Libs.okhttp
25+
26+
testImplementation Libs.junitJupiterApi
27+
testRuntimeOnly Libs.junitJupiterEngine
28+
testRuntimeOnly Libs.junitPlatformLauncher
29+
testImplementation Libs.kotlinCoroutinesTest
30+
testImplementation Libs.mockitoKotlin
31+
testImplementation Libs.okhttpMockWebServer
32+
testImplementation Libs.retrofitMoshi
33+
testImplementation Libs.truth
34+
testImplementation Libs.turbine
35+
}
36+
37+
test {
38+
useJUnitPlatform()
39+
testLogging {
40+
events TestLogEvent.FAILED,
41+
TestLogEvent.PASSED,
42+
TestLogEvent.SKIPPED,
43+
TestLogEvent.STANDARD_ERROR,
44+
TestLogEvent.STANDARD_OUT
45+
exceptionFormat TestExceptionFormat.FULL
46+
showCauses true
47+
showExceptions true
48+
showStackTraces true
49+
}
50+
}
51+
52+
apply from: rootProject.file("gradle/deploy.gradle")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
moduleArtifactId=engelsystem-repositories
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package info.metadude.kotlin.library.engelsystem.repositories
2+
3+
import info.metadude.kotlin.library.engelsystem.repositories.models.GetShiftsState
4+
import kotlinx.coroutines.flow.Flow
5+
6+
interface EngelsystemRepository {
7+
8+
suspend fun getShiftsState(
9+
requestETag: String,
10+
requestLastModifiedAt: String,
11+
baseUrl: String,
12+
path: String,
13+
apiKey: String,
14+
): Flow<GetShiftsState>
15+
16+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package info.metadude.kotlin.library.engelsystem.repositories.models
2+
3+
import info.metadude.kotlin.library.engelsystem.models.Shift
4+
import java.net.HttpURLConnection
5+
6+
sealed interface GetShiftsState {
7+
8+
data class Success(
9+
val shifts: List<Shift>,
10+
val responseETag: String,
11+
val responseLastModifiedAt: String,
12+
) : GetShiftsState
13+
14+
data class Error(
15+
val httpStatusCode: Int,
16+
val errorMessage: String,
17+
) : GetShiftsState {
18+
val isNotModified = httpStatusCode == HttpURLConnection.HTTP_NOT_MODIFIED
19+
}
20+
21+
data class Failure(
22+
val throwable: Throwable,
23+
) : GetShiftsState {
24+
override fun toString() = "Failure(throwable=$throwable)"
25+
}
26+
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package info.metadude.kotlin.library.engelsystem.repositories.simple
2+
3+
import info.metadude.kotlin.library.engelsystem.EngelsystemApi
4+
import info.metadude.kotlin.library.engelsystem.repositories.EngelsystemRepository
5+
import info.metadude.kotlin.library.engelsystem.repositories.models.GetShiftsState
6+
import info.metadude.kotlin.library.engelsystem.repositories.models.GetShiftsState.Error
7+
import info.metadude.kotlin.library.engelsystem.repositories.models.GetShiftsState.Failure
8+
import info.metadude.kotlin.library.engelsystem.repositories.models.GetShiftsState.Success
9+
import kotlinx.coroutines.flow.Flow
10+
import kotlinx.coroutines.flow.flow
11+
import okhttp3.Call
12+
13+
class SimpleEngelsystemRepository(
14+
private val callFactory: Call.Factory,
15+
private val api: EngelsystemApi,
16+
) : EngelsystemRepository {
17+
18+
private companion object {
19+
const val HEADER_NAME_ETAG = "ETag"
20+
const val HEADER_NAME_LAST_MODIFIED = "Last-Modified"
21+
}
22+
23+
override suspend fun getShiftsState(
24+
requestETag: String,
25+
requestLastModifiedAt: String,
26+
baseUrl: String,
27+
path: String,
28+
apiKey: String
29+
): Flow<GetShiftsState> {
30+
return flow {
31+
val emission = try {
32+
val response = api
33+
.provideEngelsystemService(baseUrl, callFactory)
34+
.getShifts(requestETag, requestLastModifiedAt, path, apiKey)
35+
if (response.isSuccessful) {
36+
Success(
37+
shifts = response.body().orEmpty(),
38+
responseETag = response.headers()[HEADER_NAME_ETAG].orEmpty(),
39+
responseLastModifiedAt = response.headers()[HEADER_NAME_LAST_MODIFIED].orEmpty(),
40+
)
41+
} else {
42+
val errorMessage = response.errorBody()?.string() ?: response.message().orEmpty()
43+
Error(
44+
httpStatusCode = response.code(),
45+
errorMessage = errorMessage,
46+
)
47+
}
48+
} catch (t: Throwable) {
49+
Failure(throwable = t)
50+
}
51+
emit(emission)
52+
}
53+
}
54+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package info.metadude.kotlin.library.engelsystem.repositories.simple
2+
3+
import info.metadude.kotlin.library.engelsystem.Api
4+
import kotlinx.coroutines.flow.collectLatest
5+
import okhttp3.OkHttpClient
6+
7+
internal suspend fun main() {
8+
SimpleEngelsystemRepository(
9+
callFactory = OkHttpClient.Builder().build(),
10+
api = Api,
11+
)
12+
.getShiftsState(
13+
requestETag = "",
14+
requestLastModifiedAt = "",
15+
baseUrl = "https://staging.engelsystem.de",
16+
path = "test/shifts-json-export",
17+
apiKey = "b8bb16fcd23500f2054480faad8df9db0884280fae8242182530e982cf76e87b",
18+
)
19+
.collectLatest { println("$it") }
20+
21+
println("Done.")
22+
23+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package info.metadude.kotlin.library.engelsystem.services
2+
3+
import info.metadude.kotlin.library.engelsystem.EngelsystemService
4+
import info.metadude.kotlin.library.engelsystem.models.Shift
5+
import okhttp3.MediaType.Companion.toMediaType
6+
import okhttp3.ResponseBody.Companion.toResponseBody
7+
import retrofit2.Response
8+
9+
class ImmediatelyFailingService : EngelsystemService {
10+
11+
override suspend fun getShifts(
12+
eTag: String,
13+
lastModifiedAt: String,
14+
path: String,
15+
apiKey: String,
16+
): Response<List<Shift>> {
17+
val responseBody = "Service Unavailable.".toResponseBody("plain/text".toMediaType())
18+
return Response.error(503, responseBody)
19+
}
20+
21+
}

0 commit comments

Comments
 (0)