Skip to content

Commit

Permalink
@ConfigMapping specification (#232)
Browse files Browse the repository at this point in the history
  • Loading branch information
radcortez committed May 2, 2024
1 parent 3fe91ea commit 031d847
Show file tree
Hide file tree
Showing 9 changed files with 184 additions and 9 deletions.
2 changes: 2 additions & 0 deletions api/src/main/java/jakarta/config/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ public interface Config {
*
* @param path a configuration path
* @return a new instance of the {@link Config} class with a new <em>configuration path</em>
*
* @see ConfigMapping#path() Configuration#path
*/
Config path(String path);

Expand Down
22 changes: 22 additions & 0 deletions api/src/main/java/jakarta/config/ConfigDefault.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package jakarta.config;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Specify the default value of a configuration member.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
public @interface ConfigDefault {
/**
* The default value of the member.
*
* @return the default value as a string
*/
String value();
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Configuration {
public @interface ConfigMapping {

/**
* The <em>configuration path</em> identifies where the configuration relevant for the annotated configuration class is found
Expand Down
22 changes: 22 additions & 0 deletions api/src/main/java/jakarta/config/ConfigName.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package jakarta.config;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Override the configuration member name.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD })
public @interface ConfigName {
/**
* The name of the configuration member name. Must not be empty.
*
* @return the configuration member name
*/
String value();
}
129 changes: 129 additions & 0 deletions spec/src/main/asciidoc/03-config-mapping.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
= Config Mapping

Config Mapping allows mapping configuration entries to complex object types (usually user defined), following certain
rules:

- A configuration path uniquely identifies object member
- A configuration value maps to the object member value type
== Mapping Rules

A complex object type uses the following rules to map configuration values to their member values:

- A configuration path is built by taking the object type prefix (or namespace) and the mapping member name
- The member name is converted to its kebab-case format
- If the member name is represented as a getter, the member name is taken from its property name equivalent, and then
converted to its kebab-case format.
- The configuration value is automatically converted to the member type
- The configuration path is required to exist with a valid configuration value or the mapping will fail.

=== Nested Types

- A nested type contributes with its name (converted to its kebab-case format)
- The configuration path is built by taking the root object type prefix (or namespace), the nested
type name and the member name of the nested type

=== Collections and Arrays

- A member with a `Collection` or `Array` type requires the configuration name to be in its indexed format
- Each configuration name, plus its index maps the configuration value to the corresponding `Collection` or
`Array` element in the object type
- The index must be part of the configuration path, by appending the index between square brackets to the
`Collection` or `Array` member
- The index specified in the configuration name is used to order the element in the `Collection` or `Array`
- Missing elements or gaps are removed

=== Maps

- A member with a `Map` type requires an additional configuration name added to the configuration path of the `Map`
member to act as a map key
- The additional configuration name maps a Map entry with the configuration name as the `Map` entry key and
the configuration value as the Map entry value

=== Optionals

- A mapping can wrap any complex type with an `Optional`
- `Optional` mappings do not require the configuration path and value to be present

== Override Conventions

It is possible to override:

- The base configuration path (prefix or namespace) of the mapping
- Mapping member names
- The default Converter of a member
- Add a default value to a member

== Examples

=== Mapping definition
[source,java]
----
@ConfigMapping("my.config")
interface Server {
// my.config.host
String host();
// my.config.port
int port();
// my.config.io-threads
int ioThreads();
// my.config.endpoints[*].path
// my.config.endpoints[*].methods
List<Endpoint> endpoints();
Optional<Ssl> ssl();
// my.config.form.login-page
Map<String, String> form();
interface Ssl {
// my.config.ssl.port
int port();
// my.config.ssl.certificate
String certificate();
// my.config.ssl.protocols[*]
List<String> protocols();
}
interface Endpoint {
String path();
List<String> methods();
}
}
----

=== Programmatic Access

[source,java]
----
class Service {
Config config;
void service() {
Server server = config.load(Server.class);
}
}
----

=== Overriding Conventions

[source,java]
----
@ConfigMapping("my.config")
interface Server {
@ConfigName("hostname")
Host host();
@ConfigDefault("8080")
int port();
@ConfigConverter(IOThreadsConverter.class)
int ioThreads();
}
----
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@

package jakarta.config.tck.common;

import jakarta.config.Configuration;
import jakarta.config.ConfigMapping;

@Configuration(path = "my.configuration")
@ConfigMapping(path = "my.configuration")
public interface AnyConfiguration {
String key();
}
4 changes: 2 additions & 2 deletions tck/src/main/java/jakarta/config/tck/common/My.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@

package jakarta.config.tck.common;

import jakarta.config.Configuration;
import jakarta.config.ConfigMapping;

@Configuration(path="my")
@ConfigMapping(path="my")
public interface My {
String username();
String password();
Expand Down
4 changes: 2 additions & 2 deletions tck/src/main/java/jakarta/config/tck/common/Other.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@

package jakarta.config.tck.common;

import jakarta.config.Configuration;
import jakarta.config.ConfigMapping;

@Configuration(path = "other")
@ConfigMapping(path = "other")
public interface Other {
AnyConfiguration configuration();
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@

package jakarta.config.tck.common;

import jakarta.config.Configuration;
import jakarta.config.ConfigMapping;

@Configuration
@ConfigMapping
public interface TopLevelConfig {
My my();
Other other();
Expand Down

0 comments on commit 031d847

Please sign in to comment.