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>>
:
// File: src/main/java/mrhaki/ratpack/RecipeRepository.java 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:
// File: src/main/java/mrhaki/ratpack/Application.java 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<RegistrySpec> registry() { return registry -> registry .add(new RecipeRenderer()) .add(RecipeRepository.class, new RecipesList()); } private Action<Chain> 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<Recipe> optionalRecipe) -> ctx.render(optionalRecipe)); } }
The application also uses a custom RecipeRenderer
. This renderer is used when the Optional<Recipe>
has a value:
// File: src/main/java/mrhaki/ratpack/RecipeRenderer.java package mrhaki.ratpack; import ratpack.handling.Context; import ratpack.render.RendererSupport; import static ratpack.jackson.Jackson.json; public class RecipeRenderer extends RendererSupport<Recipe> { @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:
// File: src/test/groovy/mrhaki/ratpack/ApplicationSpec.groovy 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<Recipe> 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<Recipe> 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.