Fork me on GitHub
#clojure-spec
<
2017-10-25
>
Alex Miller (Clojure team)12:10:52

I haven’t heard of that before

robert-stuttaford13:10:12

is the right way to spec a map that must have at least one of several keys (s/or (s/keys :req []) (s/keys :req []) (s/keys :req [])) ?

taylor13:10:37

you can use a single keys spec with or inside of the :req vector

robert-stuttaford13:10:45

(s/keys :opt []) says 0..n. i want 1..n

taylor13:10:57

oh sorry, misread

mpenet13:10:33

sounds ok, but if they are sharing the same context and have something like a :type key it might make sense to make a multi-spec out of it

robert-stuttaford13:10:14

it truly is a situation where you can provide 1, 2 or 3 of the keys, but providing 0 of them makes the whole map redundant

robert-stuttaford13:10:55

modelling a rule system where there are 3 possible outcomes - no-value-yet, yes, or no. you have to model at least one outcome, or there’s no point to testing for the rule

taylor13:10:32

those three states should be mutually exclusive?

robert-stuttaford13:10:02

the rule can be reused for multiple cases in a doc

robert-stuttaford13:10:10

some may 0, some may 1, some may 2

robert-stuttaford13:10:43

i’ll try s/or + s/keys !

robert-stuttaford13:10:31

(s/keys :req [(or :a :b :c)]), plus the fact that all ks are validated gives me what i need

ajs14:10:21

looking at this example from @cemerick do i read correctly that specs default to passing valid? even if the namespaced keyword was never def'd? https://twitter.com/cemerick/status/875748591310168065

taylor14:10:06

I’ve only seen this behavior w/`key` specs, but yeah it doesn’t require that the keyword have a registered spec

mgrbyte14:10:24

nope. (require '[clojure.spec.alpha :as s]) (s/valid? ::some-undefined-spec [}) will throw an exception (unable to resolve some-undefined-spec)

mgrbyte14:10:24

yep, (s/valid? (s/keys :req [::some-spec]) {}) will pass tho

mgrbyte14:10:42

even when ::some-spec has not been defined yet

ajs14:10:14

hmm, i wonder what the reasoning is for allowing that keys

ajs14:10:29

that's doesn't make any sense to me

taylor14:10:46

maybe so that you can easily spec required map keys without writing specs for the keys’ values, or having to define the key specs before the keys spec

taylor14:10:21

otherwise you’d have to do (s/def ::foo any?) for every key before using it in a keys spec?

ajs14:10:36

lot of room for user error though, as @cemerick has noted. as i am very prone to typos (perhaps we all are), i can see this biting me

taylor14:10:16

it’s true, and I’ve actually seen some code in the last week that checks map spec keys for “typos” but I can’t remember where 🙂

ajs14:10:35

i'm currently evaluating a variety of validation libraries like funcool/struct and truss and others. the one that most ably catches my typos is the one i will use.

taylor14:10:41

;; The example program below lets you identify "missing" keys specs at
;; the time and place of your choosing, and then handle them as you
;; deem appropriate, without imposing those decisions on other
;; users of spec.

ajs14:10:30

i find it a little bizarre that you have to jump through those hoops and write that code just to have spec do what you are expecting; perhaps that leaves room for a little library on top of spec to help mitigate disaster

ajs14:10:46

i don't understand why specifying the presence of a key uses the same syntax/naming as specifying the presence of an actual spec/predicate -- those are two different things, but they are expressed the same in spec, hence the room for easy errors

bbrinck15:10:31

It seem like most of my typos are around keywords. It seems like a tool that would macroexpand code and look for keywords that are unique would catch a large number of my typos.

andre.stylianos15:10:12

From https://clojure.org/about/spec#_sets_maps_are_about_membership_that_s_it

Sets (maps) are about membership, that’s it

As per above, maps defining the details of the values at their keys is a fundamental complecting of concerns that will not be supported. Map specs detail required/optional keys (i.e. set membership things) and keyword/attr/value semantics are independent. Map checking is two-phase, required key presence then key/value conformance. The latter can be done even when the (namespace-qualified) keys present at runtime are not in the map spec. This is vital for composition and dynamicity.

andre.stylianos15:10:44

From what I understand there are two different things. -- Membership: "This map has (required or optional) such and such keys" (s/def ::map (s/keys :req [::foo])) -- Value: "Anything with this key should conform to this value" (s/def ::foo string?)

andre.stylianos15:10:09

That's the way I understood this at least

ajs16:10:30

Perhaps one could consider spec a lower level tool on top of which you could build an API to better express concrete structural requirements that eliminate some of that ambiguity, and use that instead of spec directly.

ajs16:10:26

Otherwise there seems ample room for user error, which is typically what you're trying to avoid in the first place by using a validation library

metametadata15:10:39

@U5YHNV0EA I also questioned this behavior on the mailing list recently: https://groups.google.com/forum/#!topic/clojure/i8Rz-AnCoa8 And put my current solution into the gist here: https://gist.github.com/metametadata/5f600e20e0e9b0ce6bce146c6db429e2

mmer16:10:05

Apart from expound is there a way to associate an error message with spec definition so you can force an error message that makes sense and also allows for globalisation of error messages?

bbrinck16:10:11

Is this for devs or end users?

bbrinck16:10:44

@mmer If the messages are for end users, you probably don’t want Expound. https://github.com/alexanderkiel/phrase is likely a better fit

mmer16:10:35

Would it not be reasonable to include some method of defining the error message directly alongside the definition?

bbrinck17:10:53

How would you want to use those errors messages? In explain?

bbrinck17:10:29

or would you want to show the error messages to end users?

bbrinck17:10:51

My understanding is that specs are getting metadata, so if you just need a place to add some info and you want to later pull that out, you could potentially use metadata? https://dev.clojure.org/jira/browse/CLJ-2194

mmer18:10:51

I guess I need to get a usable error message for end users. My usecase revolves around validating yaml files edited by people. As these are nested structures the explanation from explain? get complex. I was hoping for a simple way to add a message to def that explain in human terms what is required. In my case the errors from spec are about a format the user does not see. Why use spec? I have found no other suitable tool that allows me to define in effect a schema for Yaml.

bbrinck18:10:09

OK, that makes sense.

bbrinck18:10:50

So, you could assign an error message to each spec. You could then call explain-data and, for each problem, convert the failing spec into the error message.

bbrinck18:10:38

That may be sufficient for your use case. However, remember that problems are flat - if you have a “or” spec, you’ll get two different problems for the same value

bbrinck18:10:39

Additionally, it’s not trivial in my experience to show where the non-conforming is located within the larger data structure

bbrinck18:10:32

Spec also will print errors in terms of the Clojure data structure, which may not be useful if your users are expecting to see errors in terms of Yaml

bbrinck19:10:09

It really depends on your spec, but probably the simplest approach is to assign errors to each spec (you could just build a static map for now, since each spec name is a unique keyword), then call explain-data, then take the first problem for each in, then print out the in path + your custom string error (you can find the relevant spec in via). That might be sufficient, although possibly misleading if you have a lot of “or” specs.

Brendan van der Es22:10:20

Anyone know a better way to conform an or spec without keywords? This is the best I got:

mmer22:10:58

@bbrinck Thank you sir for a long and consider answer - I like the map of spec errors approach as even with an or you can explain the options. . Much appreciated.

mmer22:10:35

A follow up from my previous question - is it posible to get the set of specs that are currently in registered?

bbrinck00:10:48

(require '[clojure.spec.alpha :as s]) (s/registry)

hiredman22:10:47

there is a registry function that returns the registry (a map of names to specs)

Brendan van der Es22:10:33

@hiredmanThanks. I am trying to s/or arbitrary specs. Essentially I'm trying to confom different argument lists, e.g.[& args], to the same format of input.

Brendan van der Es22:10:56

Specifically for datomic, [db entity-hash-map] conforms to EntityMap & EntityMap conforms to EntityMap. Maybe this approach is flawed anyways 😛, I'll keep playing around with it. Thanks

hiredman22:10:20

I would use spec to recognize (tag) different formats so you know which formats to deal with, not to try and smoosh all the formats in to one

Brendan van der Es23:10:35

Makes sense, you do than have to handle the different inputs in your function, which arguably is the better place for this logic anyways. I'm hoping this logic might be more reusable if I can embed the fact that the different arg-list formats essentially carry the same information in the spec.