Fork me on GitHub
Alex Miller (Clojure team)12:10:52

I haven’t heard of that before


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 [])) ?


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


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


oh sorry, misread


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


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


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


those three states should be mutually exclusive?


the rule can be reused for multiple cases in a doc


some may 0, some may 1, some may 2


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


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


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?


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


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


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


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


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


that's doesn't make any sense to me


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


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


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


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 🙂


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.


;; 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.


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


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


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.



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.


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?)


That's the way I understood this at least


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.


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


@U5YHNV0EA I also questioned this behavior on the mailing list recently:!topic/clojure/i8Rz-AnCoa8 And put my current solution into the gist here:


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?


Is this for devs or end users?


@mmer If the messages are for end users, you probably don’t want Expound. is likely a better fit


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


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


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


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?


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.


OK, that makes sense.


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.


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


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


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


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:


@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.


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


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


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


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.