Search

Dark theme | Light theme
Showing posts with label MicronautMastery:Configuration. Show all posts
Showing posts with label MicronautMastery:Configuration. Show all posts

March 27, 2019

Micronaut Mastery: Parse String Value With Kb/Mb/Gb To Number

Micronaut can convert String values defined as a number followed by (case-insensitive) KB/MB/GB to a number value in certain cases. The conversion service in Micronaut supports the @ReadableBytes annotation that we can apply to a method parameter. Micronaut will then parse the String value and convert it to a number. The value 1Kb is converted to 1024. We can use this for example in a configuration class or path variable in a controller.

In the following example we have a configuration class annotated with @ConfigurationProperties with the property maxFileSize. We use the @ReadableBytes annotation to support setting the value with a String value:

package mrhaki;

import io.micronaut.context.annotation.ConfigurationProperties;
import io.micronaut.core.convert.format.ReadableBytes;

@ConfigurationProperties(SampleConfig.SAMPLE_CONFIG_PREFIX)
public class SampleConfig {
    
    public static final String SAMPLE_CONFIG_PREFIX = "sample.config";
    
    private long maxFileSize;

    /**
     * Use @ReadableBytes for parameter {@code maxFileSize}
     * to convert a String value formatted as number followed
     * by "KB", "MB" or "GB" (case-insensitive).
     * 
     * @param maxFileSize Maximum file size
     */
    public void setMaxFileSize(@ReadableBytes long maxFileSize) {
        this.maxFileSize = maxFileSize;
    }

    public long getMaxFileSize() {
        return maxFileSize;
    }
    
}

Let's write a Spock specification to test the conversion of String values to numbers:

package mrhaki

import io.micronaut.context.ApplicationContext
import spock.lang.Specification;

class SampleConfigSpec extends Specification {

    void "set maxFileSize configuration property with KB/MB/GB format"() {
        given:
        final ApplicationContext context =
                ApplicationContext.run("sample.config.maxFileSize": maxFileSize)

        when:
        final SampleConfig sampleConfig = context.getBean(SampleConfig)

        then:
        sampleConfig.maxFileSize == result

        where:
        maxFileSize || result
        "20KB"      || 20_480
        "20kb"      || 20 * 1024
        "1MB"       || 1_048_576
        "1Mb"       || 1 * 1024 * 1024
        "3GB"       || 3L * 1024 * 1024 * 1024
        113         || 113
    }
}

In another example we use the conversion on a path variable parameter in a controller method:

package mrhaki;

import io.micronaut.core.convert.format.ReadableBytes;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;

@Controller
public class SampleController {
    
    @Get("/{size}")
    public long size(@ReadableBytes final long size) {
        return size;
    }
    
}

And with the following test we can see if the conversion is done correctly:

package mrhaki

import io.micronaut.context.ApplicationContext
import io.micronaut.http.client.HttpClient
import io.micronaut.runtime.server.EmbeddedServer
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification

class SampleControllerSpec extends Specification {

    @Shared
    @AutoCleanup
    private static EmbeddedServer server = ApplicationContext.run(EmbeddedServer)

    @Shared
    @AutoCleanup
    private static HttpClient httpClient = HttpClient.create(server.URL)

    void "return size converted from String value with unit KB/MB/GB"() {
        expect:
        httpClient.toBlocking().retrieve("/$size") == result

        where:
        size   || result
        "20KB" || "20480"
        "20kb" || (20 * 1024).toString()
        "1MB"  || 1_048_576.toString()
        "3GB"  || (3L * 1024 * 1024 * 1024).toString()
        113    || "113"

    }
}

Written with Micronaut 1.0.4.

October 3, 2018

Micronaut Mastery: Configuration Property Name Is Lowercased And Hyphen Separated

In Micronaut we can inject configuration properties in different ways into our beans. We can use for example the @Value annotation using a string value with a placeholder for the configuration property name. If we don't want to use a placeholder we can also use the @Property annotation and set the name attribute to the configuration property name. We have to pay attention to the format of the configuration property name we use. If we refer to a configuration property name using @Value or @Property we must use lowercased and hyphen separated names (also known as kebab casing). Even if the name of the configuration property is camel cased in the configuration file. For example if we have a configuration property sample.theAnswer in our application.properties file, we must use the name sample.the-answer to get the value.

In the following Spock specification we see how to use it in code. The specification defines two beans that use the @Value and @Property annotations and we see that we need to use kebab casing for the configuration property names, even though we use camel casing to set the configuration property values:

package mrhaki

import groovy.transform.CompileStatic
import io.micronaut.context.ApplicationContext
import io.micronaut.context.annotation.Property
import io.micronaut.context.annotation.Value
import io.micronaut.context.exceptions.DependencyInjectionException
import spock.lang.Shared
import spock.lang.Specification

import javax.inject.Singleton

class ConfigurationPropertyNameSpec extends Specification {

    // Create application context with two configuration 
    // properties: reader.maxFileSize and reader.showProgress.
    @Shared
    private ApplicationContext context = 
            ApplicationContext.run('reader.maxFileSize': 1024, 
                                   'reader.showProgress': true)

    void "use kebab casing (hyphen-based) to get configuration property value"() {
        expect:
        with(context.getBean(FileReader)) {
            maxFileSize == 1024   
            showProgress == Boolean.TRUE
        }
    }

    void "using camel case to get configuration property should throw exception"() {
        when:
        context.getBean(InvalidFileReader).maxFileSize

        then:
        final dependencyException = thrown(DependencyInjectionException)
        dependencyException.message == """\
            |Failed to inject value for parameter [maxFileSize] of method [setMaxFileSize] of class: mrhaki.InvalidFileReader
            |
            |Message: Error resolving property value [\${reader.maxFileSize}]. Property doesn't exist
            |Path Taken: InvalidFileReader.setMaxFileSize([Integer maxFileSize])""".stripMargin()
    }
}

@CompileStatic
@Singleton
class FileReader {
    
    private Integer maxFileSize
    private Boolean showProgress
    
    // Configuration property names 
    // are normalized and 
    // stored lowercase hyphen separated (= kebab case).
    FileReader(
            @Property(name ='reader.max-file-size') Integer maxFileSize,
            @Value('${reader.show-progress:false}') Boolean showProgress) {
        
        this.maxFileSize = maxFileSize
        this.showProgress = showProgress
    }
    
    Integer getMaxFileSize() {
        return maxFileSize
    }
    
    Boolean showProgress() {
        return showProgress
    }
}

@CompileStatic
@Singleton
class InvalidFileReader {
    // Invalid reference to property name,
    // because the names are normalized and
    // stored lowercase hyphen separated.
    @Value('${reader.maxFileSize}')
    Integer maxFileSize
}

Written with Micronaut 1.0.0.RC1.