Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validations specification #1

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open

Conversation

maxweber
Copy link

Hi,

I have added some functions which allows to specify validations in an ordinary map. In this way it is possible that the validation specification looks almost like the maps, which should be validated. The doc of the validation-spec function shows a full-blown example. The new functions have also a corresponding unit test. Validations for nested elements of a map are also supported. What do you think of it?

Best regards,

Max

… ordinary map. The support for validations for nested elements of a map has also been added.
@SergeyDidenko
Copy link

Nice addition!

But in the current form validation-spec does not handle cases when only one fail is enough to fail. I.e. it is similar to a tree of "validations" calls, without possibility to use "validate-some".

What about adding implicit "validate-some" support using, for example, vector syntax?

For example:

(validation-spec {:name not-empty? :num int?} <=> (validations ... :name ...not-empty? ... :num ... int?)
(validation-spec {[:name not-empty? :num int?]} <=> (validate-some ... :name ...not-empty? ... :num ... int?)

… a specification map can be vectors of validation functions. The validation-spec function compose the validation functions of the vector via validate-some.
@maxweber
Copy link
Author

maxweber commented Jul 5, 2011

Hi Sergey,

sorry for the late answer. I've just pushed a commit to my fork of clj-decline (https://github.com/maxweber/clj-decline) which implements your proposal. Specifications can now look like this one:
{:name [not-empty? uppercase?]}
You can find an example in the corresponding unit test. Would this fullfil your requirements? Something which is still missing is a possibility to make a parameter optional.

Best regards

Max

@maxweber
Copy link
Author

maxweber commented Jul 5, 2011

One other thing which is missing for the specification stuff is the possibility to state that a particular key is not allowed inside a map or the other way around to state that only map keys are allowed, which are specified inside the specification map. May be the latter should be the default for a specification?

@SergeyDidenko
Copy link

Hi Max,

thanks for your patches.

I think a good way to make validation-spec more universal is to add support for cross-key validation on any level. The "optional/ required" checks fit nicely in this scheme.

It should be some data tied to a spec part or the whole spec. IMHO the most readable way is to place optional lambda before the first :key, like this:

(defn all-spec-keys? [value-part spec-part]
  (let [missing-keys (clojure.set/difference (keys spec-part) (keys value-part))]
      (when missing-keys (str "The following mandatory keys are  missing: " missing-keys))))

{
  ; check that every key from the following spec is present
  all-spec-keys?

  :name not-empty?
  :num int?}

This optional cross validator can also be used for checking different dependencies between subkeys:

For example, check that the object and every nested object has :id :

(defn mandatory-id-recursive? [value-part _ ]
  ((fn tid [obj]
     (when (map? obj)
       (if (nil? (:id obj))
         (str ":id is mandatory: " obj)
         (apply str (map tid (vals obj))))))
    value-part))

The corresponding spec example:

{
  ; we don't want any cross-check here, so ommitting the lambda
  :name not-empty?
  :num int?
  :db-obj {
    ; check for nested ids in this obj (:db-obj), :post sub-object and :first-comment sub-object
    mandatory-id-recursive?
    ; also make other checks
    :post {:part1 not-empty? :part2 not-empty?}
    :first-comment {:bit1 not-empty? :bit2 not-empty?}}}

It's also good to have an option to stop if the first validation fails (using vector notation):

{
  ; if there is no :id in any (sub-)object, don't make other checks
  [
  mandatory-id-recursive?
  ; also make other checks
  :post not-empty?
  :first-comment not-empty? ]}

P.S. I have not yet clarified my requirements. But it already looks promising for storing both server (Clojure) and client (Javascript) validation specs in one place.

Regards, Sergey.

…a convention has been introduced that all keys of a spec are required by default and all additional keys inside a map which should be validated lead to a :not-specified error.
@maxweber
Copy link
Author

Hi Sergey,

the validation-spec function supports optional keys now. The keyword of an optional key starts with a question mark:

>(def check (validation-spec {:?name not-empty? :num int?}))
>(= (check {:num 1}) nil)
true

Furthermore a convention has been introduced that all keys of a spec are required by default and all additional keys inside a map which should be validated lead to a :not-specified error.
I think these conventions help to make the validation-spec function easy to use. Cross validations can be done with the normal building blocks of clj-decline anyhow. Furthermore due to the fact that the spec map is a normal Clojure map, we can use the power of Clojure to assemble a spec map (for example to add a mandatory-id check into all levels of the spec recursively, while the resulting spec map stays readable).
However I have to test if these changes are practical in a real project. For example the :required and :not-specified error may be have to be namespaced (::required and ::not-specfied) to avoid collisions with other error key words etc.

Best regards

Max

@SergeyDidenko
Copy link

Thanks, Max.

I'll try to use it the next time I'll be hacking on that project.

…ors into a vector instead of a list. The two error keywords :required and :not-specified have been renamed to :entry-required and :entry-not-specified.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants