Micronaut supports the RFC-6570 URI template specification to define URI variables in a path definition. The path definition can be a value of the @Controller
annotation or any of the routing annotations for example @Get
or @Post
. We can define a path variable as {?binding*}
to support binding of request parameters to all properties of an object type that is defined as method argument with the name binding
. We can even use the Bean Validation API (JSR380) to validate the values of the request parameters if we add an implementation of this API to our class path.
In the following example controller we have the method items
with method argument sorting
of type Sorting
. We want to map request parameters ascending
and field
to the properties of the Sorting
object. We only have the use the path variable {?sorting*}
to make this happen. We also add the dependency io.micronaut.configuration:micronaut-hibernate-validator
to our class path. If we use Gradle we can add compile("io.micronaut.configuration:micronaut-hibernate-validator")
to our build file.
package mrhaki; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; import io.micronaut.validation.Validated; import javax.validation.Valid; import javax.validation.constraints.Pattern; import java.util.List; @Controller("/sample") @Validated // Enable validation of Sorting properties. public class SampleController { private final SampleComponent sampleRepository; public SampleController(final SampleComponent sampleRepository) { this.sampleRepository = sampleRepository; } // Using the syntax {?sorting*} we can assign request parameters // to a POJO, where the request parameter name matches a property // name in the POJO. The name 'must match the argument // name of our method, which is 'sorting' in our example. // The properties of the POJO can use the Validation API to // define constraints and those will be validated if we use // @Valid for the method argument and @Validated at the class level. @Get("/{?sorting*}") public List<Item> items(@Valid final Sorting sorting) { return sampleRepository.allItems(sorting.getField(), sorting.getDirection()); } private static class Sorting { private boolean ascending = true; @Pattern(regexp = "name|city", message = "Field must have value 'name' or 'city'.") private String field = "name"; private String getDirection() { return ascending ? "ASC" : "DESC"; } public boolean isAscending() { return ascending; } public void setAscending(final boolean ascending) { this.ascending = ascending; } public String getField() { return field; } public void setField(final String field) { this.field = field; } } }
Let's write a test to check that the binding of the request parameters happens correctly. We use the Micronaut test support for Spock so we can use the @Micronaut
and @MockBean
annotations. We add a dependency on io.micronaut:micronaut-test-spock
to our build, which is testCompile("io.micronaut.test:micronaut-test-spock:1.0.2")
if we use a Gradle build.
package mrhaki import io.micronaut.http.HttpStatus import io.micronaut.http.client.RxHttpClient import io.micronaut.http.client.annotation.Client import io.micronaut.http.client.exceptions.HttpClientResponseException import io.micronaut.http.uri.UriTemplate import io.micronaut.test.annotation.MicronautTest import io.micronaut.test.annotation.MockBean import spock.lang.Specification import javax.inject.Inject @MicronautTest class SampleControllerSpec extends Specification { // Client to test the /sample endpoint. @Inject @Client("/sample") RxHttpClient httpClient // Will inject mock created by sampleRepository method. @Inject SampleComponent sampleRepository // Mock for SampleRepository to check method is // invoked with correct arguments. @MockBean(SampleRepository) SampleComponent sampleRepository() { return Mock(SampleComponent) } void "sorting request parameters are bound to Sorting object"() { given: // UriTemplate to expand field and ascending request parameters with values. // E.g. ?field=name&expanding=false. final requestURI = new UriTemplate("/{?field,ascending}").expand(field: paramField, ascending: paramAscending) when: httpClient.toBlocking().exchange(requestURI) then: 1 * sampleRepository.allItems(sortField, sortDirection) >> [] where: paramField | paramAscending | sortField | sortDirection null | null | "name" | "ASC" null | false | "name" | "DESC" null | true | "name" | "ASC" "city" | false | "city" | "DESC" "city" | true | "city" | "ASC" "name" | false | "name" | "DESC" "name" | true | "name" | "ASC" } void "invalid sorting field should give error response"() { given: final requestURI = new UriTemplate("/{?field,ascending}").expand(field: "invalid") when: httpClient.toBlocking().exchange(requestURI) then: final HttpClientResponseException clientResponseException = thrown() clientResponseException.response.status == HttpStatus.BAD_REQUEST clientResponseException.message == "sorting.field: Field must have value 'name' or 'city'." } }
Written with Micronaut 1.0.4.