The Pair class in Ratpack is an easy way to create a growing data structure, passed on via Promise methods. A Pair object has a left and right part containing data. These parts can even be other Pair objects. Since Ratpack 1.4.0 the Promise class has methods to set the right or left part of a Pair: left, flatLeft, right and flatRight. The result of these methods is a Promise<Pair> object. The input can be Promise type or a Function that can use a previous Promise.

In the following example specification we use the different new methods to create a Pair. We also create a simple Ratpack server with a asynchronous HTTP client implementation to simulate remote calls returning a Promise:

package mrhaki

import ratpack.exec.Promise
import ratpack.func.Pair
import ratpack.groovy.test.embed.GroovyEmbeddedApp
import ratpack.http.HttpUrlBuilder
import ratpack.http.client.HttpClient
import ratpack.test.embed.EmbeddedApp
import ratpack.test.exec.ExecHarness
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification

class PromisePairSpec extends Specification {

    /**
     * Simple server to serve /{value} and /{value}/size
     * GET requests.
     */
    @Shared
    @AutoCleanup
    private EmbeddedApp serverApi = GroovyEmbeddedApp.of({
        handlers {
            get(':value') { render pathTokens.value }
            get(':value/size') { render String.valueOf(pathTokens.value.size()) }
        }
    })

    /**
     * Asynchronous HTTP client.
     */
    @AutoCleanup
    private HttpClient api = HttpClient.of { client ->
        client.poolSize 1
    }

    def "set right side of Pair with result of function using initial Promise value"() {
        expect:
        ExecHarness.yieldSingle {
            Promise.value('Ratpack')
                    // Use Promise 'Ratpack' in right method as argument.
                   .right { s -> s.size() }
        }.value == Pair.of('Ratpack', 7)
    }

    def "set right side of Pair with result of other Promise"() {
        expect:
        ExecHarness.yieldSingle {
            Promise.value('Ratpack is')
                   // Use Promise value
                   .right(getApiText('cool'))
        }.value == Pair.of('Ratpack is', 'cool')
    }

    def "set right side of Pair with result Promise of function using initial Promise value"() {
        expect:
        ExecHarness.yieldSingle {
            Promise.value('Ratpack')
                   // Use Promise 'Ratpack' in flatRight method as argument.
                   .flatRight { s -> getApiText("${s}/size") }
        }.value == Pair.of('Ratpack', '7')
    }

    def "set right side of Pair with result Promise of function using initial Promise value using flatMap"() {
        expect:
        ExecHarness.yieldSingle {
            Promise.value('Ratpack')
                    // Use Promise 'Ratpack' in flatMap method as argument.
                    // This is the way to set the Pair values
                    // before the flatRight method was added
                   .flatMap { s -> getApiText("${s}/size").map { content -> Pair.of(s, content) } }
        }.value == Pair.of('Ratpack', '7')
    }

    def "set left side of Pair with result of function using initial Promise value"() {
        expect:
        ExecHarness.yieldSingle {
            Promise.value('Ratpack')
                    // Use Promise 'Ratpack' in left method as argument.
                   .left { s -> s.size() }
        }.value == Pair.of(7, 'Ratpack')
    }

    def "set left side of Pair with result of other Promise"() {
        expect:
        ExecHarness.yieldSingle {
            Promise.value('cool')
                   // Get Promise value without using Promise 'cool' value.
                   .left(getApiText('Ratpack is'))
        }.value == Pair.of('Ratpack is', 'cool')
    }

    def "set left side of Pair with result Promise of function using initial Promise value"() {
        expect:
        ExecHarness.yieldSingle {
            Promise.value('Ratpack')
                    // Use Promise 'Ratpack' in flatLeft method as argument.
                   .flatLeft { s -> getApiText("${s}/size") }
        }.value == Pair.of('7', 'Ratpack')
    }

    private Promise getApiText(final String path) {
        api.get(createRequest(path))
           .map { response -> response.body.text }
    }

    private URI createRequest(final String path) {
        HttpUrlBuilder.base(serverApi.address).path(path).build()
    }
} 

Written with Ratpack 1.4.3.

Original blog post

shadow-left