Updated on 2026-08-14
This commit is contained in:
parent
26a6da1b85
commit
428c0a56b0
3 changed files with 137 additions and 13 deletions
|
|
@ -1 +1 @@
|
|||
Subproject commit e4fac168bd941fe90b0f081dfa70ea1b4148b49f
|
||||
Subproject commit b693d601d9dc3de27f0536574bce59a6aa5e2e89
|
||||
|
|
@ -4,6 +4,7 @@ import com.squareup.kotlinpoet.*
|
|||
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
|
||||
import kotlinx.serialization.json.*
|
||||
import java.io.File
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Generator for environment configuration Kotlin object from JSON file.
|
||||
|
|
@ -68,12 +69,13 @@ object EnvironmentConfigGenerator {
|
|||
* Adds a property to the TypeSpec based on the JSON value type
|
||||
*/
|
||||
private fun addPropertyFromJsonValue(builder: TypeSpec.Builder, name: String, value: JsonElement) {
|
||||
val propertyName = name.toValidIdentifier()
|
||||
when (value) {
|
||||
is JsonPrimitive -> {
|
||||
when {
|
||||
value.isString -> {
|
||||
val stringValue = value.content
|
||||
val propertySpec = PropertySpec.builder(name, STRING)
|
||||
val propertySpec = PropertySpec.builder(propertyName, STRING)
|
||||
.addModifiers(KModifier.CONST)
|
||||
.initializer("%S", stringValue)
|
||||
|
||||
|
|
@ -81,7 +83,7 @@ object EnvironmentConfigGenerator {
|
|||
}
|
||||
value.booleanOrNull != null -> {
|
||||
builder.addProperty(
|
||||
PropertySpec.builder(name, BOOLEAN)
|
||||
PropertySpec.builder(propertyName, BOOLEAN)
|
||||
.addModifiers(KModifier.CONST)
|
||||
.initializer("%L", value.boolean)
|
||||
.build()
|
||||
|
|
@ -89,7 +91,7 @@ object EnvironmentConfigGenerator {
|
|||
}
|
||||
value.longOrNull != null -> {
|
||||
builder.addProperty(
|
||||
PropertySpec.builder(name, LONG)
|
||||
PropertySpec.builder(propertyName, LONG)
|
||||
.addModifiers(KModifier.CONST)
|
||||
.initializer("%L", value.long)
|
||||
.build()
|
||||
|
|
@ -97,7 +99,7 @@ object EnvironmentConfigGenerator {
|
|||
}
|
||||
value.doubleOrNull != null -> {
|
||||
builder.addProperty(
|
||||
PropertySpec.builder(name, DOUBLE)
|
||||
PropertySpec.builder(propertyName, DOUBLE)
|
||||
.addModifiers(KModifier.CONST)
|
||||
.initializer("%L", value.double)
|
||||
.build()
|
||||
|
|
@ -106,7 +108,7 @@ object EnvironmentConfigGenerator {
|
|||
else -> {
|
||||
// Null value
|
||||
builder.addProperty(
|
||||
PropertySpec.builder(name, STRING.copy(nullable = true))
|
||||
PropertySpec.builder(propertyName, STRING.copy(nullable = true))
|
||||
.initializer("null")
|
||||
.build()
|
||||
)
|
||||
|
|
@ -117,7 +119,7 @@ object EnvironmentConfigGenerator {
|
|||
val listType = LIST.parameterizedBy(STRING)
|
||||
val values = value.map { it.jsonPrimitive.content }
|
||||
builder.addProperty(
|
||||
PropertySpec.builder(name, listType)
|
||||
PropertySpec.builder(propertyName, listType)
|
||||
.initializer(
|
||||
CodeBlock.builder()
|
||||
.add("listOf(\n")
|
||||
|
|
@ -147,14 +149,48 @@ object EnvironmentConfigGenerator {
|
|||
}
|
||||
|
||||
/**
|
||||
* Converts a string to PascalCase, handling dashes and underscores.
|
||||
* Examples: "cosmos-hub" -> "CosmosHub", "polygon-zkevm" -> "PolygonZkevm"
|
||||
* Converts a string to PascalCase for use as a class/object name.
|
||||
* - If the string contains separators (dots, dashes, underscores), splits and joins in PascalCase
|
||||
* - If no separators, just capitalizes the first letter to preserve original casing (e.g., "AppsFlyer" stays "AppsFlyer")
|
||||
*/
|
||||
private fun String.toPascalCase(): String {
|
||||
return this.split("-", "_")
|
||||
val hasSeparators = contains('.') || contains('-') || contains('_')
|
||||
return if (hasSeparators) {
|
||||
this.split("-", "_", ".")
|
||||
.filter { it.isNotEmpty() }
|
||||
.joinToString("") { part ->
|
||||
part.lowercase(Locale.ROOT).replaceFirstChar { it.uppercase(Locale.ROOT) }
|
||||
}
|
||||
} else {
|
||||
this.replaceFirstChar { it.uppercase(Locale.ROOT) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string to a valid Kotlin property identifier.
|
||||
* - If the string contains dots, converts to camelCase (dots cannot be escaped by KotlinPoet)
|
||||
* - Otherwise, ensures the first letter is lowercase (Kotlin property naming convention)
|
||||
*/
|
||||
private fun String.toValidIdentifier(): String {
|
||||
return if (contains('.')) {
|
||||
toCamelCase()
|
||||
} else {
|
||||
this.replaceFirstChar { it.lowercase(Locale.ROOT) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string to camelCase, handling dashes, underscores, and dots.
|
||||
* Normalizes each segment to lowercase first for consistent results.
|
||||
* Examples: "cosmos-hub" -> "cosmosHub", "customer.io" -> "customerIo", "CUSTOMER.IO" -> "customerIo"
|
||||
*/
|
||||
private fun String.toCamelCase(): String {
|
||||
val parts = this.split("-", "_", ".")
|
||||
.filter { it.isNotEmpty() }
|
||||
.joinToString("") { part ->
|
||||
part.replaceFirstChar { it.uppercase() }
|
||||
}
|
||||
return parts.mapIndexed { index, part ->
|
||||
val normalized = part.lowercase(Locale.ROOT)
|
||||
if (index == 0) normalized
|
||||
else normalized.replaceFirstChar { it.uppercase(Locale.ROOT) }
|
||||
}.joinToString("")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -210,6 +210,32 @@ class EnvironmentConfigGeneratorTest {
|
|||
assertThat(generatedCode).contains("""const val deepValue: String = "deep"""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `generate preserves camelCase object names without separators`() {
|
||||
// Arrange - object names like "AppsFlyer" should stay as "AppsFlyer", not become "Appsflyer"
|
||||
val json = """
|
||||
{
|
||||
"AppsFlyer": {
|
||||
"DevKey": "key123"
|
||||
},
|
||||
"GetBlockAccessTokens": {
|
||||
"ethereum": {
|
||||
"jsonRpc": "token"
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
// Act
|
||||
val generatedCode = generateAndReadOutput(json)
|
||||
|
||||
// Assert - object names preserved, property names have first letter lowercased
|
||||
assertThat(generatedCode).contains("object AppsFlyer {")
|
||||
assertThat(generatedCode).contains("""const val devKey: String = "key123"""")
|
||||
assertThat(generatedCode).contains("object GetBlockAccessTokens {")
|
||||
assertThat(generatedCode).contains("object Ethereum {")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `generate converts dash-separated names to PascalCase`() {
|
||||
// Arrange
|
||||
|
|
@ -451,6 +477,68 @@ class EnvironmentConfigGeneratorTest {
|
|||
assertThat(generatedCode).contains("object QuickNodeSolanaCredentials {")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `generate converts dot-separated object names to PascalCase`() {
|
||||
// Arrange - testing the customer.io case that caused the original build failure
|
||||
val json = """
|
||||
{
|
||||
"customer.io": {
|
||||
"TrackSiteID": "site-id-123",
|
||||
"TrackApiKey": "api-key-456"
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
// Act
|
||||
val generatedCode = generateAndReadOutput(json)
|
||||
|
||||
// Assert
|
||||
assertThat(generatedCode).contains("object CustomerIo {")
|
||||
// Property names have first letter lowercased (Kotlin convention)
|
||||
assertThat(generatedCode).contains("""const val trackSiteID: String = "site-id-123"""")
|
||||
assertThat(generatedCode).contains("""const val trackApiKey: String = "api-key-456"""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `generate converts dot-separated property names to camelCase`() {
|
||||
// Arrange - testing property names with dots (not nested objects)
|
||||
val json = """
|
||||
{
|
||||
"api.key": "test-key",
|
||||
"service.url": "https://example.com"
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
// Act
|
||||
val generatedCode = generateAndReadOutput(json)
|
||||
|
||||
// Assert - dots in property names are converted to camelCase
|
||||
assertThat(generatedCode).contains("""const val apiKey: String = "test-key"""")
|
||||
assertThat(generatedCode).contains("""const val serviceUrl: String = "https://example.com"""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `generate preserves valid property names without transformation`() {
|
||||
// Arrange - valid Kotlin identifiers should not be transformed
|
||||
val json = """
|
||||
{
|
||||
"apiKey": "key1",
|
||||
"moonPayApiKey": "moon-pay-key",
|
||||
"isEnabled": true,
|
||||
"maxRetryCount": 5
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
// Act
|
||||
val generatedCode = generateAndReadOutput(json)
|
||||
|
||||
// Assert - original names preserved exactly
|
||||
assertThat(generatedCode).contains("""const val apiKey: String = "key1"""")
|
||||
assertThat(generatedCode).contains("""const val moonPayApiKey: String = "moon-pay-key"""")
|
||||
assertThat(generatedCode).contains("const val isEnabled: Boolean = true")
|
||||
assertThat(generatedCode).contains("const val maxRetryCount: Long = 5")
|
||||
}
|
||||
|
||||
private fun generateAndReadOutput(jsonContent: String): String {
|
||||
val inputFile = File(tempDir, "config.json").apply {
|
||||
writeText(jsonContent)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue