Permalink
Browse files

Added initial(alpha) support for validator pre-conditions. Updated do…

…cs and tests. Bumped version to 0.2.3-beta1
  • Loading branch information...
1 parent 97b036e commit 9dc0eec747f27c1f800d0ab5941bd775b027f6aa @leonardoborges committed Mar 15, 2013
Showing with 97 additions and 22 deletions.
  1. +22 −1 CHANGELOG.md
  2. +27 −1 README.md
  3. +15 −10 docs/uberdoc.html
  4. +1 −1 project.clj
  5. +14 −9 src/bouncer/core.clj
  6. +18 −0 test/bouncer/core_test.clj
View
@@ -1,7 +1,28 @@
## 0.2.3 (UNRELEASED)
- Validator sets can now be used at the top level call to `validate` and `valid?`.
-- Added tested and a doc section around validation pipelining. It was an undocumented invariant. See discussion [here](https://github.com/leonardoborges/bouncer/pull/4).
+
+ ```clojure
+ (defvalidatorset address-validator
+ :postcode v/required)
+
+ (defvalidatorset person-validator
+ :name v/required
+ :age [v/required v/number]
+ :address address-validator)
+
+ (core/validate {} person-validator)
+ ```
+
+- Added tests and a doc section around validation pipelining. It was an undocumented invariant. See discussion [here](https://github.com/leonardoborges/bouncer/pull/4).
+- (alpha) Validators now support a pre-condition option. If it is met, the validator will run, otherwise it's just skipped:
+
+ ```clojure
+ (core/valid? {:a 1 :b "X"}
+ :b (v/member #{"Y" "Z"} :pre (comp pos? :a)))
+
+ ;; false
+ ```
## 0.2.2 (16/01/2013)
View
@@ -13,6 +13,7 @@ A validation DSL for Clojure apps
* [Multiple validation errors](#multiple-validation-errors)
* [Validating collections](#validating-collections)
* [Validation pipelining](#validation-pipelining)
+ * [Pre-conditions](#pre-conditions)
* [Composability: validator sets](#composability-validator-sets)
* [Customization support](#customization-support)
* [Custom validators using arbitrary functions](#custom-validations-using-arbitrary-functions)
@@ -200,6 +201,31 @@ Note that if a map if pipelined through multiple validators, bouncer will leave
;; {:age ("age must be a number"), :name ("name must be present")}
```
+### Pre-conditions
+
+Validators can take a pre-condition option `:pre` that causes it to be executed only if the given pre-condition - a truthy function - is met.
+
+Consider the following:
+
+```clojure
+(core/valid? {:a -1 :b "X"}
+ :b (v/member #{"Y" "Z"} :pre (comp pos? :a)))
+
+;; true
+```
+
+As you can see the value of `b` is clearly not in the set `#{"Y" "Z"}`, however the whole validation passes because the `v/member` check states is should only be run if `:a` is positive.
+
+Let's now make it fail:
+
+```clojure
+(core/valid? {:a 1 :b "X"}
+ :b (v/member #{"Y" "Z"} :pre (comp pos? :a)))
+
+;; false
+```
+
+
## Composability: validator sets
If you find yourself repeating a set of validators over and over, chances are you will want to group and compose them somehow. The macro `bouncer.validators/defvalidatorset` does just that:
@@ -365,7 +391,7 @@ Feedback to both this library and this guide is welcome.
## TODO
- Add more validators (help is appreciated here)
-- Validator pre-conditions
+- Docs are getting a bit messy. Fix that.
## CONTRIBUTORS
View
@@ -3028,7 +3028,7 @@
build_tree: build_tree
};
})(SyntaxHighlighter);
-</script><title>bouncer -- Marginalia</title></head><body><table><tr><td class="docs"><div class="header"><h1 class="project-name">bouncer</h1><h2 class="project-version">0.2.3-RC1</h2><br /><p>A validation DSL for Clojure apps</p>
+</script><title>bouncer -- Marginalia</title></head><body><table><tr><td class="docs"><div class="header"><h1 class="project-name">bouncer</h1><h2 class="project-version">0.2.3-beta1</h2><br /><p>A validation DSL for Clojure apps</p>
</div><div class="dependencies"><h3>dependencies</h3><table><tr><td class="dep-name">org.clojure/clojure</td><td class="dotted"><hr /></td><td class="dep-version">1.4.0</td></tr><tr><td class="dep-name">org.clojure/algo.monads</td><td class="dotted"><hr /></td><td class="dep-version">0.1.0</td></tr></table></div></td><td class="codes" style="text-align: center; vertical-align: middle;color: #666;padding-right:20px"><br /><br /><br />(this space intentionally left almost blank)</td></tr><tr><td class="docs"><div class="toc"><a name="toc"><h3>namespaces</h3></a><ul><li><a href="#bouncer.core">bouncer.core</a></li><li><a href="#bouncer.helpers">bouncer.helpers</a></li><li><a href="#bouncer.validators">bouncer.validators</a></li></ul></div></td><td class="codes">&nbsp;</td></tr><tr><td class="docs"><div class="docs-header"><a class="anchor" href="#bouncer.core" name="bouncer.core"><h1 class="project-name">bouncer.core</h1><a class="toc-link" href="#toc">toc</a></a></div></td><td class="codes" /></tr><tr><td class="docs"><p>The <code>core</code> namespace provides the two main validation macros in bouncer:</p>
<ul>
@@ -3097,7 +3097,9 @@
(var-get (h/resolve-or-same sym-or-coll)))))
:else (conj acc `[~(h/resolve-or-same sym-or-coll) ~key-or-vec])))
[]
- (partition 2 forms)))</pre></td></tr><tr><td class="docs"><p>Wraps pred in the context of validating a single value</p>
+ (partition 2 forms)))</pre></td></tr><tr><td class="docs">
+</td><td class="codes"><pre class="brush: clojure">(defn pre-condition-met? [pre-fn map]
+ (or (nil? pre-fn) (pre-fn map)))</pre></td></tr><tr><td class="docs"><p>Wraps pred in the context of validating a single value</p>
<ul>
<li><p><code>acc</code> is the map being validated</p></li>
@@ -3106,7 +3108,8 @@
<li><p><code>args</code> any extra args to pred</p>
<p>It only runs pred if:</p></li>
-<li><p>the validator is optional <em>and</em> there is a non-nil value to be validated (this information is read from pred's metadata)</p></li>
+<li><p>the validator contains a pre-condition <em>and</em> it is met or;</p></li>
+<li>the validator is optional <em>and</em> there is a non-nil value to be validated (this information is read from pred's metadata) or;</li>
<li><p>there are no previous errors for the given path</p>
<p>Returns <code>acc</code> augmented with a namespace qualified ::errors keyword</p></li>
@@ -3118,14 +3121,16 @@
error-path (cons ::errors k)
{:keys [default-message-format optional]} (meta pred)
[args opts] (split-with (complement keyword?) args)
- {:keys [message] :or {message default-message-format}} (apply hash-map opts)
+ {:keys [message pre] :or {message default-message-format}} (apply hash-map opts)
pred-subject (get-in acc k)]
- (if (or (and optional (nil? pred-subject))
- (not (empty? (get-in acc error-path)))
- (apply pred pred-subject args))
- acc
- (update-in acc error-path
- #(conj % (format message (name (peek k))))))))</pre></td></tr><tr><td class="docs"><p>Internal Use.</p>
+ (if (pre-condition-met? pre acc)
+ (if (or (and optional (nil? pred-subject))
+ (not (empty? (get-in acc error-path)))
+ (apply pred pred-subject args))
+ acc
+ (update-in acc error-path
+ #(conj % (format message (name (peek k))))))
+ acc)))</pre></td></tr><tr><td class="docs"><p>Internal Use.</p>
<p> Chain of responsibility.</p>
View
@@ -1,4 +1,4 @@
-(defproject bouncer "0.2.3-SNAPSHOT"
+(defproject bouncer "0.2.3-beta1"
:description "A validation DSL for Clojure apps"
:url "http://github.com/leonardoborges/bouncer"
:license {:name "MIT License"
View
@@ -82,6 +82,9 @@ If you'd like to know more about the motivation behind `bouncer`, check the
[]
(partition 2 forms)))
+(defn pre-condition-met? [pre-fn map]
+ (or (nil? pre-fn) (pre-fn map)))
+
(defn wrap
"Wraps pred in the context of validating a single value
@@ -95,8 +98,8 @@ If you'd like to know more about the motivation behind `bouncer`, check the
It only runs pred if:
- - the validator is optional *and* there is a non-nil value to be validated (this information is read from pred's metadata)
-
+ - the validator contains a pre-condition *and* it is met or;
+ - the validator is optional *and* there is a non-nil value to be validated (this information is read from pred's metadata) or;
- there are no previous errors for the given path
Returns `acc` augmented with a namespace qualified ::errors keyword
@@ -107,14 +110,16 @@ If you'd like to know more about the motivation behind `bouncer`, check the
error-path (cons ::errors k)
{:keys [default-message-format optional]} (meta pred)
[args opts] (split-with (complement keyword?) args)
- {:keys [message] :or {message default-message-format}} (apply hash-map opts)
+ {:keys [message pre] :or {message default-message-format}} (apply hash-map opts)
pred-subject (get-in acc k)]
- (if (or (and optional (nil? pred-subject))
- (not (empty? (get-in acc error-path)))
- (apply pred pred-subject args))
- acc
- (update-in acc error-path
- #(conj % (format message (name (peek k))))))))
+ (if (pre-condition-met? pre acc)
+ (if (or (and optional (nil? pred-subject))
+ (not (empty? (get-in acc error-path)))
+ (apply pred pred-subject args))
+ acc
+ (update-in acc error-path
+ #(conj % (format message (name (peek k))))))
+ acc)))
(defn wrap-chain
"Internal Use.
View
@@ -244,3 +244,21 @@
::core/errors)]
(is (= 2
(count (select-keys validation-errors [:age :name])))))))
+
+(deftest preconditions
+ (testing "runs the current validation only if the pre-condition is met"
+ (is (core/valid? {:a 1 :b "Z"}
+ :b (v/member #{"Y" "Z"} :pre (comp pos? :a))))
+
+ (is (not (core/valid? {:a 1 :b "X"}
+ :b (v/member #{"Y" "Z"} :pre (comp pos? :a)))))
+
+ (is (core/valid? {:a -1 :b "Z"}
+ :b (v/member #{"Y" "Z"} :pre (comp pos? :a))))
+
+ (is (core/valid? {:a -1 :b "X"}
+ :b (v/member #{"Y" "Z"} :pre (comp pos? :a))))
+
+ (is (not (core/valid? {:a 1 :b "Z"}
+ :b (v/member #{"Y" "Z"} :pre (comp pos? :a))
+ :c v/required)))))

0 comments on commit 9dc0eec

Please sign in to comment.