Your favourite clojure.spec companion library?


#1

I was just looking around to find a list of Clojure spec related libraries but couldn’t find one… so let’s make one.

One library per post, and don’t forget to include a small description of the library and why you like it.

:rocket:


#2

I’ll start. metosin/spec-tools is looking like a pretty sweet companion library to spec itself. It allows you to write simple specs in Schema style maps instead of spec’s very flexible but sometimes a little cumbersome format.

(def person
  {::id integer?
   ::age ::age
   :boss boolean?})

It also enables bijections and a whole lot of other stuff.


#3

When bootstrapping an external API I have come to love https://github.com/stathissideris/spec-provider which allows you to ship in a dataset and get out a reasonable approximation of the specs you would write by hand.


#4

https://github.com/Provisdom/defn-spec
Add function args and return Spec checking via assertions

It conforms to the spec of defn.


#5

#6

Expound. Excellent error messages for spec assertions.


#7

I’ve been meaning to check https://github.com/reifyhealth/specmonstah which apparently allows the generation of deeply-nested, hierarchical graphs of business data, but I haven’t had the chance yet.


#8

https://github.com/wilkerlucio/spec-coerce is a very good companion library, I’ve started using it for API parameter coercion and as an aid to form validation in the frontend. And it has a huge advantage: it does not define it’s own syntax, you just write your specs as normal.


#9

I dislike the fact that spec-tools defines it’s own macros and syntax on top of spec. This seems to be something of a pattern with spec companion libs (defn-spec and ghostwheel do it too), they all go “here is my custom macro for defining specs!”. The end result of course is that you have to choose one of the libraries to integrate into your project, if for some reason you need more than one, they won’t play with each other, they don’t compose!

It’s like a disease, and it’s pretty sad, because we could get so much value out of combining different libs. spec-coerce is a shining counter-example!


#10

I agree with your point of view.
Relative to defn-spec, according to the Pure Function Pipeline Dataflow, I prefer to add a verified-pipeline-function at the beginning and end of the data flow.


#11

About Spec-tools (author here): it’s a collection of things that are missing or out-of-scope of clojure.spec and you can use most of the things without any special syntax of macros:

  • spec parser
  • spec walker
  • spec visitor
  • spec transformer (json, string, your-own)
  • spec coercer
  • spec->json-schema converter
  • spec->swagger-schema converter

There is also a special macro, spec (temporary fix before CLJ-2194, CLJ-2116 &/ CLJ-2251 get resolved) & the data-spec syntax but they are optional. Happy to remove all the features that clojure.spec does by itself in the future.

here’s the feature tower, orange parts shoudn’t carry any pathogens :wink: , ping @stathissideris


#12

Can I ask a question. How do you map specs to swagger types?


#13

Sure, all spec forms are visited and transformed using an visior & an accept-spec multimethod. Swagger-schema is just a subset of json-schema, so just few overrides on top of the json-schema visitor. Usage like this:

(require '[spec-tools.swagger.core :as swagger])

;; no "anyOf" in swagger2
(swagger/transform (s/cat :int integer? :string string?))
; {:type "array"
;  :items {:type "integer"
;          :x-anyOf [{:type "integer"}
;                    {:type "string"}]}}

#14

So, you try to infer types from common predicates? What if you have a more complicated spec?


#15

Spec-tools tries to keep things open:

  • new clojure.spec.alpha/Specs (with forms) can be added for the visitor via the multimethod
  • new predicates (e.g. user-id?) can be added to the form inferrer (a multimethod)
  • custom mapping for any specs like anonymous functions via spec-tools.core/Spec (supports also custom coercion, error messages etc):
(swagger/transform
  (st/spec
    {:spec integer?
     :name "integer"
     :description "it's an int"
     :swagger/default 42}))
; {:type "integer"
;  :title "integer"
;  :description "it's an int"
;  :default 42}

sample app with reitit: https://github.com/metosin/reitit/blob/master/examples/http-swagger/src/example/server.clj#L26-L31


#16

I like to be naughty and check the return values using: https://github.com/jeaye/orchestra


#17

Many thanks for the clarification Tommi. The truth is that my aversion to macros is so great that I ended up ignoring parts of spec-tools. Maybe a sentence summarising all this great functionality at the beginning of the documentation would help. In any case, I really appreciate your work and I think it’s amazing that you’re providing all this functionality!


#18

Check out speculative

Try online: http://bit.ly/speculative-repl


#19

Phrase gives better error messages on assertions which can be propagated to end users. Handy for form validations.