Fork me on GitHub
#clojure-spec
<
2018-08-23
>
lmergen09:08:32

i'm struggling a bit on how to design my spec rules. specifically, i have a complex object that i want to spec, and this object can have several traits, depending upon where it is. so, for example, let's say i have an animal object with the following definition:

(s/def ::vaccinations (s/coll-of {:foo :bar})
 (s/def ::medical (s/keys :opt-un [::vaccinations])
(s/def ::animal (s/keys :req-un [::medical]))
so here we can see that an animal has a optional medical information about vaccinations. most of my functions just work with animals, and do not care whether some specific nested property it true. however, some other functions do care whether an animal is vaccinated. for now, i have this:
(s/def ::vaccinated-animal (s/and ::animal #(some? (get-in % [:medical :vaccinations])))
but this feels a bit hacky, and doesn't scale very well when you want to compose multiple properties into it. it also feels like i'm developing a poor man's trait system here, which is maybe the intention, maybe not regardless, what would be the proper way to organize this stuff be?

guy09:08:55

are you trying to do s/merge maybe?

lmergen09:08:11

i'm not sure how to structure that in this specific case

lmergen09:08:15

would i s/merge the ::animal into an ::vaccinated-animal ? but how would i re-define a property of a nested map in this case (i.e., :req-un the ::vaccinations rather than :opt-un) ?

guy09:08:19

Can you show me an example shape of the data?

lmergen09:08:28

hmm ? the spec is up there ?

guy09:08:01

(def animal {:medical [{:foo :bar}]})

guy09:08:03

like that yeah?

lmergen09:08:28

(def animal {:medical {:vaccinations [:foo :bar]}})

lmergen09:08:45

(def animal {:medical {}})

guy09:08:05

ah yeah sorry

lmergen09:08:27

so my problem is that i will be needing the s/merge nested traits

guy09:08:26

So basically (s/def ::medical (s/keys :opt-un [::vaccinations]) or (s/def ::medical (s/keys :req-un [::vaccinations])

lmergen09:08:00

but i cannot just rename ::medical to ::medical-with-vaccinations

lmergen09:08:04

because then the key would change

guy09:08:30

you can do :some-ns/medical

lmergen09:08:41

yeah i know, would that really be the solution here?

lmergen09:08:57

it seems inappropriate

guy09:08:24

let me get the bit

guy09:08:42

(s/def :company.department.employee/name string?)
(s/def :company.department.employee/payroll-number nat-int?)
(s/def :company.department/employee 
  (s/keys :req [:company.department.employee/name
                :company.department.employee/payroll-number]))
(s/def :company.department/employees (s/coll-of :company.department/employee))
(s/def :company.department/id uuid?)
(s/def :company/department (s/keys :req [:company.department/id
                                         :company.department/employees]))

guy09:08:49

this is sorta what i would do in ur case

guy09:08:58

Naming conventions for nested maps

guy09:08:06

If you read that part

guy09:08:14

The reason i think the above is useful to you is it allows you to use ns’s to sorta describe what you are talking about

guy09:08:27

Which is ultimately vaccinated animals vs just animals

lmergen09:08:27

right! this is useful

guy09:08:43

Does that help at all?

guy09:08:48

ok great

lmergen09:08:02

so basically, additional namespaces

guy09:08:13

its defo one answer

guy09:08:27

I like it as it makes your specs predictable

conan09:08:10

yes, this! no surprises!

👍 4
dominicm11:08:52

I don't suppose anyone has created a spec for "edn" have they?

😱 4
guy11:08:12

I wanna see it 😱

dominicm13:08:21

I want to test aero better, there's gaps where certain data structures don't work.

dominicm13:08:45

One really hard part is getting it to generate #ref

bbrinck14:08:48

It depends on what you want to test, but on strategy for building generators like this is to some from a small set of valid keys OR random keys

bbrinck14:08:06

Or am I misunderstanding what is difficult about generating refs?

dominicm14:08:31

I want to generate valid nested paths, e.g. #ref [:adjfkf :bjkadf :foobar]

dominicm14:08:54

which can have many levels of nesting

bbrinck14:08:18

Is the goal of the generator to produce refs that will always resolve, or just be valid syntax (but maybe not resolve), or or a mix of resolved and unresolved refs?

bbrinck14:08:57

i.e. do you want all paths to be valid (assuming “valid” means “it resolves to some actual path in the map”) or is it OK if some are invalid?

dominicm15:08:17

A mix would be useful, but you can't spec that an exception happens can you?

dominicm15:08:36

I think nil is the current behaviour, but I don't like that for other reasons.

bbrinck15:08:06

That’s true, you can’t spec an exception occurs, although you could generate EDN with a mix and then use a normal generative test (i.e. not using check directly) to say either an exception should occur or some other thing should happen

bbrinck15:08:50

Anyway, if you want a mix, one thing you could try is to normally name keys from a small set in the generator e.g. {:a :b :c} and then generate refs of a usually small length from those same keys e.g. (s/coll-of #{:a :b :c} :kind vector?). Basically you’re just assuming you’ll get some number of “collisions” but accepting some refs won’t resolve

bbrinck15:08:40

If you want to ensure every path is going to resolve, then I think you’ll have to build a custom generator for the entire config. I’d start by generating a set of paths that will be included in the map. Then, you can take those valid paths and write a function that fills in the values for each path, which may contain a ref to another valid path

dominicm16:08:25

Starting with valid paths sounds like a great idea!

dominicm16:08:47

Especially as some things will be sets or vectors.

bbrinck17:08:59

Super hacky (and there are bugs), but here’s a quick outline of how a generator might work. Hope it helps! 😄 https://gist.github.com/bhb/b4ca38670c9c20f3d2ed8623aca5ec11

bbrinck17:08:45

If the generator works a high percentage of the time, you could also just generate a lot of examples and filter out ones where the refs are invalid

dominicm17:08:01

That's amazing, thank you!