/spex

Utils/Helpers for clojure spec

Primary LanguageClojure

spex

cljdoc badge

Small utility/extension library for clojure.spec.

Subject to changes/breakage. Use at own risk.

At the moment it does only 3 things:

assuming

(require '[qbits.spex :as spex])
  • add def-derived which creates a keyword hierarchy behind the scenes (the hierarchy is internal/scoped to spex/ so no risk of cluttering the global one)

    (s/def ::foo string?)
    (spex/def-derived ::bar ::foo)

    equivalent to:

    (s/def ::foo string?)
    (s/def ::bar ::foo)
    (spex/derive ::bar ::foo)

    but that also works with maps, here foo will derive from ::baz and ::bar

    (spex/def-merged ::foo [::bar ::baz])

    equivalent to:

    (s/def ::foo (s/merge ::bar ::baz))
    (spex/derive ::foo ::bar)
    (spex/derive ::foo ::baz)

    So why do that? well you can then inspect the hierarchy, which can be handy:

    (s/def-merged ::foo [::bar ::baz])
    
    (spex/ancestors ::foo) => #{::bar ::baz}
    (spex/isa? ::foo ::bar) => true
    (spex/isa? ::foo ::baz) => true
  • adds a metadata registry for registered specs, it currently supports variants of vary-meta!, with-meta!, meta, adds unregister-meta! and with-doc.

    you can then write code like:

    (-> (s/def ::foo string?)
        (spex/vary-meta! assoc :something :you-need))
    
     ;; and retrieve the values with spex/meta
    (spex/meta ::foo) => {:something :you-need}

    Since we have this hierarchy in place we can integrate this into metadata retrieval. Told you it could be useful :)

    (spex/def-derived ::bar ::foo)
    
    (spex/meta ::bar) => nil
    
    ;; remember we have meta data on :foo already, such that:
    (spex/meta ::foo) => {:something :you-need}
    
    ;; register meta at ::bar level
    (spex/vary-meta! ::bar assoc :another :key)
    
    ;; just the meta of ::bar
    (spex/meta ::bar) => {:another :key}
    
    ;; retrieve the meta for ::bar but also all its ancestors if you pass true to spex/meta
    (spex/meta ::bar true) => {:something :you-need, :another :key}

    and spex/with-doc is just sugar on top of all this to add docstrings to specs

    (spex/with-doc ::foo "bla bla bla")
    
    (s/doc ::foo) => "bla bla bla"

    All the functions that mutate the metadata of a spec return the spec key, that makes chaining easier, same goes for spex/def-derived:

    (-> (s/def ::foo string?)
        (spex/vary-meta! assoc :something :you-need)
        (cond->
          something?
          (spex/vary-meta! assoc :something-else :you-might-need)))

    The internal hierarchy is queriable just like the global keyword hierarchy, you can use spex/isa? spex/descendants spex/ancestors spex/parents spex/derive spex/underive, which are just partially applied functions over the same functions in core with our own internal hierarchy spex/spec-hierarchy.

       (s/def ::port (int-in-range? 1 65535))
       (spex/def-derived ::redis-port ::port)
    
       (spex/isa? ::redis-port ::port) => true
    
       (spex/def-derived ::cassandra-port ::port)
    
       ;; list all things ::port
       (spex/descendants ::port) => #{::redis-port ::cassandra-port}))
    

    This only works for aliases obviously.

  • adds a sugar to create namespaces within a ns. Ex: if you are in the user namespace (spex/rel-ns 'foo.bar) would create user.foo.bar

Installation

spex is available on Clojars.

Clojars Project

License

Copyright © 2016 Max Penet

Distributed under the Eclipse Public License, the same as Clojure.