Skip to content

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))))))

ikitommi added 3 commits July 20, 2017 10:14
* 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