Here's two example:
1) You accidentally put extra sensitive data on maps that end up persisted unencrypted. If spec doesn't fail a map validation if it contains extra keys, this is easy to mistakenly have happen.
2) You mistype the spec of a key, and inadvertly now don't validate a user input field, allowing it to pass validation undetected. You're now opened to code injection attacks.
Or at least include in core a strict spec, like s/keys and s/keys-strict. I know personally, I'll need the strict variant in 100% of my use cases, as I work on backend services.
Hi!I have some noobie questions for which I couldn't google the compelling answers.1) Is there any way to ensure that the keys I used in `s/keys` have the associated specs defined?
I second this from my experience, using spec quite extensively since its release.We already had some invalid data passing silently because of this. It can easily happen if you have a typo in the spec.Also we never experienced benefits from being able to not spec keys required in s/keys. It appears to be a pretty obsolete feature, making vulnerabilities more likely.
I'm sure this is sometimes true, but I can't think of how that would happen. Could you detail it a little?
For me, whenever I needed to add more keys, it was simple to evolve the spec with the function, why would you leave the spec behind?
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+unsubscribe@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
On puzzler's database example, I would have thought that restricting the keys that go into the DB should not be the job of spec (since functions may not be instrumented anyway), but the job of the 'core logic'. Maybe I am misunderstanding though.
Regarding open maps and keysets I'm fine with them. However, I use select-keys after validating data over the wire. Would be nice if there were some "select-spec" for open specs.
Specs can be defined or added later, so there is no valid way to do this.
1) About `s/keys` silently ignoring missing value specs. My question was: "Is there any way to ensure that the keys I used in `s/keys` have the associated specs defined?."Specs can be defined or added later, so there is no valid way to do this.OK, so requiring that values are spec-ed can't be enforced at compilation time because this would make it impossible to define value specs after `s/keys`:```(s/def ::foo (s/keys :req [::bar])),,,(s/def ::bar ,,,)```This can explain why it's not built into the library. In such case I'm fine with using a custom macro instead of `s/keys` (see my gist in the previous post).But what about enforcing existence of value specs at runtime, during validation? `s/cat` does that, e.g.:```cljs.user=> (s/def ::foo (s/cat :bar ::baz)):cljs.user/foocljs.user=> (s/valid? ::foo [123])Unable to resolve spec: :cljs.user/baz```
Why is it then not a default behaviour for `s/keys` as well? I.e.:
```; current behaviorcljs.user=> (s/def ::foo (s/keys :req [::x])):cljs.user/foocljs.user=> (s/valid? ::foo {::x 123})true; desired behaviorcljs.user=> (s/def ::foo (s/keys :req [::x])):cljs.user/foocljs.user=> (s/valid? ::foo {::x 123})Unable to resolve spec: :cljs.user/x```I don't think Spec-ulation Keynote addresses this behaviour.2) There's no *built-in* way restrict the keyset of the map in `core.spec`.The reasoning for this seems to be based around the idea of backwards compatible evolving specs (see Spec-alution Keynote). But there are several good examples already covered in this thread which demonstrate that it would be very convenient to also have strict keyset validations available in `core.spec`. After all, the library is about specifying the structure of data and not only about specifying API contracts.
3) Thinking more about `s/keys` vs. `s/cat` *specifically* in the context of asserting API contracts (I'm stressing on the context here because `core.spec` is a general library and should take in account other use cases too).In this context it's even more apparent that `s/keys` should behave in the same way as `s/cat` because there's not much difference between positional arguments and keyword arguments.
I'll try to illustrate what I mean with an example. Let's say there's a function with positional arguments:```(defn foo-pos [x y z]); call example:(foo xxx yyy zzz)```I hope we can agree that it's more or less equivalent to this function with the keyword arguments where each keyword corresponds to the position number in `foo-pos`:
(require '[clojure.spec.alpha :as s]) (require '[spec-tools.core :as st]) (s/def ::name string?) (s/def ::street string?) (s/def ::address (st/spec (s/keys :req-un [::street]))) (s/def ::user (st/spec (s/keys :req-un [::name ::address]))) (def inkeri {:name "Inkeri" :age 102 :address {:street "Satamakatu" :city "Tampere"}}) (st/select-spec ::user inkeri) ; {:name "Inkeri" ; :address {:street "Satamakatu"}}
I'm not able to follow how that is?
You would have only needed this if you are somewhere validating or have setup test.check to run. In both cases, if you wanted to validate, why don't you also want to validate this new field? Or why not test.check your code with it?
Also, if you had a keys spec for this map, it would only require you adding one key to that one spec. Why would all your intermediate functions not share the same map spec?
Anyhow, I'm not arguing against open key specs, I'm arguing for adding to spec a closed keys spec. Either as an option to s/keys or another spec macro. There clearly a large number of people who seem to have use cases for both, and implementing your own is non trivial, I think it would be a great addition to spec for practical completeness.
I understand the core team wanting to take a minimal approach to spec, and that open is easier to restrict later. But I worry that already in alpha state, spec is unpractical for many people as is, and Orchestra and Spec-tools are already needed supplement.
For instrumentation, it's no big deal, but for specs I think it is. Having a canonical set of specs accross Clojure shops is a way to form a common language. If I start having my custom map specs, and so does everyone, I'd be tempted to say something core is missing. Strict map specs which also vallidates each key has a registerer spec I think is a glaring omission.
Having used spec in one of my design, I've had to justify already to 6 people, some being senior security engineers, why the validation allows for open keys. Other team members were just confused as to why that was, and the only argument I had was that Rich doesn't like to break APIs :p. But I've had to add validation on top to pass security audit. So I think while not breaking APIs when incrementally adding specs to legacy code is a good use case, there's the security and safety use case which seems to be shared by a large swat of Clijurist, and I think spec is in need of a core support for it.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
3) I agree that my argumentation was vague but the sentiment still holds true: I'd like to easily validate keyword args with the same level of confidence as I can validate positional args.
1,2)> Because all map keys are independent and don't rely on each other for ordering.> You can add additional checks to verify stricter things on top of open specs. You can't add openness to something that is already closed.If I understand you correctly, library currently is only concerned with doing simplest things and that's why `s/keys` is not very strict? But then I'm not sure if it's the simplest behaviour though since `s/keys` specs validate 2 things: keys AND (sometimes) values. And that "sometimes" part makes production code more error-prone.
Is there any chance that in the future core.spec will have additional API to allow specifying other levels of map validation strictness? Possible options:
* Keyset validation strictness:- open (current behavior, allows putting any additional keys into map)- closed (map must contain only the listed keys)
* Values validation strictness:- never validate (i.e. any values will pass, not sure about the use cases though)
| Spec-tools (https://github.com/metosin/spec-tools) has some tools for this: the spec visitor (walking over all core specs, e.g. to collect all registered specs) and map-conformers: fail-on-extra-keys and strip-extra-keys.I understand the core team wanting to take a minimal approach to spec, and that open is easier to restrict later.
But I worry that already in alpha state, spec is unpractical for many people as is
, and Orchestra and Spec-tools are already needed supplement.
For instrumentation, it's no big deal, but for specs I think it is. Having a canonical set of specs accross Clojure shops is a way to form a common language. If I start having my custom map specs, and so does everyone, I'd be tempted to say something core is missing. Strict map specs which also vallidates each key has a registerer spec I think is a glaring omission.
Having used spec in one of my design, I've had to justify already to 6 people, some being senior security engineers, why the validation allows for open keys.
Other team members were just confused as to why that was, and the only argument I had was that Rich doesn't like to break APIs :p.
But I've had to add validation on top to pass security audit.
So I think while not breaking APIs when incrementally adding specs to legacy code is a good use case, there's the security and safety use case which seems to be shared by a large swat of Clijurist, and I think spec is in need of a core support for it.
(ns tst.demo.core(:use demo.core tupelo.test )(:require[tupelo.core :as t] ))(t/refer-tupelo)(dotest(let [map-ab {:a 1 :b 2}map-abc {:a 1 :b 2 :c 3}](is= map-ab (validate-map-keys map-ab [:a :b]))(is= map-ab (validate-map-keys map-ab [:a :b :x]))(is= map-ab (validate-map-keys map-ab #{:a :b}))(is= map-ab (validate-map-keys map-ab #{:a :b :x}))(is= map-abc (validate-map-keys map-abc [:a :b :c :x]))(is (thrown? IllegalArgumentException (validate-map-keys map-ab [:a])))(is (thrown? IllegalArgumentException (validate-map-keys map-ab [:b])))(is (thrown? IllegalArgumentException (validate-map-keys map-ab [:a :x])))(is (thrown? IllegalArgumentException (validate-map-keys map-abc [:a :b])))(is (thrown? IllegalArgumentException (validate-map-keys map-abc [:a :c :x])))))
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.
As one of the (apparently pretty uncommon) users who actually does happily define s/keys specs without correspondingly speccing the leaves as an "incrementally lock down/validate" approach, I wouldn't be too upset if I lost that ability and it started throwing an error. I mean it throws an error if I go to generate it anyway.
*puts hand up!*
I don’t want to have to write (s/def ::some-key any?) all over the place as I’m developing specs, just to satisfy an overly eager checker (in my mind). Worse, since the check would need to be deferred until validation time, as Beau notes, the omission of an “any?” key spec might not even show up until much further down the line.
To me, this default behavior of silently not checking the _value_ associated with a _key_ is in keeping with the design principles of spec which focus on maps being based on a key set, while offering functions to allow you to optionally check values.
Sean Corfield -- (970) FOR-SEAN -- (904) 302-SEAN
An Architect's View -- http://corfield.org/
"If you're not annoying somebody, you're not really alive."
-- Margaret Atwood
> The argument that existence of specs provided to s/keys can only be checked at runtime is false.> The argument that that recursive specs are impossible if existence of specs provided to s/keys was checked at compile time is also false.Could you explain to us why this is false? Clojure is a dynamic language, as such I don't see how you could define a time when all specs need to be present. How would I enter this spec at the repl if spec definition was required at s/keys invocation time?
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.
(s/def ::spec (s/keys :req [:other/spec]))
(s/def :other/spec int?)
(s/valid? ::spec {:other/spec "123"})
(defmacro known-keys [& {:keys [req req-un opt opt-un gen] :as args}] (letfn [(known-spec? [k] (boolean (s/get-spec k)))] (doseq [e (concat req req-un opt opt-un)] (when (not (known-spec? e)) (throw (ex-info (str e " is not a currently registered spec.") args))))) `(s/keys ~@(interleave (keys args) (vals args))))
One way to prevent that would be to only ever use aliases for fully qualified keywords, never type out the whole thing.
Eric does raise an interesting question tho’:
If you have an API that cares about ‘a’, ‘b’, and ‘c’ and you later specify that ‘d’ is optional and should be an ‘int?’, does that qualify as breakage or growth? If clients were sending ‘d’ as a string before but you ignored it, it will break those clients. Clients that were not sending ‘d’ will not be affected by the change. The old spec – allowing ‘d’ to be ‘any?’ essentially – won’t fail on any data that omits ‘d’ or passes it as ‘int?’ so it passes your compatibility test.
(we actually ran into this at work because a client app was passing a field we didn’t care about and we later decided that was an optional field but couldn’t be an empty string and it broke that client)
Sean Corfield -- (970) FOR-SEAN -- (904) 302-SEAN
An Architect's View -- http://corfield.org/
"If you're not annoying somebody, you're not really alive."
-- Margaret Atwood
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
You received this message because you are subscribed to a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/i8Rz-AnCoa8/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojure+unsubscribe@googlegroups.com.
Eric does raise an interesting question tho
I think so too. I'm still finding it hard to come up with a single example of why allowing extra keys to validate would ever be useful even for non-breakage. I can't see how it would break anything.
You received this message because you are subscribed to a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/i8Rz-AnCoa8/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojure+unsubscribe@googlegroups.com.
If you have an API that cares about ‘a’, ‘b’, and ‘c’ and you later specify that ‘d’ is optional and should be an ‘int?’, does that qualify as breakage or growth? If clients were sending ‘d’ as a string before but you ignored it, it will break those clients. Clients that were not sending ‘d’ will not be affected by the change. The old spec – allowing ‘d’ to be ‘any?’ essentially – won’t fail on any data that omits ‘d’ or passes it as ‘int?’ so it passes your compatibility test.
Isn't this precisely why you should use namespaced keywords?
tirsdag 14. november 2017 19.43.55 UTC+1 skrev Sean Corfield følgende:
Eric does raise an interesting question tho’:
(we actually ran into this at work because a client app was passing a field we didn’t care about and we later decided that was an optional field but couldn’t be an empty string and it broke that client)
You received this message because you are subscribed to a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/i8Rz-AnCoa8/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojure+unsubscribe@googlegroups.com.
If you have an API that cares about ‘a’, ‘b’, and ‘c’ and you later specify that ‘d’ is optional and should be an ‘int?’, does that qualify as breakage or growth? If clients were sending ‘d’ as a string before but you ignored it, it will break those clients. Clients that were not sending ‘d’ will not be affected by the change. The old spec – allowing ‘d’ to be ‘any?’ essentially – won’t fail on any data that omits ‘d’ or passes it as ‘int?’ so it passes your compatibility test.Isn't this precisely why you should use namespaced keywords?Yes! Great point.That sounds like a great thing to document in your API. Something like: "all keys in com.xyz namespace are reserved and could be restricted at any time. Please don't use them." You could send a warning back in your API if they use any that don't validate.Are there any resources that document these best practices? It seems like Clojure is trying to push us down this road but there's no map yet at the moment.
tirsdag 14. november 2017 19.43.55 UTC+1 skrev Sean Corfield følgende:Eric does raise an interesting question tho’:
(we actually ran into this at work because a client app was passing a field we didn’t care about and we later decided that was an optional field but couldn’t be an empty string and it broke that client)
Sean Corfield -- (970) FOR-SEAN -- (904) 302-SEAN
An Architect's View -- http://corfield.org/
"If you're not annoying somebody, you're not really alive."
-- Margaret Atwood
From: clo...@googlegroups.com <clo...@googlegroups.com> on behalf of Seth Verrinder <set...@gmail.com>
You received this message because you are subscribed to a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/i8Rz-AnCoa8/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.
That use case sounds more like the exception then the rule to me. I still think its valid, and for that I recognise there should be a way to have a spec which allows any key and requires specific ones.
Now that I've listened to everyone, I see that there might be many things needed by different people, and its not clear if all should be provided by spec directly.
Mostly I can see wanting:
A. To spec a map in which you expect the keys to have a registered spec.
B. To spec a map in which you don't expect the keys to have a registered spec.
1. To spec a map in which the keys should be closed to only the req and opt keys that are defined.
2. To spec a map in which the keys should be open to what is defined, but where certain keys are required and others reserved as optional.
Where we want to be able to mix and match A,B with 1,2.
I see a safe default being A,1. Though I'd be okay with A,2. I think B is the bigger issue not to have as default, and is less intuitive. It's prone to typos and forgetting to add the spec or require the proper namespace, etc.
My conclusion: I think s/keys is a funny part of spec, because compared to all other provided speccing tool, this one is really just syntax sugar for convenience. Yet it makes sense, because it is just so damn convenient. But for a lot of people like me, its convenient in a surprising way, which leans towards compatibility instead of correctness and security. And most baffling, trying to spec a map in a correct, secure and convenient way is not possible, at least not with the same convenience. So I'd say if there was just also convenient s/closed-keys and s/specified-keys and s/specified-closed-keys everyone would be happy, and it would also be more explicit what distinction exist between all 4. I'd probably always reach first for s/specified-closed-keys, and relax if I need too only, but all least everyone would have what they want and could chose for themselves.
It's not about easier, it's about possible. Open grows, closed breaks.
Personally I also wouldn't consider a client who sends a key that is not used, to be broken if I now validate that he is not supposed to send it. I did not break the API, I'm only making him realize he might have been wrong on how the API worked since the beginning, if he thought somehow that this key was a valid input and maybe expected it to alter the behavior. But in terms of semantics and behavior, nothing has changes or been broken on my API.
Yes, I think I agree with this position and don’t consider it a “breakage” – even tho’ it may actually “break” code that was assuming the API would always ignore any additional keys.
Sean Corfield -- (970) FOR-SEAN -- (904) 302-SEAN
An Architect's View -- http://corfield.org/
"If you're not annoying somebody, you're not really alive."
-- Margaret Atwood
[...] when we constrain maps in that closed way, aren't we creating some new subtype of a map, with fundamentally different semantics? If you are going to fully close a map, you might as well use a deftype and make a custom object and not call it a map, right?
(s/def ::name string?) (defmulti msg :msg-type) (defmethod msg :greeting [x] (s/keys :req-un [::name])) (defmethod msg :count [x] (s/keys :req-un [::num])) (s/def ::msg (s/multi-spec msg :msg-type))
(I suppose it's always going to be possible to write a linter that walks the Clojure code and understands the semantics of the above macros, but I was hoping I could write one using the data contained in the spec registry instead)
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.