Uploaded image for project: 'Clojure'
  1. CLJ-2116

[spec] Support for selective conforming with clojure.spec

    Details

    • Type: New Feature
    • Status: Open
    • Priority: Major
    • Resolution: Unresolved
    • Affects versions: Release 1.9
    • Fix versions: None
    • Labels:
    • Environment:

      [org.clojure/clojure "1.9.0-alpha14"]

      Description

      Problem

      using clojure.spec in runtime border validation supporting multiple exchange formats is hard.

      Details

      Currently in clojure.spec (alpha-14), conformers are attached to Spec instances at creation time and they are invoked on every conform. This is not very useful in system border validation, where conforming/coercion functions should be selected based on runtime data, e.g. the exchange format.

      Examples:

      • a keyword? spec:
        • with EDN, no coercion should be done (it can present Keywords)
        • with JSON, String->Keyword coercion should be applied
        • with String-based formats (CSV, query-params, ...), String->Keyword coercion should be applied
      • a integer? spec:
        • with EDN, no coercion should be done (it can present numbers)
        • with JSON, no coercion should be done (it can present numbers)
        • with String-based formats (CSV, query-params, ...), String->Long coercion should be applied

      Here is a more complete example:

      (s/def ::id integer?)
      (s/def ::name string?)
      (s/def ::title keyword?)
      (s/def ::person (s/keys :opt [::id], :req-un [::name ::title]))
      
      ;; this is how we see the data over different exchange formats
      (def edn-person {::id 1, :name "Tiina", :title :boss})
      (def json-person {::id 1, :name "Tiina", :title "boss"})
      (def string-person {::id "1", :name "Tiina", :title "boss"})
      
      ;; here's what we want
      (def conformed-person edn-person)
      

      To use this today, one needs to manually create new border specs with different conformers for all different exchange formats. Non-qualified keywords could be mapped in s/keys to work (e.g. ::title => ::title$JSON), but this wont work if fully qualified keys are exposed over the border (like ::id in the example) - one can't register multiple, differently conforming version of the spec with same name.

      Suggestion

      Support selective conforming in the Spec Protocol with a new 3-arity conform* and clojure.spec/conform, both taking a extra user-provided callback/visitor function. If the callback is provided, it's called from within the Specs conform* with the current spec as argument and it will return either nil or a 2-arity conformer function that should be used for the actual confrom.

      Actual conforming-matcher implementations can be maintained in 3rd party libraries, like spec-tools[1].

      Using it would look like this:

      ;; edn
      (assert (= conformed-person (s/conform ::person edn-person)))
      (assert (= conformed-person (s/conform ::person edn-person nil)))
      
      ;; json
      (assert (= conformed-person (s/conform ::person json-person json-conforming-matcher)))
      
      ;; string
      (assert (= conformed-person (s/conform ::person string-person string-conforming-matcher)))
      

      Alternative

      Another option to support this would be to allow Specs to be extended with Protocols. 3rd party libs could have a new Conforming protocol with 3-arity conform and add implementations for it on all current specs. Currently this is not possible.

      [1] https://github.com/metosin/spec-tools

        Attachments

          Activity

            People

            • Assignee:
              Unassigned
              Reporter:
              ikitommi Tommi Tommi
            • Votes:
              12 Vote for this issue
              Watchers:
              10 Start watching this issue

              Dates

              • Created:
                Updated: