Skip to content

Commit

Permalink
Merge pull request #3 from infinum/fix/tag-options
Browse files Browse the repository at this point in the history
Fix/tag options
  • Loading branch information
bojankoma authored Jan 22, 2024
2 parents 0af19cf + cc92c2a commit f46c641
Show file tree
Hide file tree
Showing 23 changed files with 276 additions and 173 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
Changelog
=========

## Version 0.0.5

_TBD_

* Tag options per Retrofit service interface method.

## Version 0.0.4

_2023-03-31_
Expand Down
36 changes: 20 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath "com.infinum.halley:halley-plugin:0.0.4"
classpath "com.infinum.halley:halley-plugin:0.0.5"
}
}
```
Expand All @@ -58,7 +58,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath("com.infinum.halley:halley-plugin:0.0.4")
classpath("com.infinum.halley:halley-plugin:0.0.5")
}
}
```
Expand All @@ -85,57 +85,57 @@ Now you can sync your project and core features like serialization and deseriali
**Groovy**

```groovy
implementation "com.infinum.halley:halley-core:0.0.4"
implementation "com.infinum.halley:halley-core:0.0.5"
```

**KotlinDSL**

```kotlin
implementation("com.infinum.halley:halley-core:0.0.4")
implementation("com.infinum.halley:halley-core:0.0.5")
```

### Retrofit

**Groovy**

```groovy
implementation "com.infinum.halley:halley-retrofit:0.0.4"
implementation "com.infinum.halley:halley-retrofit:0.0.5"
```

**KotlinDSL**

```kotlin
implementation("com.infinum.halley:halley-retrofit:0.0.4")
implementation("com.infinum.halley:halley-retrofit:0.0.5")
```

### Ktor

**Groovy**

```groovy
implementation "com.infinum.halley:halley-ktor:0.0.4"
implementation "com.infinum.halley:halley-ktor:0.0.5"
```

**KotlinDSL**

```kotlin
implementation("com.infinum.halley:halley-ktor:0.0.4")
implementation("com.infinum.halley:halley-ktor:0.0.5")
```

### Retrofit and Ktor together

**Groovy**

```groovy
implementation "com.infinum.halley:halley-retrofit:0.0.4"
implementation "com.infinum.halley:halley-ktor:0.0.4"
implementation "com.infinum.halley:halley-retrofit:0.0.5"
implementation "com.infinum.halley:halley-ktor:0.0.5"
```

**KotlinDSL**

```kotlin
implementation("com.infinum.halley:halley-retrofit:0.0.4")
implementation("com.infinum.halley:halley-ktor:0.0.4")
implementation("com.infinum.halley:halley-retrofit:0.0.5")
implementation("com.infinum.halley:halley-ktor:0.0.5")
```

## Usage
Expand Down Expand Up @@ -265,6 +265,8 @@ val actual: HalModel = halley.decodeFromString(
)
```
### Retrofit
When using _Halley_ with Retrofit, it is important to ensure that each Retrofit service interface method is annotated with `@HalTag`.
The value (any `String` you've chosen) set for the tag will later be used to match the method with the corresponding option arguments.
#### Annotated option arguments in Retrofit service interface
```kotlin
@GET("/Profile/self")
Expand Down Expand Up @@ -293,19 +295,21 @@ val actual: HalModel = halley.decodeFromString(
)
]
)
fun profileWithOptionsFromAnnotation(): Call<ProfileResource>
@HalTag("profileWithAnnotatedOptions")
fun profileWithAnnotatedOptions(): Call<ProfileResource>
```
#### Imperative option arguments before Retrofit method call
When setting options imperatively, it is important to ensure that you have used the same **tag** value as the one set in the Retrofit service interface method you intend to use the options for.
```kotlin
private fun fetchProfile() {
// Halley for Retrofit provides convenience halleyQueryOptions functions
halleyQueryOptions {
halleyQueryOptions(tag = "profileWithImperativeOptions") {
mapOf("animal" to mapOf("country" to "Brazil"))
}
halleyTemplateOptions {
halleyTemplateOptions(tag = "profileWithImperativeOptions") {
mapOf("animal" to mapOf("id" to "1"))
}
webServer.client()?.service?.profileWithOptionsFromCache()
webServer.client()?.service?.profileWithImperativeOptions()
?.enqueue(object : Callback<ProfileResource> {
override fun onResponse(
call: Call<ProfileResource>,
Expand Down
2 changes: 1 addition & 1 deletion config.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
ext {
def major = 0
def minor = 0
def patch = 4
def patch = 5

buildConfig = [
"minSdk" : 21,
Expand Down
1 change: 1 addition & 0 deletions cpd.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ cpdCheck {
xml.required = true
}
source = allprojects*.file("src/main/kotlin")
minimumTokenCount = 100
}
1 change: 1 addition & 0 deletions dokka.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
apply plugin: "org.jetbrains.dokka"

tasks.named("dokkaJavadoc") {
dependsOn(":halley-plugin:pluginVersion")
outputDirectory.set(file("$buildDir/javadoc"))

dokkaSourceSets {
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[versions]
halley = "0.0.4"
halley = "0.0.5"
gradle = "7.4.2"
kotlin = "1.8.10"
serialization = "1.5.0"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.infinum.halley.retrofit.annotations

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
@MustBeDocumented
public annotation class HalTag(
val value: String
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,81 @@ package com.infinum.halley.retrofit.cache
import com.infinum.halley.core.serializers.link.models.templated.params.Arguments
import com.infinum.halley.core.typealiases.HalleyKeyedMap
import com.infinum.halley.core.typealiases.HalleyMap
import com.infinum.halley.retrofit.converters.options.HalleyOptionsFactory

internal object HalleyOptions {

internal var common: Arguments.Common? = null
internal var query: Arguments.Query? = null
internal var template: Arguments.Template? = null
/*
HalleyOptions are constructed per call site (Retrofit service interface method) and stored in the cache.
Each Retrofit service interface method is identified by a tag.
*/
internal data class HalleyOptions(
var common: Arguments.Common? = null,
var query: Arguments.Query? = null,
var template: Arguments.Template? = null

) {
internal fun common(): Arguments.Common? = this.common

internal fun query(): Arguments.Query? = this.query

internal fun template(): Arguments.Template? = this.template
}

public fun halleyCommonOptions(value: () -> HalleyMap) {
HalleyOptions.apply {
this.common = Arguments.Common(value.invoke())
}
public fun halleyCommonOptions(tag: String, value: () -> HalleyMap) {
val exists = HalleyOptionsCache.check(tag)

val newOption: HalleyOptions =
if (exists) {
val option = HalleyOptionsCache.get(tag)
option?.copy(
common = Arguments.Common(value.invoke())
) ?: HalleyOptions(
common = Arguments.Common(value.invoke())
)
} else {
HalleyOptions(
common = Arguments.Common(value.invoke())
)
}

HalleyOptionsCache.put(tag, newOption)
}

public fun halleyQueryOptions(value: () -> HalleyKeyedMap) {
HalleyOptions.apply {
this.query = Arguments.Query(value.invoke())
}
public fun halleyQueryOptions(tag: String, value: () -> HalleyKeyedMap) {
val exists = HalleyOptionsCache.check(tag)

val newOption: HalleyOptions =
if (exists) {
val option = HalleyOptionsCache.get(tag)
option?.copy(
query = Arguments.Query(value.invoke())
) ?: HalleyOptions(
query = Arguments.Query(value.invoke())
)
} else {
HalleyOptions(
query = Arguments.Query(value.invoke())
)
}

HalleyOptionsCache.put(tag, newOption)
}

public fun halleyTemplateOptions(value: () -> HalleyKeyedMap) {
HalleyOptions.apply {
this.template = Arguments.Template(value.invoke())
}
public fun halleyTemplateOptions(tag: String, value: () -> HalleyKeyedMap) {
val exists = HalleyOptionsCache.check(tag)

val newOption: HalleyOptions =
if (exists) {
val option = HalleyOptionsCache.get(tag)
option?.copy(
template = Arguments.Template(value.invoke())
) ?: HalleyOptions(
template = Arguments.Template(value.invoke())
)
} else {
HalleyOptions(
template = Arguments.Template(value.invoke())
)
}

HalleyOptionsCache.put(tag, newOption)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.infinum.halley.retrofit.cache

internal object HalleyOptionsCache {

private var cache = mapOf<String, HalleyOptions>()
fun check(tag: String): Boolean =
cache.containsKey(tag)

fun get(tag: String): HalleyOptions? =
cache[tag]

fun put(tag: String, option: HalleyOptions) {
cache = cache + mapOf(tag to option)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ internal class HalleyConverterFactory(
annotations: Array<out Annotation>,
retrofit: Retrofit
): Converter<ResponseBody, *>? {
HalleyOptionsFactory.setAnnotations(annotations)
val tag = HalleyOptionsFactory.setAnnotations(annotations)
return HalleyDeserializationConverter<HalResource>(
halley,
type
type,
tag
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,27 @@ package com.infinum.halley.retrofit.converters

import com.infinum.halley.core.Halley
import com.infinum.halley.core.serializers.hal.models.HalResource
import com.infinum.halley.retrofit.converters.options.HalleyOptionsFactory
import com.infinum.halley.retrofit.cache.HalleyOptionsCache
import java.lang.reflect.Type
import okhttp3.ResponseBody
import retrofit2.Converter

internal class HalleyDeserializationConverter<T : HalResource>(
private val halley: Halley,
private val type: Type
private val type: Type,
private val tag: String
) : Converter<ResponseBody, T> {

override fun convert(value: ResponseBody): T {
val options: Halley.Options? = HalleyOptionsFactory.options()
return halley.decodeFromString(type, value.string(), options)
return halley.decodeFromString(type, value.string(), getOptions())
}

private fun getOptions(): Halley.Options? =
HalleyOptionsCache.get(tag)?.let {
Halley.Options(
common = it.common(),
query = it.query(),
template = it.template()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,30 @@ import com.infinum.halley.core.serializers.link.models.templated.params.Argument
import com.infinum.halley.core.typealiases.HalleyMap
import com.infinum.halley.retrofit.annotations.HalArgumentEntry
import com.infinum.halley.retrofit.annotations.HalCommonArguments
import com.infinum.halley.retrofit.cache.HalleyOptions
import com.infinum.halley.retrofit.cache.HalleyOptionsCache

internal class HalleyCommonArgumentsFactory :
OptionFactory<Arguments.Common?, HalArgumentEntry, HalleyMap> {
internal class HalleyCommonArgumentsFactory : OptionFactory<Arguments.Common?, HalArgumentEntry, HalleyMap> {

override operator fun invoke(annotations: Array<out Annotation>): Arguments.Common? =
override operator fun invoke(tag: String, annotations: Array<out Annotation>): Arguments.Common? =
(annotations.find { it.annotationClass == HalCommonArguments::class } as? HalCommonArguments)?.let {
if (it.arguments.isNotEmpty()) {
/**
* Keys from cache overwrite the keys from annotations
*/
Arguments.Common(
annotationParameters(it.arguments) + cacheParameters("")
annotationParameters(it.arguments) + cacheParameters(tag, "")
)
} else {
cacheParameters("").takeIf { map -> map.isNotEmpty() }
cacheParameters(tag, "").takeIf { map -> map.isNotEmpty() }
?.let { map -> Arguments.Common(map) }
}
} ?: cacheParameters("").takeIf { it.isNotEmpty() }?.let { Arguments.Common(it) }
} ?: cacheParameters(tag, "").takeIf { it.isNotEmpty() }?.let { Arguments.Common(it) }

override fun annotationParameters(parameters: Array<HalArgumentEntry>): HalleyMap =
parameters.associate { parameter ->
parameter.key to parameter.value
}

override fun cacheParameters(key: String): HalleyMap =
HalleyOptions.common()?.mappedValues ?: mapOf()
override fun cacheParameters(tag: String, key: String): HalleyMap =
HalleyOptionsCache.get(tag)?.common()?.mappedValues ?: mapOf()
}
Loading

0 comments on commit f46c641

Please sign in to comment.