Fork me on GitHub
#clojure-spec
<
2018-02-17
>
bmaddy05:02:50

Is there a way to ensure two values are the same in a structure with spec? I didn't see anything in the spec docs (but maybe I missed it). I'd like to define a spec :account/by-id that specs data like this:

{-5 {:db/id -5 :account/title "customer five"}}
Here's what I've got so far:
(s/def :db/id (s/or :db/id int? :uuid uuid?))
(s/def :account/title string?)
(s/def ::account (s/keys :req [:db/id :account/title]))
(s/def :account/by-id (s/map-of :db/id ::account))
but gen/generate gives me stuff like this:
{7 {:db/id -5 :account/title "customer five"}}

bmaddy05:02:32

I'd like to ensure the :db/id is the same as the associated key in the outer map.

seancorfield05:02:29

@bmaddy look at s/and to add a constraint

seancorfield05:02:13

(s/def :account/by-id (s/and (s/map-of :db/id ::account) db-id-matches))
where db-id-matches checks that the values match

bmaddy02:02:23

Thanks @seancorfield, that's what I was missing!

gfredericks12:02:08

then you'll have to modify the generator too, to just copy the id from the key spot to the attribute spot

roklenarcic16:02:15

I'm trying to add clojure.spec.test.alpha/check based testing to my clojure.test tests, but the integration doesn't seem trivial. Do I have to write my own transform of the check map to some sensible assert or is there a lib for that out already?

gfredericks16:02:11

that's asked a lot; I don't know if anybody's published anything. but in any case doing the integration manually should be just barely nontrivial

roklenarcic16:02:57

I mean I can whip up something. I have to note that one thing that is very confusing is the :failure key in the return map

roklenarcic16:02:07

it comes back set to false

roklenarcic16:02:18

on a failed check

roklenarcic16:02:32

this will probably confuse a lot of people

gfredericks16:02:27

yes there's a ticket about that

roklenarcic16:02:07

There seems to be a curious mix of namespaces in the returned maps as well: To get predicate that failed you need to get:

(-> (stest/check `fnsym)
  :clojure.spec.test.check/ret
  :result-data
  :clojure.test.check.properties/error
  (.getData)
  :clojure.spec.alpha/problems
  (get 0)
  :pred)
Goes from spec to test.check back to spec keys.

gfredericks16:02:22

yeah, I think spec is currently embedding data in an ExceptionInfo object because test.check didn't used to have a mechanism for adding any info to a failure

gfredericks16:02:37

I thought it was being unwrapped somehow though

mathpunk21:02:18

I'm trying to do spec-and-test driven development in ClojureScript, and specs are not resolving like I expect them to. This test (https://github.com/mathpunk/sherman/blob/master/test/sherman/grammar_test.cljs#L13) passes if I uncomment the specification above it, but fails as is, with the specification in another namespace (https://github.com/mathpunk/sherman/blob/master/src/sherman/grammar.cljs#L7)

seancorfield23:02:13

@mathpunk Your test namespace does not require the namespace containing the spec -- how would it know about it?

mathpunk23:02:02

@seancorfield thanks! Since the name of the spec has the namespace in it, I didn't realize the test namespace still needed to require it

seancorfield23:02:55

You need to load the namespace for the s/def to be executed, otherwise the spec isn't defined -- the keyword is just a keyword.

seancorfield23:02:41

Also, spec names (keywords) have no connection to code namespaces (except insofar as the :: alias resolution works).

mathpunk23:02:27

wow! ok, I thought they were somehow being 'registered' in a way that needed to match the namespaces

seancorfield23:02:20

s/def and s/fdef are the functions that perform the registration and the spec name just needs to be as globally unique within your application as it needs to be in order to not clash with any other specs.

seancorfield23:02:00

Namespace-qualified keywords have been around for a long time before spec and have never been tied to code namespaces. For example, we use :ws.web/config as a key in Ring requests in our applications for our application configuration map -- but we don't have a ws.web namespace.

seancorfield23:02:52

You can also set up aliases without needing code namespaces to match:

(alias 'm  (create-ns 'ws.domain.member))
(alias 'mu (create-ns 'ws.domain.member.update))
(alias 'mv (create-ns 'ws.domain.member.validation))
The first one is used for specs (`::m/username` for example), the other two are just used as part of unique keys in maps.

seancorfield23:02:07

(it just so happens we do have code namespaces matching those, but where we use the aliases, we don't need the functions or specs from those namespaces so we don't require the namespaces)