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

CLJ2116: Support for selective conforming with clojure.spec #1

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

Conversation

ikitommi
Copy link

@ikitommi ikitommi commented Jul 21, 2017

THIS IS A DUMMY PR for discussion purposes, will create a patch via Jira is gets further.

What

Support separation of conform from Specs, allowing different conforming to be run for same specs at runtime.

This is already implemented in spec-tools, but it needs to use Dynamic Binding and wrap specs into Spec Records to make this work.

Why

Allows Specs to be used as a runtime transformation engine, main use cases being the Web: sending and receiving Spec'd data over different formats (String, JSON, Transit) without needing to write manually differently conforming specs for all combinations.

How

  • Add a extra argument cc (conforming callback) to:
    • conform*, explain*, conform, explain, explain-data and explain-str: all support the old arities, causing cc to be set to nil
    • BREAKING: conform* and explain* always have the extra parameter, passed on to internal spec functions
    • if cc is set in conform, it is called with a spec argument. It should return either nil (default case, run conform as before) or a anonymous conform* function, which is used to pre-conform the value before passing it to normal conform
    • BREAKING: all calls to to subspecs conform* from conform* need to be called via top-level conform` - which has a perf optimized arity for this.

Todo

  • Support also runtime conforming explain
  • Fix docs
  • More tests

Notes

The actual supporting converters (string->long, string->keyword) and Conforming Callback could be hosted in non-core project like in spec-tools to enable moving fast - and supporting both clj & cljs.

Example

(deftest conforming-callback-test
  (let [string->int-conforming
        (fn [spec]
          (condp = spec
            int? (fn [_ x _]
                   (cond
                     (int? x) x
                     (string? x) (try
                                   (Long/parseLong x)
                                   (catch Exception _
                                     ::s/invalid))
                     :else ::s/invalid))
            :else nil))]

    (testing "no conforming callback"
      (is (= 1 (s/conform int? 1)))
      (is (= ::s/invalid (s/conform int? "1"))))

    (testing "with conforming callback"
      (is (= 1 (s/conform int? 1 string->int-conforming)))
      (is (= 1 (s/conform int? "1" string->int-conforming))))))

* pass 3rd argument (cc, conforming-callback) via
  - conform*, explain*
  - conform, explain-data, explain, explain-str
  - needed internal functions
conform has extra 4-arity version with boolean specize?
to allow bypassing the (potentially slow) specize.
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.

1 participant