Fork me on GitHub
#clojure-spec
<
2018-02-12
>
Alex Miller (Clojure team)01:02:38

Yes, that last url is the correct one. Earlier, spec was in clojure and the prior one was correct

Vincent Cantin01:02:06

beginner question: Can we use spec to validate the correlation between values at different places in a structure?

taylor01:02:30

yes, are you thinking maps or another type of structure?

Vincent Cantin01:02:41

For example, if I want to write a spec for [a b 1 5 ... 3 a b] where a and b could be anything.

Vincent Cantin01:02:10

... supposing that the structure is complex enough so that I would need to use s/conform to match the location of a and b at different places.

Vincent Cantin02:02:19

I could use a classical function to check if they match at different places, but I would like to know if there is a better spec-ish way to do it. Something like declaratively defining the pattern.

Vincent Cantin02:02:11

For example, something like: "For any a, b : [a b ... a b] is valid."

taylor02:02:09

something like this?

(s/def ::my-coll
  (s/* (s/alt :a-b (s/cat :a #{'a} :b #{'b})
              :num number?)))
(s/conform ::my-coll ['a 'b 1 2 3 'a 'b])

taylor02:02:08

Or if you wanted to require the coll always begin and end with a b:

(s/def ::a-b (s/cat :a #{'a} :b #{'b}))
(s/def ::my-coll (s/cat :a-b-start ::a-b
                        :nums (s/* number?)
                        :a-b-end ::a-b))

Vincent Cantin02:02:57

Thank you. I will try it again later tonight.

Jakub Holý (HolyJak)14:02:32

Is there a way to manually conform function arguments before I call (apply myfn args)? I.e. having

(spec/fdef myfn :args (spec/cat :userid ::userid))
how do I check that the args I have conform to that? Thanks! (I want to use this with expound. I have command functions invoked from a Slack bot and want to verify that the command the user typed has all the correct arguments and show a nice error if not)

mpenet14:02:54

make a separate spec of the :args and use s/valid? on it ?

mpenet14:02:27

you can then write (s/fdef myfn :args ::foo-args ... )

mpenet14:02:59

wondering: why wasn't check-asserts based on a dynamic var like *compile-asserts*? Right now it's a bit all or northing at runtime for assertion checking (for the one that are compiled at least)

mpenet14:02:30

it's easy enough to work around it anyway

Alex Miller (Clojure team)15:02:07

all or nothing is kind of the typical modes for assertions

Alex Miller (Clojure team)15:02:17

you can also grab the :args spec of a spec’ed function with

(-> `myfn s/get-spec :args)

jumar16:02:57

I'm wondering if I can do any better if I want to have two different specs for the same key... I have a generic auth-provider , e.g. #:auth-provider{:type "ldap" :config {:host "abc" ...}} This has a generic spec :

(s/def :auth-provider/config (s/map-of keyword? any?))
...
(s/def ::auth-provider-spec (s/keys :req [:auth-provider/type
:auth-provider/active?]
:opt [:auth-provider/id
:auth-provider/default-role
:auth-provider/config
:auth-provider/priority-order
:auth-provider/role-mapping]))
which is fine for some generic application code. However, in some scenarios I want to act differently based on the type of the auth-provider. E.g. for "ldap" I know that certain keys have to be present inside :auth-provider/config. I couldn't find a better solution than this:
(s/def ::ldap-provider-spec (s/and
::auth-specs/auth-provider-spec
;; this is less descriptive than using `s/keys` directly
;; but we cannot use `s/keys` because `:auth-provider/config` default
;; spec is already registered in auth-specs namespace
;; and you cannot have two different specs for the same namespaced key
#(s/valid? ::ldap-config (:auth-provider/config %))))

My problem is that since specs are global I cannot register two different specs for the same key (`auth-provider/config`). I also tried multi-spec but couldn't solved it either. My solution works but it's quite opaque - the error message just says that data fails ::ldap-config spec (not that e.g. :host key is missing in config map)

misha18:02:58

look again at multi-spec, @jumar. it would be verbose solution, but sounds like it fits.

jumar18:02:20

@U051HUZLD well, my problem was that I still couldn't define two different specs for :auth-provider/config key. Basically, based on the value of :type key I'd implement two multimethods - one for the :type value "ldap" and one for :default. But in both of them I'd need to use something like (s/keys :req [:auth-provider/config]) - and here I'm stuck. I don't know how to define two different specs for :auth-provider/config key.

misha18:02:37

although, it'd be much easier, if you'd put :auth-provider/type inside config itself

misha18:02:46

if you put type inside config itself - multispecs will be about config keys, net provider's ones

misha18:02:22

or you can just s/or the :provider/config, but it will not keep :provider/type and or's branches "in sync"

roklenarcic21:02:45

how do you guys spec a map where keys are generic and values are constant for a type? To give you an example, clojure.xml will output something like {:tag :first_name, :attrs nil, :content ["John"]}. How would I spec that I want my ::first-name to have this structure? The keys function would require me to make a spec for ::tag but the value is constant for ::first-name and at the same time, different in another spec, I can't globally specify ::tag spec.

seancorfield21:02:30

@roklenarcic I suspect you'll want a multi-spec for the various types of parsed maps you can get back from clojure.xml

seancorfield21:02:47

(and the multi-spec would branch on the :tag key's value)

roklenarcic22:02:21

I'll look into it.

roklenarcic22:02:42

There's something weird with the large-integer generator. As is, it never generates a number over a 100. Even if I use the large-integer* generator with options, the numbers are very low.

(gen/sample (gen/large-integer* {:min 1 :max 1000000}))
=> (2 2 1 4 1 1 9 1 2 15)

roklenarcic22:02:47

The test.check generator clojure doc states:

(def ^{:added "0.9.0"} large-integer
  "Generates a platform-native integer from the full available range
  (in clj, 64-bit Longs, and in cljs, numbers between -(2^53 - 1) and
  (2^53 - 1)).
  Use large-integer* for more control."
  (large-integer* {}))
But in my experience it generates number in about a -100 +100 range instead of 2^64

taylor22:02:06

try generating a larger sample

taylor22:02:11

(gen/sample (gen/large-integer* {:min 1 :max 1000000}) 1000)

roklenarcic22:02:59

hm... I guess I expected that generated numbers were uniformly distributed and so it would be extremely likely that a number over a 1000 was generated in 10 tries

gfredericks23:02:55

@roklenarcic the large-integer generator exhibits growth just like most of the other generators

gfredericks23:02:01

meaning it respects the size parameter

gfredericks23:02:26

gen/sample with 1 arg gives you samples using sizes 0⇒9

roklenarcic23:02:40

I see, thank you

roklenarcic23:02:06

The reason I was worried is that when I use integer generator to generate ids, I get a lot of non-unique small numbers (because of the small initial range), which makes it very likely for a sequence of generated entities to have non-unique IDs, which causes such-that to fail occasionally, when IDs are specced to be unique.