Skip to content

Commit

Permalink
SDKS-3622 Add Accept-Language Header Support
Browse files Browse the repository at this point in the history
  • Loading branch information
witrisna committed Feb 4, 2025
1 parent c68b9a4 commit a30e42a
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 1 deletion.
1 change: 1 addition & 0 deletions davinci/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ dependencies {
testImplementation(libs.mockk)
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.ktor.client.mock)
testImplementation(libs.robolectric)

androidTestImplementation(libs.kotlin.test)
androidTestImplementation(libs.kotlinx.coroutines.test)
Expand Down
31 changes: 31 additions & 0 deletions davinci/src/main/kotlin/com/pingidentity/davinci/DaVinci.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

package com.pingidentity.davinci

import android.os.LocaleList
import com.pingidentity.davinci.module.NodeTransform
import com.pingidentity.davinci.module.Oidc
import com.pingidentity.davinci.plugin.DaVinci
Expand All @@ -17,6 +18,7 @@ import com.pingidentity.orchestrate.module.CustomHeader
// typealias DaVinciConfig = WorkflowConfig
private const val X_REQUESTED_WITH = "x-requested-with"
private const val X_REQUESTED_PLATFORM = "x-requested-platform"
private const val ACCEPT_LANGUAGE = "Accept-Language"

// Constants for header values
private const val PING_SDK = "ping-sdk"
Expand Down Expand Up @@ -49,6 +51,7 @@ fun DaVinci(block: DaVinciConfig.() -> Unit = {}): DaVinci {
module(CustomHeader) {
header(X_REQUESTED_WITH, PING_SDK)
header(X_REQUESTED_PLATFORM, ANDROID)
header(ACCEPT_LANGUAGE, LocaleList.getDefault().toAcceptLanguage())
}
module(NodeTransform)
//Module cookie has lower priority than Oidc, the Cookie module requires the request Url to be set
Expand All @@ -64,3 +67,31 @@ fun DaVinci(block: DaVinciConfig.() -> Unit = {}): DaVinci {

return DaVinci(config)
}

fun LocaleList.toAcceptLanguage(): String {
if (isEmpty) return ""

val languageTags = mutableListOf<String>()
var currentQValue = 0.9

(0 until size()).forEach { index ->
val locale = this[index]

// Add toLanguageTag version first
if (index == 0) {
languageTags.add(locale.toLanguageTag())
currentQValue = 0.9
} else {
languageTags.add("${locale.toLanguageTag()};q=%.1f".format(currentQValue))
currentQValue -= 0.1
}

// Add language version with next q-value
if (locale.toLanguageTag() != locale.language) {
languageTags.add("${locale.language};q=%.1f".format(currentQValue))
currentQValue -= 0.1
}
}

return languageTags.joinToString(", ")
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,14 @@ import com.pingidentity.testrail.TestRailWatcher
import io.ktor.http.headers
import org.junit.Rule
import org.junit.rules.TestWatcher
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import kotlin.test.BeforeTest
import kotlin.test.assertContains
import kotlin.test.assertEquals
import kotlin.test.assertNull

@RunWith(RobolectricTestRunner::class)
class DaVinciErrorTest {
@JvmField
@Rule
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import org.junit.Rule
import org.junit.rules.TestWatcher
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import kotlin.test.AfterTest
import kotlin.test.BeforeTest
import kotlin.test.Test
Expand All @@ -56,6 +58,7 @@ import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue

@RunWith(RobolectricTestRunner::class)
class DaVinciTest {
@JvmField
@Rule
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright (c) 2025 Ping Identity. All rights reserved.
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/

package com.pingidentity.davinci

import android.os.LocaleList
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import java.util.Locale
import kotlin.test.Test
import kotlin.test.assertEquals

@RunWith(RobolectricTestRunner::class)
class LocaleListExtensionsTest {

@Test
fun `empty locale list returns empty string`() {
val emptyLocaleList = LocaleList()
assertEquals("", emptyLocaleList.toAcceptLanguage())
}

@Test
fun `single locale without script returns language tag`() {
val localeList = LocaleList(Locale("en", "US"))
assertEquals("en-US, en;q=0.9", localeList.toAcceptLanguage())
}

@Test
fun `single locale with different language tag adds both versions`() {
val localeList = LocaleList(Locale.forLanguageTag("zh-Hant-TW"))
assertEquals("zh-Hant-TW, zh;q=0.9", localeList.toAcceptLanguage())
}

@Test
fun `multiple locales are ordered with decreasing q values`() {
val localeList = LocaleList(
Locale("en", "US"),
Locale("es", "ES"),
Locale("fr", "FR")
)
assertEquals(
"en-US, en;q=0.9, es-ES;q=0.8, es;q=0.7, fr-FR;q=0.6, fr;q=0.5",
localeList.toAcceptLanguage()
)
}

@Test
fun `complex locale list with scripts handled correctly`() {
val localeList = LocaleList(
Locale.forLanguageTag("zh-Hant-TW"),
Locale("en", "US"),
Locale.forLanguageTag("zh-Hans-CN")
)
assertEquals(
"zh-Hant-TW, zh;q=0.9, en-US;q=0.8, en;q=0.7, zh-Hans-CN;q=0.6, zh;q=0.5",
localeList.toAcceptLanguage()
)
}

@Test
fun `q values decrease correctly for long lists`() {
val localeList = LocaleList(
Locale("en", "US"),
Locale("fr", "FR"),
Locale("de", "DE"),
Locale("it", "IT"),
Locale("es", "ES")
)
assertEquals(
"en-US, en;q=0.9, fr-FR;q=0.8, fr;q=0.7, de-DE;q=0.6, de;q=0.5, " +
"it-IT;q=0.4, it;q=0.3, es-ES;q=0.2, es;q=0.1",
localeList.toAcceptLanguage()
)
}
}
18 changes: 17 additions & 1 deletion foundation/orchestrate/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,20 @@ val noSession = Module {
request
}
}
```
```

## Module execution order
The SDK allows you to register a module into the ```Workflow``` Engine. It accepts several parameters to control how the module is registered and configured:

- ```priority``` (optional): A numeric value that determines the module's execution order in the registry. Default value is 10. Higher priority modules are processed first.
- ```mode``` (optional): Determines how the registration handles existing modules. Default is OVERRIDE. The available modes are:
- ```OVERRIDE```: If a module with the same identifier already exists, the new module will replace it.
- ```APPEND```: The new module will be added to the registry's list and cannot be overridden by future registrations. This ensures the module remains in the workflow permanently.
- ```IGNORE```: If a module with the same identifier already exists, the registration request will be silently ignored, keeping the existing module unchanged.

Here is an example of how to register a module with a specific priority and mode:
```kotlin
module(CustomHeader, priority = 5, mode = OverrideMode.APPEND) {
header("Accept-Language", "zh")
}
```

0 comments on commit a30e42a

Please sign in to comment.