How and where do you use Specs?

Trying to see know real-world use case to help myself get more familiar with ideas of Specs, and to use Specs in my existing projects.

I’ve used it to validate and document the structure and values of data that my application serializes. So anything that gets persisted, or sent to a different process.

And on occasion I spec a function that I want to perform generative testing on. Mostly pure functions.

I think its best to think of spec as a really good library to use when you want to:

  • Perform runtime validation of data.
  • Setup generative testing, or help generate unit test samples.
  • Parse data, mostly for helping in writing macro DSLs

And for these use case, its pretty good. Better then other libs I know. Though Plumatic Schema gives it a run for its money in terms of data validation.

But for me, it worked best to define a data interface between systems and persistence store. Especially on a team, if you agree on the data spec that will be exchanged, you can easily parallelise the work for building the producer of the data at the same time other devs build the consumer of the data. Everyone can generate mock data easily to test their pieces, and then putting it together if you passed the spec generally means it’ll just work.

2 Likes
(ns example.specs
  (:require #?(:clj  [clojure.spec :as s]
               :cljs [cljs.spec :as s])
            [clojure.string :as str]))
;; URI spec.
(def uri?
  (s/and string? #?(:clj #(try (do (java.net.URI. %) true) (catch Exception e false))
                    :cljs #(= (str/replace (js/encodeURI %) "%25" "%") %))))
;; e.g. Call as follows:
(s/valid? uri? "https://www.google.nl/url?q=https://www.reddit.com/r/Clojure/comments/4kutl7/clojurespec_guide/&sa=U&ved=0ahUKEwic2v3-r6DQAhVBGsAKHcrVCZMQFggUMAA&usg=AFQjCNHs0DmF1uNIw9BYUK7pqpgp5HEbow")
1 Like

I used it for serialising data, and to generate html to input/change the data.

Could you show some code examples?

Basically I just keep peeling with s/form till I hit an element, and use defmulti to get the correct component:

(defmethod get-primitive :set
  [id spec initial-value]
  (let [value-id (str "edit-style-" id)
        warning-id (str "edit-style-span" id)
        set-for-spec (sort (s/form spec))
        get-function (fn [] (keyword (.-value (util/ensure-element value-id))))]
    {:html         [:div.control [:span.select {:id warning-id} [:select {:id value-id}
                                                                 (for [item set-for-spec] [:option (if (= item initial-value) {:selected "selected"}) item])]]]
     :validation-f #(validate spec get-function warning-id)
     :get-value-f  #(get-if-valid spec get-function)}))```

shadow-cljs uses it to parse ClojureScript (ns foo.bar ...) forms.

1 Like