# Mapping configuration to objects

With config mappings it is possible to group multiple configuration properties in a single interface that share the same prefix.

## 1. `@ConfigMapping`

A config mapping requires a public interface with minimal metadata configuration and annotated with the `@io.smallrye.config.ConfigMapping` annotation.

    @ConfigMapping(prefix = "server")
    public interface Server {
        String host();

        int port();
    }

The `Server` interface is able to map configuration properties with the name `server.host` into the `Server.host()` method and `server.port` into `Server.port()` method. The configuration property name to look up is built from the prefix, and the method name with `.` (dot) as the separator.

| If a mapping fails to match a configuration property a `NoSuchElementException` is thrown, unless the mapped element is an `Optional`.

### 1.1. Registration

When a Quarkus application starts, a config mapping can be registered twice. One time for STATIC INIT and a second time for RUNTIME INIT:

#### 1.1.1. STATIC INIT

Quarkus starts some of its services during static initialization, and `Config` is usually one of the first things that is created. In certain situations it may not be possible to correctly initialize a config mapping. For instance, if the mapping requires values from a custom `ConfigSource`. For this reason, any config mapping requires the annotation `@io.quarkus.runtime.configuration.StaticInitSafe` to mark the mapping as safe to be used at this stage. Learn more about registration of a custom `ConfigSource`.

##### 1.1.1.1. Example

    @StaticInitSafe
    @ConfigMapping(prefix = "server")
    public interface Server {
        String host();

        int port();
    }

#### 1.1.2. RUNTIME INIT

The RUNTIME INIT stage happens after STATIC INIT. There are no restrictions at this stage, and any config mapping is added to the `Config` instance as expected.

### 1.2. Retrieval

A config mapping interface can be injected into any CDI aware bean:

    class BusinessBean {
        @Inject
        Server server;

        public void businessMethod() {
            String host = server.host();
        }
    }

In non-CDI contexts, use the API `io.smallrye.config.SmallRyeConfig#getConfigMapping` to retrieve the config mapping instance:

    SmallRyeConfig config = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class);
    Server server = config.getConfigMapping(Server.class);

### 1.3. Nested groups

A nested mapping provides a way to subgroup other config properties:

    @ConfigMapping(prefix = "server")
    public interface Server {
        String host();

        int port();

        Log log();

        interface Log {
            boolean enabled();

            String suffix();

            boolean rotate();
        }
    }

application.properties

    server.host=localhost
    server.port=8080
    server.log.enabled=true
    server.log.suffix=.log
    server.log.rotate=false

The method name of a mapping group acts as sub-namespace to the configurations properties.

### 1.4. Overriding property names

#### 1.4.1. `@WithName`

If a method name, or a property name do not match with each other, the `@WithName` annotation can override the method name mapping and use the name supplied in the annotation:

    @ConfigMapping(prefix = "server")
    public interface Server {
        @WithName("name")
        String host();

        int port();
    }

application.properties

    server.name=localhost
    server.port=8080

#### 1.4.2. `@WithParentName`

The `@WithParentName` annotation allows to configurations mapping to inherit its container name, simplifying the configuration property name required to match the mapping:

    interface Server {
        @WithParentName
        ServerHostAndPort hostAndPort();

        @WithParentName
        ServerInfo info();
    }

    interface ServerHostAndPort {
        String host();

        int port();
    }

    interface ServerInfo {
        String name();
    }

application.properties

    server.host=localhost
    server.port=8080
    server.name=konoha

Without the `@WithParentName` the method `name()` requires the configuration property `server.info.name`. Because we use `@WithParentName`, the `info()` mapping will inherit the parent name from `Server` and `name()` maps to `server.name` instead.

#### 1.4.3. NamingStrategy

Method names in camelCase map to kebab-case property names:

    @ConfigMapping(prefix = "server")
    public interface Server {
        String theHost();

        int thePort();
    }

application.properties

    server.the-host=localhost
    server.the-port=8080

The mapping strategy can be adjusted by setting `namingStrategy` value in the `@ConfigMapping` annotation:

    @ConfigMapping(prefix = "server", namingStrategy = ConfigMapping.NamingStrategy.VERBATIM)
    public interface ServerVerbatimNamingStrategy {
        String theHost();

        int thePort();
    }

application.properties

    server.theHost=localhost
    server.thePort=8080

The `@ConfigMapping` annotation support the following naming strategies:

- `KEBAB_CASE` (default) - The method name is derived by replacing case changes with a dash to map the configuration property.

- `VERBATIM` - The method name is used as is to map the configuration property.

- `SNAKE_CASE` - The method name is derived by replacing case changes with an underscore to map the configuration property.

### 1.5. Conversions

A config mapping class support automatic conversions of all types available for conversion in `Config`:

    @ConfigMapping
    public interface SomeTypes {
        @WithName("int")
        int intPrimitive();

        @WithName("int")
        Integer intWrapper();

        @WithName("long")
        long longPrimitive();

        @WithName("long")
        Long longWrapper();

        @WithName("float")
        float floatPrimitive();

        @WithName("float")
        Float floatWrapper();

        @WithName("double")
        double doublePrimitive();

        @WithName("double")
        Double doubleWrapper();

        @WithName("char")
        char charPrimitive();

        @WithName("char")
        Character charWrapper();

        @WithName("boolean")
        boolean booleanPrimitive();

        @WithName("boolean")
        Boolean booleanWrapper();
    }

application.properties

    int=9
    long=9999999999
    float=99.9
    double=99.99
    char=c
    boolean=true

This is also valid for `Optional` and friends:

    @ConfigMapping
    public interface Optionals {
        Optional<Server> server();

        Optional<String> optional();

        @WithName("optional.int")
        OptionalInt optionalInt();

        interface Server {
            String host();

            int port();
        }
    }

In this case, the mapping won’t fail if there is no configuration property to match the mapping.

#### 1.5.1. `@WithConverter`

The `@WithConverter` annotation provides a way to set a `Converter` to use in a specific mapping:

    @ConfigMapping
    public interface Converters {
        @WithConverter(FooBarConverter.class)
        String foo();
    }

    public static class FooBarConverter implements Converter<String> {
        @Override
        public String convert(final String value) {
            return "bar";
        }
    }

application.properties

    foo=foo

A call to `Converters.foo()` results in the value `bar`.

#### 1.5.2. Collections

A config mapping is also able to map collections types `List` and `Set`:

    @ConfigMapping(prefix = "server")
    public interface ServerCollections {
        Set<Environment> environments();

        interface Environment {
            String name();

            List<App> apps();

            interface App {
                String name();

                List<String> services();

                Optional<List<String>> databases();
            }
        }
    }

application.properties

    server.environments[0].name=dev
    server.environments[0].apps[0].name=rest
    server.environments[0].apps[0].services=bookstore,registration
    server.environments[0].apps[0].databases=pg,h2
    server.environments[0].apps[1].name=batch
    server.environments[0].apps[1].services=stock,warehouse

The `List` or `Set` mappings can use indexed properties to map configuration values in mapping groups. For collection with simple element types like `String`, their configuration value is a comma separated string.

#### 1.5.3. Maps

A config mapping is also able to map a `Map`:

    @ConfigMapping(prefix = "server")
    public interface Server {
        String host();

        int port();

        Map<String, String> form();
    }

application.properties

    server.host=localhost
    server.port=8080
    server.form.login-page=login.html
    server.form.error-page=error.html
    server.form.landing-page=index.html

The configuration property needs to specify an additional name to act as the key. In this case the `form()` `Map` will contain three elements with the keys `login-page`, `error-page` and `landing-page`.

### 1.6. Defaults

The `@WithDefault` annotation allows to set a default property into a mapping (and prevent and error if the configuration value is not available in any `ConfigSource`):

    public interface Defaults {
        @WithDefault("foo")
        String foo();

        @WithDefault("bar")
        String bar();
    }

No configuration properties required. The `Defaults.foo()` will return the value `foo` and `Defaults.bar()` will return the value `bar`.

### 1.7. Validation

A config mapping may combine annotations from Bean Validation to validate configuration values:

    @ConfigMapping(prefix = "server")
    public interface Server {
        @Size(min = 2, max = 20)
        String host();

        @Max(10000)
        int port();
    }

| For validation to work, the `quarkus-hibernate-validator` extension is required, and it is performed automatically.

### 1.8. Mocking

A mapping interface implementation is not a proxy, so it cannot be mocked directly with `@InjectMock` like other CDI beans. One trick is to make it proxyable with a producer method:

    public class ServerMockProducer {
        @Inject
        Config config;

        @Produces
        @ApplicationScoped
        @io.quarkus.test.Mock
        Server server() {
            return config.unwrap(SmallRyeConfig.class).getConfigMapping(Server.class);
        }
    }

The `Server` can be injected as a mock into a Quarkus test class with `@InjectMock`:

    @QuarkusTest
    class ServerMockTest {
        @InjectMock
        Server server;

        @Test
        void localhost() {
            Mockito.when(server.host()).thenReturn("localhost");
            assertEquals("localhost", server.host());
        }
    }

| The mock is just an empty shell without any actual configuration values.

If the goal is to only mock certain configuration values and retain the original configuration, the mocking instance requires a spy:

    @ConfigMapping(prefix = "app")
    public interface AppConfig {
        @WithDefault("app")
        String name();

        Info info();

        interface Info {
            @WithDefault("alias")
            String alias();
            @WithDefault("10")
            Integer count();
        }
    }

    public static class AppConfigProducer {
        @Inject
        Config config;

        @Produces
        @ApplicationScoped
        @io.quarkus.test.Mock
        AppConfig appConfig() {
            AppConfig appConfig = config.unwrap(SmallRyeConfig.class).getConfigMapping(AppConfig.class);
            AppConfig appConfigSpy = Mockito.spy(appConfig);
            AppConfig.Info infoSpy = Mockito.spy(appConfig.info());
            Mockito.when(appConfigSpy.info()).thenReturn(infoSpy);
            return appConfigSpy;
        }
    }

The `AppConfig` can be injected as a mock into a Quarkus test class with `@Inject`:

    @QuarkusTest
    class AppConfigTest {
        @Inject
        AppConfig appConfig;

        @Test
        void localhost() {
            Mockito.when(appConfig.name()).thenReturn("mocked-app");
            assertEquals("mocked-app", server.host());

            Mockito.when(appConfig.info().alias()).thenReturn("mocked-alias");
            assertEquals("mocked-alias", server.info().alias());
        }
    }

| Nested elements need to be spied individually by Mockito.