Fork me on GitHub
#clojure-spec
<
2016-11-17
>
seancorfield00:11:28

We’re slowly creating functions that turn explain data into a series of unique failure codes which we have as a documented part of our API. Our use case is essentially

(let [conformed (s/conform spec args)]
  (if (s/invalid? conformed)
    (render-failure (problems->failure-codes spec args))
    (render-result conformed)))

ikitommi11:11:35

@seancorfield is it possible to share what kind of code lies in the problems->failure-codes?

ikitommi15:11:57

Shoud (s/form (s/spec integer?)) return (clojure.spec/spec clojure.core/integer?)? now it returns just clojure.core/integer?

ikitommi15:11:35

hmm.. (s/spec? (s/form (s/spec integer?))) ; => nil

seancorfield15:11:12

@ikitommi: it walks the problems and maps path to a code if it non-empty else it maps pred to a code (and it special-cases (contains? % key) which you get for a missing key from req/req-un.

dergutemoritz15:11:52

@ikitommi That's to be expected, s/form returns a data structure, not a spec. The clojure.core/integer? you're seeing is just the symbol, not the var

dergutemoritz16:11:40

@ikitommi Ah, alright, makes sense 🙂

dergutemoritz16:11:19

Looks like s/form is not injective!

dergutemoritz16:11:53

This is also interesting:

(s/form integer?)
; => :clojure.spec/unknown

yonatanel17:11:20

Why does this not work?

(def not-blank? (complement clojure.string/blank?))
(s/conform not-blank? "")
IllegalStateException Attempting to call unbound fn: #'dev/not-blank?  clojure.lang.Var$Unbound.throwArity (Var.java:43)

gfredericks17:11:32

I don't know.

yonatanel17:11:48

It doesn't work when I do it in the REPL, otherwise it's fine.

hiredman17:11:55

my guess is there is an error from the def you aren't seeing

hiredman17:11:13

likely your repl is running with a version of clojure that doesn't have blank?

hiredman17:11:44

so the def is interning the var, but not giving it a value

Alex Miller (Clojure team)18:11:17

@dergutemoritz: that's a known bug and will be fixed

Alex Miller (Clojure team)19:11:54

@dergutemoritz: I've done some of the work on it, just needs a bit more input from rich

joost-diepenmaat19:11:39

is there a way to do something similar to the removed clojure.spec.test/instrument-all before running (but after compiling) tests with lein test?

joost-diepenmaat19:11:00

oh I guess I can use fixtures for this

bfabry19:11:11

@joost-diepenmaat (instrument-all) was replaced with (instrument)

bfabry19:11:17

and yeah, I think (use-fixtures :once #(do (clojure.spec.test/instrument) (%)))

joost-diepenmaat19:11:51

yes that works fine

Alex Miller (Clojure team)21:11:40

you might want to unstrument at the end

joost-diepenmaat21:11:15

(defn with-instrument-all [t] (let [instrumented (stest/instrument)] (t) (stest/unstrument instrumented)))

joost-diepenmaat21:11:50

That's what I'm using as fixture right now

gfredericks21:11:17

try/`finally`

hiredman22:11:00

that was my first thought too, but in theory deftest will catch any exceptions from tests, so the only exceptions could be from fixtures, but your other fixtures should already never bubble out exceptions, because fixtures have some odd edge cases when exceptions are allowed to bubble out

hiredman22:11:25

if I recall fixtures that bubble out exceptions can hang lein test

spieden23:11:39

am i abusing spec if i use it for type conversions?

spieden23:11:05

e.g. an external system returns strings containing integers and i use a conformer to parse them

spieden23:11:28

it’s mostly convenient except that i have to use with-gen everywhere

seancorfield23:11:35

Channeling Alex: it’s OK to do a type conversion in a spec if you’re OK with all clients of that spec getting the built-in type conversion 🙂

seancorfield23:11:49

(but in general avoid conformers in specs)

seancorfield23:11:21

FWIW, we have a bunch of specs that accept strings and conform them to non-strings, for use in a REST API.

spieden23:11:38

ok that’s comforting

spieden23:11:30

i’ll probably want to circle back and override a bunch of the generators anyway

seancorfield23:11:09

We use this macro to wrap such specs:

(defmacro api-spec
  "Given a coercion function and a predicate / spec, produce a
  spec that accepts strings that can be coerced to a value that
  satisfies the predicate / spec, and will also generate strings
  that conform to the given spec."
  [coerce str-or-spec & [spec]]
  (let [[to-str spec] (if spec [str-or-spec spec] [str str-or-spec])]
    `(s/with-gen (s/and (s/conformer ~coerce) ~spec)
       (fn [] (g/fmap ~to-str (s/gen ~spec))))))
so you can say (s/def ::age (api-spec ->long (s/int-in 18 121))) where ->long is a conversion that produces ::s/invalid if the conversion fails.

seancorfield23:11:58

This also allows (s/def ::yes-no (api-spec keyword name #{:yes :no}))

spieden23:11:03

cool thanks