This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2019-10-24
Channels
- # announcements (1)
- # aws (140)
- # beginners (41)
- # calva (47)
- # cider (43)
- # clj-kondo (36)
- # clojure (178)
- # clojure-europe (12)
- # clojure-gamedev (2)
- # clojure-italy (1)
- # clojure-nl (17)
- # clojure-russia (3)
- # clojure-spec (37)
- # clojure-uk (97)
- # clojurescript (173)
- # core-async (16)
- # cursive (18)
- # data-science (2)
- # datascript (6)
- # datomic (32)
- # dirac (16)
- # duct (16)
- # events (2)
- # figwheel-main (7)
- # fulcro (8)
- # graalvm (18)
- # immutant (3)
- # joker (2)
- # kaocha (8)
- # nrepl (6)
- # nyc (2)
- # off-topic (62)
- # quil (3)
- # re-frame (18)
- # reitit (6)
- # ring-swagger (1)
- # shadow-cljs (119)
- # spacemacs (4)
- # specter (2)
- # tools-deps (10)
- # vim (58)
- # xtdb (9)
do we have any libraries that can generate specs based on DB schema, e.g.: postgress DDLs?
has anyone dealt with type of a problem where you need a spec for a map with underscored keys (for data coming from DB) and absolutely same spec with the only difference being that keys are hyphenated? Maybe someone wrote a macro?
So, if i have s Schema with ::id
, ::name
, ::description
and ::secret
keys and I would like to make an api where you must send ::id
& ::name
, optionally ::description
, but not ::secret
, I would first have to make an (api) Schema with just ::id
, ::name
and ::description
close that (so the ::secret
becomes illegal) and make a select for that schema with just ::id
and ::name
defined so the ::description
becomes optional. Right?
Spec 2 uses an option hash map to specify which specs are closed -- at checking time (`s/valid?` s/conform
etc)
select
is always open. It just says what is required.
Thanks, that’s my understanding too. So, to support a use case that one can’t send the ::secret
, I need to create a concrete new Schema to be able to mark is as closed.
The checking code can determine which specs are treated as closed -- independent of schema
/ select
.
https://github.com/clojure/spec-alpha2/wiki/Differences-from-spec.alpha#closed-spec-checking
(s/def ::id int?)
(s/def ::name string?)
(s/def ::description string?)
(s/def ::secret boolean?)
;; the internal schema
(s/def ::user (s/schema [::id ::name ::description ::secret]))
;; the api schema
(s/def ::user2 (s/schema [::id ::name ::description]))
;; the api select (id & name mandatory, description optional)
(s/def ::user3 (s/select ::user2 [::id ::name]))
;; closed validation
(s/valid? ::user3 {::id 1, ::name "kikka"} {:closed #{::user2}})
i wonder why closed specs are implemented as options instead of as another type of spec
@vlaaad Alex talks about that on the Inside Clojure blog as Spec 2 went down the "closed spec" path first and then changed to "closed checking".
you mean this http://insideclojure.org/2019/04/19/journal/ ?
(s/close-specs ::s)
this is some mutability thing, I was talking about having closed specs as different specs, no mutability attached
like that:
(s/def ::open-keys (s/keys :req-un [::some-key]))
(s/def ::closed-keys (s/closed ::open-keys #{::open-keys}))
I think that makes sense: being able to refer to your data specification by name, instead of by combining a name and an arguments to s/valid?
I'll leave it up to @alexmiller to respond. I prefer the path they've taken where "closedness" is just an option on checking.
Not going to do that
It’s not composable - negation is inherently problematic as specs evolve
I don't get it, can you explain? I thought about closed specs for a bit, and the way I see them, they can be just a s/select
counterpart, where s/select
describes lower bound of data shape ("be at least that"), while s/closed
describes upper bound ("be at most that").
By composable I mean using same specs both as open and as closed in same s/valid?
etc. checks. I think it might make sense, for example, if I were to spec a function that cleans user input before putting it into db, it's spec would look like that:
(s/fspec
:args (s/cat :user ::user)
:ret (s/closed ::user))
Speaking of specs evolution, do you mean being able to tell if change of spec from a to b is accretive or not? I don't see problems here as well:
- changing (s/closed (s/keys [::name]))
to (s/closed (s/keys [::name ::age]))
in return position provides more — it returned ::name
, now it also can return ::age
;
- changing (s/closed (s/keys [::name]))
to (s/closed (s/keys [::name ::age]))
in argument position requires less — it didn't accept ::age
, now it accepts it;
- changing (s/closed (s/keys [::name]))
to (s/keys [::name])
in return position provides more — it used to return only ::name
, now it may return more.those words do not match what you're saying in the specs. the closed version says "MUST NOT contain ..." 1/3. is probably ok from the consumers point of view but is contextual to use 2. if you provide age on the first one, then forbid on the second one, you've broken users that previously passed age
this notion of context is critical and is driving the changes in schema/select/fdef, so I wouldn't rule out some way to make a more restrictive statement at the point of use, although we currently think of that again as more of a "check", not part of a spec
maybe we have different definitions of closed? My (s/closed [::name ::address])
says that map may be empty, or contain ::name
, or contain ::address
, or contain both, but no extra keys are allowed
like select
, that requires at least this and allows more, but in different direction: requires at most this and allows less. in different contexts select
, closed
and a combination of both might be useful
so in "2" you could not provide ::age
before: this is invalid, and version after allows it
well, again the principle here is that "closedness" is part of the checking, never part of the spec
I agree on that, but have thought that select is a utility of making checking context (values!) from schema. It would allow one place & syntax to easily close any of the nested maps too. The current syntax of closing specs by name in s/valid?
with different (nested maps) syntax adds more things for the developer to keep in his/her head. Need to jump to both schema and select definitions to see what subkeys are including in the select and need to be closed. Also, if schemas & selects get refactored, the closed options might get out-of-sync.
In the code I pasted above, keysets need to be introduced in three places: the root schema, the api schema and the select. Also, partially in the s/valid?
call to make it closed.
I was a bit surprised by this:
user=> (require '[clojure.spec-alpha2 :as s])
nil
user=> (s/def ::thing (s/schema [::a ::b ::c]))
:user/thing
user=> (s/valid? (s/select ::thing [::a ::b]) {::a 1 ::b 2 ::c 3} {:closed #{::thing}})
true ; fine -- ::c is optional but present
user=> (s/valid? (s/select ::thing [::a ::b]) {::a 1 ::b 2} {:closed #{::thing}})
true ; fine -- ::c is optional and omitted
user=> (s/valid? (s/select ::thing [::a ::b]) {::a 1 ::b 2 ::d 4} {:closed #{::thing}})
true ; huh? -- ::d is not in ::thing -- why doesn't this fail?
user=> (s/valid? (s/select ::thing [::a ::b]) {::a 1 ::d 4} {:closed #{::thing}})
false ; because ::b is required but omitted... see next...
user=> (s/explain-str (s/select ::thing [::a ::b]) {::a 1 ::d 4} {:closed #{::thing}})
"#:user{:a 1, :d 4} - failed: (fn [m] (contains? m :user/b))\n"
user=>
@alexmiller is that expected or a bug?user=> (s/def ::sub-thing (s/select ::thing [::a ::b]))
:user/sub-thing
user=> (s/valid? ::sub-thing {::a 1 ::b 2 ::d 4} {:closed #{::sub-thing}})
true
user=> (s/valid? ::sub-thing {::a 1 ::b 2 ::d 4} {:closed #{::thing}})
true
(still puzzled)there's some lingering tech debt in the schema/select code where some things are duplicated rather than having select lean on schema's validation code. I think once that's cleaned up, these will fail as you expect
I agree on that, but have thought that select is a utility of making checking context (values!) from schema. It would allow one place & syntax to easily close any of the nested maps too. The current syntax of closing specs by name in s/valid?
with different (nested maps) syntax adds more things for the developer to keep in his/her head. Need to jump to both schema and select definitions to see what subkeys are including in the select and need to be closed. Also, if schemas & selects get refactored, the closed options might get out-of-sync.
I don't get it, can you explain? I thought about closed specs for a bit, and the way I see them, they can be just a s/select
counterpart, where s/select
describes lower bound of data shape ("be at least that"), while s/closed
describes upper bound ("be at most that").
By composable I mean using same specs both as open and as closed in same s/valid?
etc. checks. I think it might make sense, for example, if I were to spec a function that cleans user input before putting it into db, it's spec would look like that:
(s/fspec
:args (s/cat :user ::user)
:ret (s/closed ::user))
Speaking of specs evolution, do you mean being able to tell if change of spec from a to b is accretive or not? I don't see problems here as well:
- changing (s/closed (s/keys [::name]))
to (s/closed (s/keys [::name ::age]))
in return position provides more — it returned ::name
, now it also can return ::age
;
- changing (s/closed (s/keys [::name]))
to (s/closed (s/keys [::name ::age]))
in argument position requires less — it didn't accept ::age
, now it accepts it;
- changing (s/closed (s/keys [::name]))
to (s/keys [::name])
in return position provides more — it used to return only ::name
, now it may return more.@alexmiller bump