Created
September 29, 2018 13:00
-
-
Save bhb/462c3ef97058d669a448aa85e7db5998 to your computer and use it in GitHub Desktop.
Interpreting `:in` paths in `clojure.spec`
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
> clj -Sdeps '{:deps {org.clojure/clojure {:mvn/version "1.10.0-alpha8"}}}' | |
Clojure 1.10.0-alpha8 | |
user=> (require '[clojure.spec.alpha :as s]) | |
nil | |
user=> ;; `explain-data` returns a description of the problem, including the `:in` path | |
user=> ;; which is the location of the invalid data within the context of the larger | |
user=> ;; data structure | |
user=> (-> (s/explain-data (s/coll-of int?) [1 "2"]) ::s/problems first) | |
{:path [], :pred int?, :val "2", :via [], :in [1]} | |
user=> (-> (s/explain-data (s/coll-of int?) [1 "2"]) ::s/problems first :in) | |
[1] | |
user=> ;; for instance, in the data above, the invalid data is located at path [1] | |
user=> ;; In simple cases, we can use our experience with `get-in` to understand | |
user=> ;; how the invalid data is found in the larger data structure | |
user=> (get-in [1 "2"] [1]) | |
"2" | |
user=> ;; However, despite the similarity to paths compatible with `get-in`, these `:in` | |
user=> ;; paths don't work with `get-in` generally, since they also describe, for example, lists | |
user=> (-> (s/explain-data (s/coll-of int?) '(1 "")) ::s/problems first) | |
{:path [], :pred int?, :val "", :via [], :in [1]} | |
user=> ;; and this won't work with `get-in` | |
user=> (get-in '(1 "") [1]) | |
nil | |
user=> ;; Another complication is that the meaning of the `:in` path depends on the | |
user=> ;; larger data structure. So, the same `:in` path can require totally different | |
user=> ;; access patterns depending on data structure. For example, here are four | |
user=> ;; calls to `explain-data` that all return the same `:in` path, but use different | |
user=> ;; collections: | |
user=> ;; 1. vectors in vectors | |
user=> (-> (s/explain-data (s/coll-of (s/coll-of int?)) [[] [:a]]) ::s/problems first :in) | |
[1 0] | |
user=> ;; 2. lists in lists | |
user=> (-> (s/explain-data (s/coll-of (s/coll-of int?)) '((1) (:a))) ::s/problems first :in) | |
[1 0] | |
user=> ;; (the meaning of the path is the same as example 1, we just can't use `get-in` directly) | |
user=> ;; 3. single-entry map | |
user=> (-> (s/explain-data (s/map-of keyword? int?) {1 1}) ::s/problems first :in) | |
[1 0] | |
user=> ;; (in spec, given a map `{:k :v}`, the `:in` path `[:k 0]` means "the path to the literal key `:k`" | |
user=> ;; whereas the `:in` path `[:k 1]` means "the path to the value assoced to `:k` i.e. :v".) | |
user=> ;; 4. multi-entry map | |
user=> (-> (s/explain-data (s/coll-of (s/tuple keyword? int)) {:a 1 "b" 2}) ::s/problems first :in) | |
[1 0] | |
user => ;; (the meaning here is to turn the map into a sequence of key-value pairs, and then use | |
user => ;; the path like normal i.e. you can roughly translate this into | |
user => ;; `(get-in (vec {:a 1 "b" 2}) [1 0])` ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment