Fork me on GitHub
#clojure-spec
<
2017-07-31
>
Oliver George05:07:51

I'd like to check some javascript data based on a regular spec which uses s/keys and s/coll-of.

Oliver George05:07:21

I know it's not in scope for clojure-spec but perhaps someone has written something which does this?

Oliver George05:07:59

(s/assert-obj ::typical-spec #js {:a [1 2 3]})

ikitommi06:07:14

@camdez if you are dealing with deeply nested legacy models and only want to validate those in the api layer - after which you only transform them to conform your own application specs - you could check out data-specs (in spec-tools lib). Builds on top of clojure.spec. It’s *not* a good/best practise, but food for thought anyway. Something like:

(require '[clojure.spec.alpha :as s])
(require '[spec-tools.data-spec :as ds])

(s/def ::business
  (ds/spec
    ::business
    {:id integer?
     :name string?
     :phone_number {:id integer?
                    :phone_number string?}}))

(s/valid?
  ::business
  {:id 214566,
   :name "Fake Co",
   :phone_number {:id 141683, :phone_number "555 123 4567"}})
; true

ikitommi06:07:34

a sample bare-bones http api with it: https://github.com/ikitommi/business, added a :tags (a set of keywords) to demonstrate the automatic coercion (string->keyword & vector->set here)

camdez12:07:24

Thanks! I’ve used Plumatic (née Prismatic) Schema quite a bit, so this schema-resembles-data approach is quite familiar. But I’ve been using keyword prefixes divorced from the extant namespaces and it’s working pretty well for me, if a tad verbose.

bfabry16:07:57

@olivergeorge simplest thing that comes to mind is to just js->clj and run spec over the result

misha17:07:47

@bfabry @olivergeorge js->clj and clj->js are not symmetric, beware

plins19:07:30

hello everyone, im trying to spec a map where all keys are optional but you should have at least one of them, is this achievable?

bfabry19:07:36

@plins sure, (s/and (s/keys :opt [...]) not-empty)

plins19:07:36

i wasnt aware i could use not empty as a spec, thx!!!!

bfabry19:07:12

you can use any predicate function as a spec 🙂 though not-empty isn't technically a predicate function but it's good enough

plins19:07:17

so spec treats nil as false and truthy values as true ?

bfabry19:07:02

well, clojure treats nil and false as false and everything else as true. so yeah spec does too

bfabry19:07:16

cljs.user=> (s/valid? (s/and (s/keys :opt [::foo]) not-empty) {::foo nil})
true
cljs.user=> (s/valid? (s/and (s/keys :opt [::foo]) not-empty) {})
false
cljs.user=> (s/explain (s/and (s/keys :opt [::foo]) not-empty) {})
val: {} fails predicate: not-empty
:cljs.spec.alpha/spec  #object[cljs.spec.alpha.t_cljs$spec$alpha16935]
:cljs.spec.alpha/value  {}
nil

mfikes19:07:47

It doesn’t appear to be possible to use an s/cat spec for an argument. Does this instrument failure make sense?

user=> (require '[clojure.spec.alpha :as s] '[clojure.spec.test.alpha :as st])
nil
user=> (s/def ::ingredient (s/cat :quantity number? :unit keyword?))
:user/ingredient
user=> (s/valid? ::ingredient [0 :teaspoon])
true
user=> (defn none? [ingredient] (zero? (first ingredient)))
#'user/none?
user=> (s/fdef none? :args (s/cat :ingredient ::ingredient))
user/none?
user=> (st/instrument)
[user/none?]
user=> (none? [0 :teaspoon])

ExceptionInfo Call to #'user/none? did not conform to spec:
In: [0] val: [0 :teaspoon] fails spec: :user/ingredient at: [:args :ingredient :quantity] predicate: number?
:clojure.spec.alpha/spec  #object[clojure.spec.alpha$regex_spec_impl$reify__1200 0x379e0a93 "clojure.spec.alpha$regex_spec_impl$reify__1200@379e0a93"]
:clojure.spec.alpha/value  ([0 :teaspoon])
:clojure.spec.alpha/args  ([0 :teaspoon])
:clojure.spec.alpha/failure  :instrument
:clojure.spec.test.alpha/caller  {:file "form-init192069056035209642.clj", :line 1, :var-scope user/eval1427}
  clojure.core/ex-info (core.clj:4725)

athos01:08:46

You should wrap ::ingredient with s/spec, like (s/fdef none? :args (s/cat :ingredient (s/spec ::ingredient)))

plins19:07:30

@bfabry, i think i found a problem

(s/def ::my-spec (s/and not-empty (s/keys :opt-un [::a ::b])))
(s/valid? ::my-spec {} => false
(s/valid? ::my-spec {:A 1 :c 1}) => true

bfabry19:07:29

@plins well, that's not so much a problem you just didn't fully explain your requirements to me 😛

plins19:07:55

im sorry for that

plins20:07:01

let my try express myself better

bfabry20:07:07

no it's ok I get it now

bfabry20:07:06

you probably want (s/def ::my-spec (s/and (some-fn :a :b) (s/keys :opt-un [::a ::b])))

bfabry20:07:19

there's various ways to get around that duplication, fwiw

plins20:07:32

i’ll dig deeper into the docs, thx 🙂

plins20:07:16

ive settled with

(s/def ::my-spec (s/and (s/map-of #{:a :b} any?)
                        (s/keys :opt-un [::a ::b])))
thx!