Skip to content

Type conversions

Andy Boothe edited this page Jan 20, 2025 · 5 revisions

In an effort to minimize boilerplate, Rapier provides automatic type conversion as part of its core feature set. This allows users to provision configuration data in whatever data type they require, as opposed to having to write a conversion factory method every time.

Example

The following Dagger component uses type conversion to provision an environment variable as a long:

@Component(modules = RapierExampleComponentEnvironmentVariableModule.class)
public interface ExampleComponent {
    /**
     * Get timeout in milliseconds from environment variable TIMEOUT, or use the
     * default of 30000 if not present
     */
    @EnvironmentVariable(value="TIMEOUT", defaultValue="30000")
    public long timeout();
}

Environment variables are provided as a String by default. This method requests the environment variable TIMEOUT as a long, which causes Rapier to generate a @Provides method for the TIMEOUT environment variable as a long.

Standard conversions

From java.lang.String

Most Rapier modules provide their configuration data as a String by default. These modules generally support the following type conversions:

  • Primitives (i.e., byte, short, int, long, char, float, double, boolean)
  • Boxed primitives (i.e., Byte, Short, Integer, Long, Character, Float, Double, Boolean)
  • Types T with a method public static T fromString(String). In addition to custom user types, this adds support for the following built-in JRE types:
    • java.util.UUID
  • Types T with a method public static T valueOf(String)
  • Types T with a constructor public T(String). In addition to custom user types, this adds support for the following built-in JRE types:
    • java.net.URL
    • java.net.URI
    • java.io.File
    • java.math.BigDecimal
    • java.math.BigInteger

From java.util.List<java.lang.String>

Some Rapier modules also provide their configuration data as a List<String>. These modules generally support the following type conversions:

  • List types java.util.List<T> for any reference scalar type T with a conversion from String
  • Types T with a method public static T valueOf(List<String>)
  • Types T with a constructor public T(List<String>)

Exceptions

If the converted parameter is not nullable and the conversion results in a null value, then an IllegalStateException is thrown.

If the conversion results in an exception of any type, then a IllegalArgumentException is thrown with the original exception as the cause.

Manual conversions

Users can choose to perform manual Dagger conversions instead of relying on the standard Rapier-generated conversions.

For example, when using the Rapier environment variable module, Rapier automatically generates a standard conversion for the TIMEOUT environment variable to the requested int type for the specified component:

@Component(modules = RapierExampleComponentEnvironmentVariableModule.class)
public interface ExampleComponent {
    @EnvironmentVariable(value="TIMEOUT", defaultValue="30000")
    public int timeout();
}

/**
 * Rapier would generate code like this from the injection site in 
 * ExampleComponent.
 */
@Module
public class RapierExampleComponentEnvironmentVariableModule {
    @Provides
    @EnvironmentVariable(value="TIMEOUT", defaultValue="30000")
    public String provideEnvironmentVariableTimeoutAsString() {
        final String result=env.get("TIMEOUT");
        if(result == null)
            return "30000";
        return result;
    }

    @Provides
    @EnvironmentVariable(value="TIMEOUT", defaultValue="30000")
    public int provideEnvironmentVariableTimeoutAsInt(
            @EnvironmentVariable(value="TIMEOUT", defaultValue="30000")
            String value) {
        return Integer.parseInt(value);
    }       
}

Users who prefer to handle the conversion manually can use the following approach instead:

@Component(modules = {
    RapierExampleComponentEnvironmentVariableModule.class,
    ExampleModule.class
})
public interface ExampleComponent {
    /**
     * Users can use whatever qualifier they like, or no qualifier.
     * This is just for clarity in this example.
     */
    @Named("timeout")
    public int timeout();
}

/**
 * The user performs the conversion themselves with this module.
 */
@Module
public class ExampleModule {
    @Provides
    public int provideTimeoutAsInt(
            @EnvironmentVariable(value="TIMEOUT", defaultValue="30000")
            String value) {
        final int result=Integer.parseInt(value);
        // Custom validation logic here, or whatever
        return result;
    }
}

/**
 * Rapier would generate code like this from the injection site in 
 * ExampleModule.
 */
@Module
public class RapierExampleComponentEnvironmentVariableModule {
    @Provides
    @EnvironmentVariable(value="TIMEOUT", defaultValue="30000")
    public String provideEnvironmentVariableTimeoutAsString() {
        final String result=env.get("TIMEOUT");
        if(result == null)
            return "30000";
        return result;
    }
}

The main difference between the two approaches lies in how the @EnvironmentVariable annotation is used. The first example uses the @EnvironmentVariable annotation on the public int timeout() method, whereas the second example uses the @EnvironmentVariable annotation on the parameter of the public int provisionTimeoutAsInt(String) method.

This flexibility allows users to leverage Rapier to generate code for a default or convenient representation while providing their own modules with factory methods to handle the conversion instead of being forced to use the standard conversion. This approach works consistently across all Rapier modules.