Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-19 18:42:56 +04:00
parent 5da87df354
commit e725649059
18 changed files with 661 additions and 421 deletions

View file

@ -1,7 +1,6 @@
package com.tangem.plugin.configuration.configurations
import com.squareup.kotlinpoet.*
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
@ -12,8 +11,7 @@ import java.util.Locale
/**
* Generator for toggle configuration Kotlin enum classes from JSON files.
* Parses a JSON array of `{ "name": "...", "version": "..." }` entries
* and generates an enum class with an entry for each toggle and a `values: Map<String, String>`
* in companion object mapping toggle name to its version string.
* and generates an enum class with `rawName` and `version` properties for each toggle.
*
[REDACTED_AUTHOR]
*/
@ -39,31 +37,35 @@ object TogglesGenerator {
ToggleEntry(name = name, enumName = name.toEnumEntryName(), version = version)
}
val mapInitializer = buildCodeBlock {
addStatement("mapOf(")
withIndent {
entries.forEach { entry ->
addStatement("%S to %S,", entry.name, entry.version)
}
}
add(")")
}
val companionBuilder = TypeSpec.companionObjectBuilder()
.addProperty(
PropertySpec.builder("values", MAP.parameterizedBy(STRING, STRING))
.initializer(mapInitializer)
.build(),
)
val enumBuilder = TypeSpec.enumBuilder(fileName)
.addKdoc("Generated from ${inputFile.name}\nAuto-generated - do not edit manually.")
.primaryConstructor(
FunSpec.constructorBuilder()
.addParameter(ParameterSpec.builder("rawName", String::class).build())
.addParameter(ParameterSpec.builder("version", String::class).build())
.build(),
)
.addProperty(
PropertySpec.builder("rawName", String::class)
.initializer("rawName")
.build(),
)
.addProperty(
PropertySpec.builder("version", String::class)
.initializer("version")
.build(),
)
.apply {
entries.forEach { entry ->
addEnumConstant(entry.enumName)
addEnumConstant(
entry.enumName,
TypeSpec.anonymousClassBuilder()
.addSuperclassConstructorParameter("%S", entry.name)
.addSuperclassConstructorParameter("%S", entry.version)
.build(),
)
}
}
.addType(companionBuilder.build())
val fileSpec = FileSpec.builder(packageName = PACKAGE_NAME, fileName = fileName)
.indent(" ")
@ -73,7 +75,6 @@ object TogglesGenerator {
outputDir.mkdirs()
fileSpec.writeTo(outputDir)
// Remove redundant public visibility modifiers
val generatedFile = File(outputDir, "${PACKAGE_NAME.replace('.', '/')}/$fileName.kt")
if (generatedFile.exists()) {
val content = generatedFile.readText()
@ -81,6 +82,7 @@ object TogglesGenerator {
.replace("public enum class ", "enum class ")
.replace("public companion object", "companion object")
.replace("public val ", "val ")
.replace(Regex(""",\s*;\s*\n\s*}"""), ",\n}")
generatedFile.writeText(fixedContent)
}
}

View file

@ -42,11 +42,25 @@ class TogglesGeneratorTest {
)
// Assert
assertThat(code).contains("enum class FeatureToggles {")
assertThat(code).contains("enum class FeatureToggles(")
}
@Test
fun `generate creates enum entries and companion object`() {
fun `generate creates enum with constructor parameters`() {
// Arrange
val json = """[{ "name": "onramp/ios", "version": "1.0.0" }]"""
// Act
val code = generateAndReadOutput(json, "Toggles")
// Assert
assertThat(code).contains("ONRAMP_IOS(")
assertThat(code).contains("val rawName: String")
assertThat(code).contains("val version: String")
}
@Test
fun `generate creates enum entries without companion object`() {
// Arrange & Act
val code = generateAndReadOutput(
json = SINGLE_ENTRY_JSON,
@ -54,21 +68,23 @@ class TogglesGeneratorTest {
)
// Assert
assertThat(code).contains("STAKING_ETH_ENABLED,")
assertThat(code).contains("companion object {")
assertThat(code).contains("STAKING_ETH_ENABLED(")
assertThat(code).contains("val rawName: String")
assertThat(code).contains("val version: String")
assertThat(code).doesNotContain("companion object")
assertThat(code).doesNotContain("val values:")
}
@Test
fun `generate creates values map with string literal keys`() {
fun `generate creates enum entries with constructor arguments`() {
// Arrange & Act
val code = generateAndReadOutput(
json = SINGLE_ENTRY_JSON,
fileName = "FeatureToggles",
)
// Assert
assertThat(code).contains("val values: Map<String, String> = mapOf(")
assertThat(code).contains(""""STAKING_ETH_ENABLED" to "undefined"""")
// Assert - verify enum entry includes constructor arguments
assertThat(code).contains(""""STAKING_ETH_ENABLED", "undefined"""")
}
@Test
@ -86,12 +102,12 @@ class TogglesGeneratorTest {
val code = generateAndReadOutput(json, "TestToggles")
// Assert
assertThat(code).contains("FEATURE_A,")
assertThat(code).contains("FEATURE_B,")
assertThat(code).contains("FEATURE_C,")
assertThat(code).contains(""""FEATURE_A" to "1.0"""")
assertThat(code).contains(""""FEATURE_B" to "2.0"""")
assertThat(code).contains(""""FEATURE_C" to "undefined"""")
assertThat(code).contains("FEATURE_A(")
assertThat(code).contains("FEATURE_B(")
assertThat(code).contains("FEATURE_C(")
assertThat(code).contains(""""FEATURE_A", "1.0"""")
assertThat(code).contains(""""FEATURE_B", "2.0"""")
assertThat(code).contains(""""FEATURE_C", "undefined"""")
}
@Test
@ -103,8 +119,8 @@ class TogglesGeneratorTest {
)
// Assert
assertThat(code).contains("enum class EmptyToggles {")
assertThat(code).contains("val values: Map<String, String> = mapOf(")
assertThat(code).contains("enum class EmptyToggles(")
assertThat(code).doesNotContain("companion object")
}
@Test
@ -116,8 +132,8 @@ class TogglesGeneratorTest {
val code = generateAndReadOutput(json, "Toggles")
// Assert
assertThat(code).contains("NEXA_TEST,")
assertThat(code).contains(""""NEXA/test" to "undefined"""")
assertThat(code).contains("NEXA_TEST(")
assertThat(code).contains(""""NEXA/test", "undefined"""")
}
@Test
@ -129,8 +145,8 @@ class TogglesGeneratorTest {
val code = generateAndReadOutput(json, "Toggles")
// Assert
assertThat(code).contains("VANAR_CHAIN,")
assertThat(code).contains(""""vanar-chain" to "undefined"""")
assertThat(code).contains("VANAR_CHAIN(")
assertThat(code).contains(""""vanar-chain", "undefined"""")
}
@Test
@ -142,8 +158,8 @@ class TogglesGeneratorTest {
val code = generateAndReadOutput(json, "Toggles")
// Assert
assertThat(code).contains("SONIC,")
assertThat(code).contains(""""sonic" to "5.21.0"""")
assertThat(code).contains("SONIC(")
assertThat(code).contains(""""sonic", "5.21.0"""")
}
@Test
@ -169,7 +185,6 @@ class TogglesGeneratorTest {
// Assert
assertThat(code).doesNotContain("public enum class")
assertThat(code).doesNotContain("public companion object")
assertThat(code).doesNotContain("public val")
}
@ -188,12 +203,12 @@ class TogglesGeneratorTest {
val code = generateAndReadOutput(json, "FeatureToggles")
// Assert
assertThat(code).contains("NEW_CARD_SCANNING_ENABLED,")
assertThat(code).contains("HOT_WALLET_CREATION_RESTRICTION_ENABLED,")
assertThat(code).contains("SWAP_MARKET_LIST_ENABLED,")
assertThat(code).contains(""""NEW_CARD_SCANNING_ENABLED" to "undefined"""")
assertThat(code).contains(""""HOT_WALLET_CREATION_RESTRICTION_ENABLED" to "5.32.0"""")
assertThat(code).contains(""""SWAP_MARKET_LIST_ENABLED" to "5.34"""")
assertThat(code).contains("NEW_CARD_SCANNING_ENABLED(")
assertThat(code).contains("HOT_WALLET_CREATION_RESTRICTION_ENABLED(")
assertThat(code).contains("SWAP_MARKET_LIST_ENABLED(")
assertThat(code).contains(""""NEW_CARD_SCANNING_ENABLED", "undefined"""")
assertThat(code).contains(""""HOT_WALLET_CREATION_RESTRICTION_ENABLED", "5.32.0"""")
assertThat(code).contains(""""SWAP_MARKET_LIST_ENABLED", "5.34"""")
}
@Test
@ -211,13 +226,15 @@ class TogglesGeneratorTest {
val code = generateAndReadOutput(json, "ExcludedBlockchainToggles")
// Assert
assertThat(code).contains("enum class ExcludedBlockchainToggles {")
assertThat(code).contains("NEXA,")
assertThat(code).contains("NEXA_TEST,")
assertThat(code).contains("SONIC,")
assertThat(code).contains(""""NEXA" to "undefined"""")
assertThat(code).contains(""""NEXA/test" to "undefined"""")
assertThat(code).contains(""""sonic" to "5.21.0"""")
assertThat(code).contains("enum class ExcludedBlockchainToggles(")
assertThat(code).contains("val rawName: String")
assertThat(code).contains("val version: String")
assertThat(code).contains("NEXA(")
assertThat(code).contains("NEXA_TEST(")
assertThat(code).contains("SONIC(")
assertThat(code).contains(""""NEXA", "undefined"""")
assertThat(code).contains(""""NEXA/test", "undefined"""")
assertThat(code).contains(""""sonic", "5.21.0"""")
}
@Test
@ -230,8 +247,25 @@ class TogglesGeneratorTest {
val code2 = generateAndReadOutput(json, "ExcludedBlockchainToggles")
// Assert
assertThat(code1).contains("enum class FeatureToggles {")
assertThat(code2).contains("enum class ExcludedBlockchainToggles {")
assertThat(code1).contains("enum class FeatureToggles(")
assertThat(code2).contains("enum class ExcludedBlockchainToggles(")
}
@Test
fun `generate does not include semicolon after enum entries`() {
// Arrange
val json = """
[
{ "name": "FEATURE_A", "version": "1.0" },
{ "name": "FEATURE_B", "version": "2.0" }
]
""".trimIndent()
// Act
val code = generateAndReadOutput(json, "TestToggles")
// Assert - ensure no trailing semicolons
assertThat(code).doesNotContain(";\n")
}
private fun generateAndReadOutput(json: String, fileName: String): String {