3

I'm wondering how to call a closure from a closure that's being used with a DSL. For example, let's take the RestBuilder plugin for Grails.

Imagine I have several blocks in a row like:

rest.post("http://my.domain/url") {
    auth(username, password)
    contentType "text/xml"
    body someContent
}

... where the only thing changing is the someContent. It gets repetitive to call auth and contentType and body each time. So I'd like to do something like:

def oauth = [clientId: 'c', clientSecret: 's']

def withAuth(Closure toWrap) {
    Closure wrapped = { it ->
        auth(oauth.clientId, oauth.clientSecret)
        contentType "text/xml"
        toWrap.call()
    }
    return wrapped
}

rest.post("http://my.domain/url") (withAuth {
    body someContent
})

Now, I'd like wrapped and toWrap to have access to auth and contentType as defined in the RestBuilder DSL. Is there a way I can do this by setting owners, delegates, or suchlike?

(Note: I understand in the example above that I could just declare a function that takes a URL + content as argument, and just call rest.post within the function. My question is more general -- I'm looking to understand the language, and for functional techniques I can apply more broadly.)

Bosh
  • 8,138
  • 11
  • 51
  • 77
  • 1
    probably setting `delegate` will be enough: `toWrap.delegate = delegate; toWrap.call()` – Igor Artamonov Aug 27 '13 at 04:21
  • That doesn't quite do the trick, because when it's called, the code in `wrapped` can't see oauth in the top scope. – Bosh Aug 27 '13 at 05:34
  • Ah, I see I need to define `oauth` as a @Field in my script (http://stackoverflow.com/questions/6305910/how-do-we-create-and-access-the-global-variables-in-groovy) – Bosh Aug 27 '13 at 05:45

2 Answers2

3

Thanks to @igor-artamonov, I have the following working approach. Note that I changed withAuth from a function to a closure so that it could access script-level state without having to declare @Field variables on the script.

def oauth = [clientId: 'c', clientSecret: 's']

def withAuth { Closure toWrap ->
   return { 
      auth(oauth.clientId, oauth.clientSecret)
      contentType "text/xml"
      toWrap.delegate = delegate
      toWrap.call()
   }
}

rest.post("http://my.domain/url", withAuth {
    body someContent
})
Bosh
  • 8,138
  • 11
  • 51
  • 77
1

You can take advantage of the fact that the syntax there of foo(params) { ... } is syntactic sugar for foo(params, { ... }):

def c = { oauth, b ->
   auth(oauth.clientId, oauth.clientSecret)
   contentType "text/xml"
   body b
}

...

def doPost(String body) {
   rest.post("http://my.domain/url", c.clone().curry(oauth, body))
}

Cloning the closure each time prevents stale state, and currying the values caches them in the closure so they're available when it's invoked by the Rest builder.

Burt Beckwith
  • 75,342
  • 5
  • 143
  • 156
  • This is a great bit of syntax to know, but it doesn't quite get at the question of how I can compose closures: in this case, you're passing a single closure to `rest.post` (and that closure, returned by `curry`, doesn't run any closures inside). – Bosh Aug 27 '13 at 05:32