Hi all,Coming from a Java background, I am having a hard time understanding how validation error propagation should work in clojure web APIs.To be clear, this is similar to how my Java web service would be setup:/** Method that validates the model, accesses the DB. If something went wrong, throw an exception */public void validateAndCreateUser(User u) throws ValidationException, EmailAlreadyInUseException, ... {...if(...) {throw new ValidationException(fieldName);} else if (...) {throw new EmailAlreadyInUseException(u.getEmail());}}/** Endpoint method, catches & formats the exceptions thrown by the db method. **/@POST("/api/user/create")public Response createUser (User u) {..try{validateAndCreateUser(u);return Response.ok();} catch (Exception e) {return generateExceptionResponse(e); //Method that maps exceptions to responses.}}For all of Java's clunkiness, this had the benefit of not having to write tons of if/else statements for validation handling. Exception were just thrown from anywhere, bubbling back up to inital call, and if not handled in the endpoint method, a specialized class mapped them into a proper response. The exceptions contained all the information needed to generate 'rich' error messages back to the client.Being a Clojure newbie, I wonder what a good pattern is for a similar situation. So far, I have a method that validates models based on a schema, that returns{:success true}or{:success false :errors ["error 1" "error 2" ...]}But I don't know how to avoid having to write if/else conditions of the sort in each function between my endpoint and db functions.(if (validation :success)(follow-normal-path)(handle-validation-errors validation))
Any guidance appreciated.Cheers,
Julien--
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
Hi I don't have much time to write a comprehensive answer, but if you want to follow the control via exceptions route, there's nothing preventing you from doing so.
You can (throw (ex-info msg map)), and then write a ring middle ware to handle exceptions globally, for instance.
HTH,
Laurent
One argument against using exceptions for commonplace occurrences (like invalid user input) is that the structure of the code may make it difficult to see where those things can pop up, which can lead to misunderstanding and introduction of bugs.
Even with Java's checked exceptions, where a certain method may be declared to throw a custom ValidationException or whatever, you can't see from the calling code which methods can throw one. You have to go look at the signatures of all called methods looking for them. (Or more likely you comment out the catch clause and see which calls your IDE underlines in red.) Using standard control-flow constructs make branch-points explicit.
Marko, do you have a good example of doing what you say?
If you responsibly keep to the "good parts", exceptions could be the way to go. Validation is one example where I love them because it happens all around, but validation failures are all handled uniformly.
On 20 March 2013 08:36, Marko Topolnik <marko.t...@gmail.com> wrote:If you responsibly keep to the "good parts", exceptions could be the way to go. Validation is one example where I love them because it happens all around, but validation failures are all handled uniformly.If validation happens "all around", that implies there is no one function that can test whether a value of data is valid for a given data store. This strikes me as a somewhat shaky foundation for a system.
There may be instances where it makes sense to use exceptions as a control flow mechanism, but I wonder whether it wouldn't be better to use something like CPS in those instances.
What I had in mind, related to OP's post, would be a middleware that does
(binding [*validation-failures* []]
(handler req)
(do-something-about *validation-failures*))
There would be a global function, such as add-failure, which would conj a new failure to the vector.
I would still code my own library because I am learning clojure at the moment.Question: how is it possible to guarantee that the *failure-list* or *error-list* is only available to the current request. Is that one of the properties of :dynamic?
Thank you all for your answers,I like Marko's approach.What I had in mind, related to OP's post, would be a middleware that does
(binding [*validation-failures* []]
(handler req)
(do-something-about *validation-failures*))
There would be a global function, such as add-failure, which would conj a new failure to the vector.Being a Clojure novice I had no idea that this was a possibility and it sounds like a better approach than exception throwing. Is this something similar to the way lib-noir does it? https://github.com/noir-clojure/lib-noir/blob/master/src/noir/validation.clj see the set-error fn.
-marko--
On Wednesday, March 20, 2013 4:34:32 PM UTC+1, James Reeves wrote:If validation happens "all around", that implies there is no one function that can test whether a value of data is valid for a given data store. This strikes me as a somewhat shaky foundation for a system.The idea is that all validation functions share the same contract to call the appropriate add-failure function that registers the validation result.
There may be instances where it makes sense to use exceptions as a control flow mechanism, but I wonder whether it wouldn't be better to use something like CPS in those instances.I can't picture how such a mechanism would work, and what benefit it would bring over the exceptions mechanism. CPS in Clojure means trampolining, which is quite an unwieldy, and I'd say "cheap" tack-on. A validating function would then be supposed to return a common, globally-defined "continuation", in fact just a simple function, that would redirect the flow towards the validation failure-handling case.
On 20 March 2013 16:41, Marko Topolnik <marko.t...@gmail.com> wrote:On Wednesday, March 20, 2013 4:34:32 PM UTC+1, James Reeves wrote:If validation happens "all around", that implies there is no one function that can test whether a value of data is valid for a given data store. This strikes me as a somewhat shaky foundation for a system.The idea is that all validation functions share the same contract to call the appropriate add-failure function that registers the validation result.I don't see why that would be necessary. Why not put all the validation logic in one place?
There may be instances where it makes sense to use exceptions as a control flow mechanism, but I wonder whether it wouldn't be better to use something like CPS in those instances.I can't picture how such a mechanism would work, and what benefit it would bring over the exceptions mechanism. CPS in Clojure means trampolining, which is quite an unwieldy, and I'd say "cheap" tack-on. A validating function would then be supposed to return a common, globally-defined "continuation", in fact just a simple function, that would redirect the flow towards the validation failure-handling case.I wasn't thinking of validation when I suggested CPS, because I don't see a need for any unusual control flow when validating data.