How To Use Spec with Namespace Keywords That Aren't Valid Namespaces Coming From Datomic? - clojure

I'm not sure how to word my question, so apologies if it's confusing. I'm currently trying to write specs for some data that's coming out of datomic. A map I'm getting back might look like:
{:id "123abc" :event/date "1/1/2020"}
My goal is to write a spec like:
(s/def ::result (s/keys :req-un [::id string? :event-date string?]))
I've tried a couple things, and have setup a namespace schema.event where I'm defining the spec for date.
(ns schema.event)
(s/def :event/date string?)
The issue is that none of these work, I can't seem to get it to work with data containing event/date as it keeps validating on just :date.
The following will return success with my current setup:
{:id "123abc" :date "1/1/2020"}
But that doesn't mirror the data I'm getting from Datomic so isn't very helpful. What am I doing wrong here? I suspect it's just something to do with my lack of understand on how Clojure is treating namespaces.

That's because you're using the :req-un option. Try switching to the :req one. And you can have both - :req-un for ::id and :req for :event/date.

Related

Clojure.spec "or" equivalent of "s/and"

I've enjoyed working with clojure.spec; it has helped uncover data errors closer to the cause. Currently I am using it to validate a response to a web server request, but I am having difficulty with the syntax for the clojure.spec operation that would allow two different map structure responses.
In my data, there are two possible responses from the web server request:
{:assignment "1232123"} and
{:no-more-assignments true}
I could use multi-spec, but that seems verbose for something that could be as simple as having one spec for each case and defining the spec as:
(s/def ::response
(s/or ::case-1 ::case-2))
Is there some syntax that I am overlooking or will I need to use multi-spec?
You can use or and and with keys specs:
(s/def ::assignment string?)
(s/def ::no-more-assignments boolean?)
(s/def ::response
(s/keys :req-un [(or ::assignment ::no-more-assignments)]))
(s/explain ::response {:assignment "123"})
;; Success!
(s/explain ::response {:foo true})
;; val: {:foo true} fails spec: :sandbox.so/response predicate: (or (contains? % :assignment) (contains? % :no-more-assignments))

Clojure.spec keys: separating key name from the spec validating it

I need to validate the shape of clojure maps that have been converted from json strings. The json strings are messages of a protocol I'm implementing.
For this I'm trying out clojure.spec.alpha.
I'm using s/keys. Multiple messages in my
protocol have the same key names, but differently shaped values attached to those keys, so they cannot be validated by the same spec.
An example:
;; Here status should have the same key name, but their shape
;; is different. But definining another spec forces me to register it with a
;; different keyword, which breaks the "should have name 'status'" requirement.
(s/def ::a-message
(s/keys :req [::status]))
(s/def ::another-message
(s/keys :req [::status]))
I think I could define the :status spec in different namespaces, but it seems overkill to me.
After all it's just different messages in the same protocol and i just have a couple of clashes.
Is there a way for (s/keys) to separate the name of the key whose presence is being checked
from the name of the spec that is validating it?
In spec, qualified keywords are used to create global semantics (via the spec) whose name is the qualified keyword. If you use the same qualified keyword with different semantics, I'd say you should change your code to use different qualifiers :ex1/status and :ex2/status for different semantics.
If you are using unqualified keywords (not uncommon when coming from JSON), you can use s/keys and :req-un to map different specs to the same unqualified keyword in different parts of your data.
(s/def :ex1/status string?)
(s/def :ex2/status int?)
(s/def ::a-message (s/keys :req-un [:ex1/status]))
(s/def ::another-message (s/keys :req-un [:ex2/status]))
(s/valid? ::a-message {:status "abc"}) ;; true
(s/valid? ::another-message {:status 100}) ;; true

Specs for conformed specs / ASTs

I have a DSL specification which is a sequence as usual (cat). I want to take advantage of spec's parsing (i.e. conforming) to get the AST of an expression that conforms with my DSL. E.g.
user> (s/def ::person (s/cat :person-sym '#{person} :name string? :age number?))
=> :user/person
user> (s/conform ::person '(person "Henry The Sloth" 55))
=> {:person-sym person, :name "Henry The Sloth", :age 55}
Now that it's parsed and I have my AST, I would want to do interesting things with it, so I would want to test it and whatnot. So now I need to write a spec for that AST, and that's basically duplicating everything. Actually it's worse than that because now I have to s/def specs for predicates that I didn't have to before, because as the docs for keys says: "there is no support for inline value specification, by design." / "It is the (enforced) opinion of spec that the specification of values associated with a namespaced keyword, like :my.ns/k, should be registered under that keyword itself..". So duplicating (with omitting the person-sym part):
user> (s/def ::name string?)
=> :user/name
user> (s/def ::age number?)
=> :user/age
user> (s/def ::person-ast (s/keys :req-un [::name ::age]))
:user/person-ast
And now it seems to be compatible:
user> (s/conform ::person-ast (s/conform ::person '(person "Henry The Sloth" 55)))
=> {:person-sym person, :name "Henry The Sloth", :age 55}
In practice, I have more complicated data of course, and I wonder what should I do? AFAIK spec doesn't give me the spec for the AST that it creates (actually personally I would figure that this is something it should do). Any suggestions?
I'd say right now you have two options - one is to do what you're doing and create two sets of specs for the before/after.
The other option is to create a model of your domain in data and generate both specs (I've seen many people are doing something like this).
I have not heard Rich talk about generating the output spec of conformed results so I don't think that is likely in the current roadmap.

clojure specs for maps and their values

I'm using Clojure to implement a (written) standards document. In general I'm pleased with the way Clojure allows me to write code that lines up with the different parts of the standard. With an eye on the future I am experimenting with writing a clojure.spec for it. In the document they define various structured data elements with named fields. However fields in different structures have the same name, for example the 'red' structure has a 'value' field which is a string, but the 'blue' structure has a 'value' field which is an integer. How can I handle this when it comes to writing specs?
(s/def ::value ???)
(s/def ::red (s/keys :req [::value ...]))
(s/def ::blue (s/keys :req [::value ...]))
The official advice, as I understand it, is that named keys should have the same semantics everywhere.
How should I approach this? I could call them 'red-value' and 'blue-value' but this makes the correspondence between the code and the standard less clear. Could I put every structure in its own namespace?
Your example is using the current namespace for all of your spec names, but you should leverage namespaces to disambiguate names.
(s/def ::red (s/keys :req [:red/value ...]))
(s/def ::blue (s/keys :req [:blue/value ...]))
You can use these specs with maps like:
(s/valid? ::red {:red/value "foo"})
(s/valid? ::blue {:blue/value 100})
Additionally, s/keys supports :req-un option to link named specs to unqualified attribute names, if that's what you have to work with.
(s/def ::red (s/keys :req-un [:red/value ...]))
(s/def ::blue (s/keys :req-un [:blue/value ...]))
You could validate with values like:
(s/valid? ::red {:value "foo"})
(s/valid? ::blue {:value 100})

Forbidden keys in clojure.spec

I am following the clojure.spec guide. I understand it is possible to declare required and optional attributes when using clojure.spec/keys.
I don't understand what is meant by optional. To me :opt doesn't do anything.
(s/valid? (s/keys :req [:my/a]) {:my/a 1 :my/b 2}) ;=> true
(s/valid? (s/keys :req [:my/a] :opt []) {:my/a 1 :my/b 2}) ;=> true
The guide promises to explain this to me, "We’ll see later where optional attributes can be useful", but I fail to find the explanation. Can I declare forbidden keys? Or somehow declare the set of valid keys to equal the keys in :req and :opt?
This is a very good question, and the clojure.spec API gives the (granted, short and unsatisfying) answer:
The :opt keys serve as documentation and
may be used by the generator.
I do not think you can invalidate a map if it contains an extra (this is what you mean by "forbidden" I think) key using this method. However, you could use this spec to make sure ::bad-key is not present:
(s/def ::m (s/and (s/keys :req [::a]) #(not (contains? % ::bad-key))))
(s/valid? ::m {::a "required!"}) ; => true
(s/valid? ::m {::a "required!" ::b "optional!"}) ; => true
(s/valid? ::m {::a "required!" ::bad-key "no good!"}) ; => false
You could limit the number of keys to exactly the set you want by using this spec:
(s/def ::r (s/and (s/keys :req [::reqd1 ::reqd2]) #(= (count %) 2)))
(s/valid? ::r {::reqd1 "abc" ::reqd2 "xyz"}) ; => true
(s/valid? ::r {::reqd1 "abc" ::reqd2 "xyz" ::extra 123}) ; => false
Still, the best way to handle this IMO, would be to simply ignore that there is a key present that you don't care about.
Hopefully as spec matures, these nice things will be added. Or, maybe they are already there (it is changing rapidly) and I simply don't know about it. This is a very new concept in clojure, so most of us have a lot to learn about it.
UPDATE - December 2016
I just wanted to revisit this 6 months since writing it. It looks like my initial comment about ignoring keys you don't care about is the preferred way to go. In fact, at the clojure/conj conference I attended two weeks ago, Rich's keynote specifically addressed the notion of versioning in all levels of software, from the function level up to the application level. He even specifically mentions this notion of disallowing keys in the talk, which can be found on youtube. He says that it was intentionally designed so that only required keys can be spec'd. Disallowing keys really serves no good purpose, and it should be done with caution.
Regarding the :opt keys, I think the original answer still stands up pretty well--it's documentation, and practically, it allows these optionally specified keys to be generated:
(s/def ::name #{"Bob" "Josh" "Mary" "Susan"})
(s/def ::height-inches (s/int-in 48 90))
(s/def ::person (s/keys :req-un [::name] :opt-un [::height-inches]))
(map first (s/exercise ::person))
; some generated data have :height-inches, some do not
({:name "Susan"}
{:name "Mary", :height-inches 48}
{:name "Bob", :height-inches 49}
{:name "Josh"}
The point about optional keys is that the value will be validated if they appear in the map