Search

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.