Fork me on GitHub
#ring
<
2016-12-13
>
tgk07:12:50

One thing that has always been a bit of a sore point for me in Clojure web development is how to handle parameter validation and sequential execution in general. For example, in Rails it’s trivial to terminate control flow early if parameters are invalid, if the current user is not authorised to perform the operation she attempts, etc. In Clojure this can be done using nested conditionals but that gets convoluted quite quickly.

tgk07:12:33

I’ve used a technique similar to monads/ring handlers in which a handler is wrapped with the steps that we go through such that we can terminate at any given point. For example wrap-check-params returns a handler that will check if the needed parameters are there and on the right format before calling the next handler in the stack. If they are not, it will return an appropriate HTTP status code.

tgk07:12:08

This works but I can’t help but feel there might be a better solution out there somewhere. Is there something I’m missing? Some amazing technique that I just haven’t stumbled upon?

seancorfield07:12:42

@tgk take a look at clojure.spec -- that's what we're using now for parameter validation in web apps.

tgk07:12:50

@seancorfield: thanks, we’re already using clojure.spec. It’s a great tool but it doesn’t solve the control flow problem.

seancorfield07:12:41

(let [params (s/conform ::my-arg-spec (:params req))]
  (if (s/invalid? params)
    ...explain-data and report error...
    (do-stuff-with params)))
You don't like that?

tgk07:12:19

Exactly. Because often do-stuff-with will contain a new conditional and so on and so on.

tgk07:12:42

Parameters were just an example of a conditional branch that incurs a new nesting point.

seancorfield07:12:03

Ah, because you need to check more than just the shape of the params? You need to check their values too, such as unique username or valid password or such?

tgk07:12:28

Sure, or I need to look up data based on the parameters and check that the current user has the right to manipulate the data

tgk07:12:43

In general, I just need to break execution at any given point

seancorfield07:12:54

I generally find a simple cond works in most cases so it doesn't get very nested...

seancorfield07:12:06

...but, sure, sometimes your control logic is inherently complex.

seancorfield07:12:22

That should be fairly rare tho' really...?

tgk07:12:33

Ah, alright. I can see how that can collapse it a bit.

tgk07:12:04

I hadn’t though about that being a sequential execution but I guess you could use it as such in many cases. I’ll give that a try!

seancorfield07:12:24

The other thing to consider is a variant of cond-> that threads the value (the req) through the predicates as well. We call that condp-> (and we have a condp->> macro as well).

tgk07:12:13

Hmm yes. I can never figure out if I ever grow fond of that form but maybe it’s time to revisit it!