Add support for customizing spring boot ConfigurationProperties (#1897)
This adds an option which allows users to control exactly how `@ConfigurationProperties` annotations are attached to classes. This helps when: * Spring Boot users don't have a single root class, but multiple classes that are injected in disparate places * Pkl's module structure doesn't match the resulting `.properties` layout
This commit is contained in:
1 parent
f973fd6717
commit
88a50d167e
15 files changed
+294
-10
No files matched your search
+12
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -77,7 +77,17 @@ data class CliJavaCodeGeneratorOptions(
|
||||
* Pkl module name, and the value is the desired replacement.
|
||||
*/
|
||||
val renames: Map<String, String> = emptyMap(),
|
||||
|
||||
/**
|
||||
* The mapping of Pkl class names to the spring boot `@ConfigurationProperties` prefix name, when
|
||||
* [generateSpringBootConfig] is enabled.
|
||||
*
|
||||
* Setting this field is optional, and by default is inferred through the schema of the input
|
||||
* modules.
|
||||
*/
|
||||
val springBootConfigurationProperties: Map<String, String> = emptyMap(),
|
||||
) {
|
||||
|
||||
@Suppress("DeprecatedCallableAddReplaceWith")
|
||||
@Deprecated("deprecated without replacement")
|
||||
fun toJavaCodegenOptions() = toJavaCodeGeneratorOptions()
|
||||
@@ -93,5 +103,6 @@ data class CliJavaCodeGeneratorOptions(
|
||||
nonNullAnnotation,
|
||||
implementSerializable,
|
||||
renames,
|
||||
springBootConfigurationProperties,
|
||||
)
|
||||
}
|
||||
@@ -97,7 +97,24 @@ data class JavaCodeGeneratorOptions(
|
||||
* from the corresponding name derived from the Pkl module declaration .
|
||||
*/
|
||||
val renames: Map<String, String> = emptyMap(),
|
||||
)
|
||||
|
||||
/**
|
||||
* The mapping of Pkl class names to the spring boot `@ConfigurationProperties` prefix name, when
|
||||
* [generateSpringBootConfig] is enabled.
|
||||
*
|
||||
* Setting this field is optional, and by default is inferred through the schema of the input
|
||||
* modules.
|
||||
*/
|
||||
val springBootConfigurationProperties: Map<String, String> = emptyMap(),
|
||||
) {
|
||||
init {
|
||||
for (value in springBootConfigurationProperties.values) {
|
||||
require(value.isEmpty() || value.isValidConfigurationPropertiesPrefix) {
|
||||
"Invalid value in `springBootConfigurationProperties`: expected a valid ConfigurationProperties prefix, but got '$value'."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Entrypoint for the Java code generator API. */
|
||||
class JavaCodeGenerator(
|
||||
@@ -523,7 +540,25 @@ class JavaCodeGenerator(
|
||||
}
|
||||
|
||||
fun generateSpringBootAnnotations(builder: TypeSpec.Builder) {
|
||||
if (isModuleClass) {
|
||||
if (codegenOptions.springBootConfigurationProperties.isNotEmpty()) {
|
||||
val configurationPropertyPrefix =
|
||||
codegenOptions.springBootConfigurationProperties[pClass.displayName] ?: return
|
||||
val annotation =
|
||||
with(
|
||||
AnnotationSpec.builder(
|
||||
ClassName.get(
|
||||
"org.springframework.boot.context.properties",
|
||||
"ConfigurationProperties",
|
||||
)
|
||||
)
|
||||
) {
|
||||
if (!configurationPropertyPrefix.isEmpty()) {
|
||||
addMember("value", "\$S", configurationPropertyPrefix)
|
||||
}
|
||||
build()
|
||||
}
|
||||
builder.addAnnotation(annotation)
|
||||
} else if (isModuleClass) {
|
||||
builder.addAnnotation(
|
||||
ClassName.get("org.springframework.boot.context.properties", "ConfigurationProperties")
|
||||
)
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.pkl.commons.cli.CliBaseOptions
|
||||
import org.pkl.commons.cli.cliMain
|
||||
import org.pkl.commons.cli.commands.ModulesCommand
|
||||
import org.pkl.commons.cli.commands.installCommonOptions
|
||||
import org.pkl.commons.isValidConfigurationPropertiesPrefix
|
||||
import org.pkl.commons.toPath
|
||||
import org.pkl.core.Release
|
||||
|
||||
@@ -131,6 +132,29 @@ class PklJavaCodegenCommand : ModulesCommand(name = "pkl-codegen-java", helpLink
|
||||
)
|
||||
.associate()
|
||||
|
||||
private val springBootConfigurationProperties: Map<String, String> by
|
||||
option(
|
||||
names = arrayOf("--spring-boot-configuration-properties"),
|
||||
metavar = "pkl_class_name=prefix_value",
|
||||
help =
|
||||
"""
|
||||
Turn the specified Pkl classes into Java classes with the `@ConfigurationProperties`
|
||||
annotation.
|
||||
|
||||
If this setting is omitted, the `@ConfigurationProperties` annotation is inferred
|
||||
according to the structure of the input modules.
|
||||
"""
|
||||
.trimIndent(),
|
||||
)
|
||||
.associate()
|
||||
.validate { map ->
|
||||
for (prefix in map.values) {
|
||||
require(prefix.isEmpty() || prefix.isValidConfigurationPropertiesPrefix) {
|
||||
"value must be a valid spring boot config prefix, but got '$prefix'"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val helpString: String = "Generate Java classes and interfaces from Pkl module(s)"
|
||||
|
||||
override fun run() {
|
||||
@@ -147,6 +171,7 @@ class PklJavaCodegenCommand : ModulesCommand(name = "pkl-codegen-java", helpLink
|
||||
nonNullAnnotation = nonNullAnnotation,
|
||||
implementSerializable = implementSerializable,
|
||||
renames = renames,
|
||||
springBootConfigurationProperties = springBootConfigurationProperties,
|
||||
)
|
||||
CliJavaCodeGenerator(options).run()
|
||||
}
|
||||
|
||||
@@ -1651,6 +1651,65 @@ class JavaCodeGeneratorTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `spring boot -- explicitly configured ConfigurationProperties`() {
|
||||
val javaCode =
|
||||
generateJavaCode(
|
||||
"""
|
||||
module my.mod
|
||||
|
||||
class Server
|
||||
"""
|
||||
.trimIndent(),
|
||||
JavaCodeGeneratorOptions(
|
||||
generateSpringBootConfig = true,
|
||||
springBootConfigurationProperties =
|
||||
mapOf("my.mod" to "", "my.mod#Server" to "my-server-prefix"),
|
||||
),
|
||||
)
|
||||
assertThat(javaCode)
|
||||
.contains(
|
||||
"""
|
||||
|@ConfigurationProperties
|
||||
|public final class Mod {
|
||||
| private Mod() {
|
||||
| }
|
||||
"""
|
||||
.trimMargin()
|
||||
)
|
||||
assertThat(javaCode)
|
||||
.contains(
|
||||
"""
|
||||
| @ConfigurationProperties("my-server-prefix")
|
||||
| public static final class Server {
|
||||
"""
|
||||
.trimMargin()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `spring boot -- explicity configured properties means no interface`() {
|
||||
val javaCode =
|
||||
generateJavaCode(
|
||||
"""
|
||||
module my.mod
|
||||
|
||||
server: Server
|
||||
|
||||
class Server
|
||||
"""
|
||||
.trimIndent(),
|
||||
JavaCodeGeneratorOptions(
|
||||
generateSpringBootConfig = true,
|
||||
springBootConfigurationProperties = mapOf("foo" to "foo"),
|
||||
),
|
||||
)
|
||||
// normally this would generate `@ConfigurationProperties` annotations through inference.
|
||||
// but setting this option means that the user is in total control of how these annotations
|
||||
// get produced.
|
||||
assertThat(javaCode).doesNotContain("@ConfigurationProperties")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `import module`() {
|
||||
val library =
|
||||
|
||||
Reference in new issue
Block a user