This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-06-07
Channels
- # aleph (5)
- # announcements (2)
- # babashka (17)
- # beginners (13)
- # cider (4)
- # clj-kondo (9)
- # cljdoc (18)
- # clojure (20)
- # clojure-art (7)
- # clojure-dev (11)
- # clojure-europe (20)
- # clojure-nl (2)
- # clojure-norway (53)
- # clojure-spec (30)
- # clojure-uk (4)
- # clojurescript (40)
- # datomic (70)
- # events (2)
- # graalvm-mobile (8)
- # gratitude (2)
- # guix (2)
- # honeysql (3)
- # hyperfiddle (10)
- # introduce-yourself (4)
- # jobs (2)
- # lsp (6)
- # luminus (3)
- # malli (5)
- # polylith (35)
- # practicalli (6)
- # re-frame (3)
- # reagent (9)
- # releases (1)
- # remote-jobs (20)
- # ring-swagger (2)
- # shadow-cljs (12)
- # sql (18)
- # xtdb (7)
Is there a good way to dynamically create new s/or
specs at runtime? As s/or
is a macro, it feels almost impossible to do that, hence I’d have to go down to or-spec-impl
which is very much discouraged:
(defn json-schema-or-spec
"Create a `s/or` spec from a coll-valued JSON Schema `type`"
[schema-types]
(let [pairs (map (fn [schema-type]
(case schema-type
"null" [:null ::null]
"boolean" [:boolean ::boolean]
"integer" [:integer ::integer]
"number" [:number (type->spec "number")]
"string" [:string (type->spec "string")]
"array" [:array (type->spec "array")]
"object" [:object (type->spec "object")]))
schema-types)
keys (mapv first pairs)
preds (mapv second pairs)]
(s/or-spec-impl keys preds preds nil))
(or alternatively end up creating a case
statement with 128 different cases)you can write that case with a macro around s/or :)
but maybe a different kind of spec is better, like s/multi-spec?
Writing a macro around s/or
was my first attempt:
(defn- schema-type->pair
[type->spec schema-type]
(case schema-type
"null" `[:null ::null]
"boolean" `[:boolean ::boolean]
"integer" `[:integer ::integer]
"number" `[:number (~type->spec "number")]
"string" `[:string (~type->spec "string")]
"array" `[:array (~type->spec "array")]
"object" `[:object (~type->spec "object")]))
(defmacro coll-schema-spec
[type->spec schema-types]
(let [schema-type->pair (partial schema-type->pair type->spec)]
`(s/or ~@(mapcat schema-type->pair schema-types))))
Unfortunately it did not go very well: I ended up encountering a lot of
Don't know how to create ISeq from: clojure.lang.Symbol
errors because I was passing in schema-types
as a variablewell I think you're on the right track there, but you don't want ~@ - this is one of those cases that might be easier as literal construction with list
cons
etc
~ turns off quoting and turns eval back on, but you don't want to eval
also, schema-type->pair
is just a map
Still getting the same error; I think the fundamental issue is that schema-types
is a coll whose values are only known as runtime, so trying to fit it inside any compile-time macro like s/or
will only result in failure.
can you just register these schemas at runtime then?
think about it as dynamically making static specs, not statically making dynamic specs
The issue comes with those other specs whose implementations depend on the JSON Schema user input (the type->spec
function elides a lot of complexity)
For example with array
and object
you’re basically creating s/coll-of
and s/keys
specs from user input - not really conducive for registering static specs
not conducive at compile time, but totally fine at runtime
Since s/or
requires keyword literals as keys, not variables representing those keywords
I used eval to pass in my schema-types
variable to the macro, but that gave a “can’t eval locals” error
Anyways, going back to @U064X3EF3’s point, I’m not sure what he meant by “register these schemas at runtime” since when I think of registering specs, I automatically think of s/def
I guess you can also generate the spec keywords at runtime, but given the potentially infinite schema values that does not seem to be a good idea
ultimately, the spec registry is a map in your Clojure runtime. you can put things in it anytime, not just when you load a namespace that calls s/def
(all of this is more readily available in the spec 2 api, both making specs and registering them without s/def)
And spec 2 is supposed to have more support for dynamic, data-driven specs like the varardic s/or
I’m trying to make, right?
It works like a charm just like any other spec, and there’s not really any downside other than a strongly worded docstring
spec 2 has a data form you can use to make a spec, and a non-macro way to register a spec