Ratpacked: Render Optional Type Instance
Ratpack uses renderers to render objects with the render
method of the Context
class.
Ratpack has several renderers that are available automatically.
One of those renderers is the OptionalRenderer
.
When we want to render an Optional
object this renderer is selected by Ratpack.
If the Optional
instance has a value the value is passed to the render
method.
If the value is not present a 404 client error is returned.
In the following example application we have a RecipeRepository
class with a findRecipeByName
method.
This method returns Promise<Optional<Recipe>>
:
package mrhaki.ratpack; import ratpack.exec.Promise; import java.util.Optional; public interface RecipeRepository { Promise<Optional<Recipe>> findRecipeByName(final String name); }
We have a Handler
that will use the findRecipeByName
method and then render the Optional<Recipe>
object.
The following example application shows the handler implementation:
package mrhaki.ratpack;
import ratpack.func.Action;
import ratpack.handling.Chain;
import ratpack.handling.Handler;
import ratpack.registry.RegistrySpec;
import ratpack.server.RatpackServer;
import java.util.Optional;
public class Application {
public static void main(String[] args) throws Exception {
new Application().startServer();
}
void startServer() throws Exception {
RatpackServer.start(server -> server
.registryOf(registry())
.handlers(chain()));
}
private Action registry() {
return registry -> registry
.add(new RecipeRenderer())
.add(RecipeRepository.class, new RecipesList());
}
private Action chain() {
return chain -> chain.post("recipe", recipeHandler());
}
private Handler recipeHandler() {
return ctx -> ctx
.parse(RecipeRequest.class)
.flatMap(recipeRequest -> ctx
.get(RecipeRepository.class)
.findRecipeByName(recipeRequest.getName()))
.then((Optional optionalRecipe) -> ctx.render(optionalRecipe));
}
}
The application also uses a custom RecipeRenderer
. This renderer is used when the Optional<Recipe>
has a value:
package mrhaki.ratpack;
import ratpack.handling.Context;
import ratpack.render.RendererSupport;
import static ratpack.jackson.Jackson.json;
public class RecipeRenderer extends RendererSupport {
@Override
public void render(final Context ctx, final Recipe recipe) throws Exception {
ctx.render(json(recipe));
}
}
Let’s write a specification where we can test that a client error with status code 404 is returned when the Optional
is empty.
Otherwise the actual value is rendered:
package mrhaki.ratpack
import groovy.json.JsonSlurper
import ratpack.exec.Promise
import ratpack.http.MediaType
import ratpack.impose.ImpositionsSpec
import ratpack.impose.UserRegistryImposition
import ratpack.registry.Registry
import ratpack.test.MainClassApplicationUnderTest
import spock.lang.Specification
import spock.lang.Subject
import static groovy.json.JsonOutput.toJson
class ApplicationSpec extends Specification {
private RecipeRepository recipeMock = Mock()
@Subject
private aut = new MainClassApplicationUnderTest(Application) {
@Override
protected void addImpositions(final ImpositionsSpec impositions) {
// Add mock for RecipeRepository.
impositions.add(UserRegistryImposition.of(Registry.of { registry ->
registry.add(RecipeRepository, recipeMock)
}))
}
}
private httpClient = aut.httpClient
void 'response status 404 when Optional is empty'() {
when:
def response = httpClient.requestSpec { requestSpec ->
requestSpec.headers.set 'Content-type', MediaType.APPLICATION_JSON
requestSpec.body { body ->
body.text(toJson(name: 'sushi'))
}
}.post('recipe')
then:
1 * recipeMock.findRecipeByName('sushi') >> Promise.value(Optional.empty())
and:
response.statusCode == 404
}
void 'render Recipe when Optional is not empty'() {
when:
def response = httpClient.requestSpec { requestSpec ->
requestSpec.headers.set 'Content-type', MediaType.APPLICATION_JSON
requestSpec.body { body ->
body.text(toJson(name: 'macaroni'))
}
}.post('recipe')
then:
1 * recipeMock.findRecipeByName('macaroni') >> Promise.value(Optional.of(new Recipe('macaroni')))
and:
response.statusCode == 200
and:
def recipe = new JsonSlurper().parseText(response.body.text)
recipe.name == 'macaroni'
}
}
Written with Ratpack 1.4.5.