Fork me on GitHub

I have a multi-spec for different query types to a service. I also want to dispatch on the query type to actually query the service


what's the best way to consolidate these two things?


cause atm I have:

(defmulti query-type :type)

(defmethod query-type :view/id [_]
  (s/keys :req-un [:content.query/id]))

(defmethod query-type :fragment/id [_]
  (s/keys :req-un [:content.query/id]))
and then for implementation:
(defmulti query (fn [conn q] (:type q)))

(defmethod query :view/id
  [{:keys [url]} {:keys [id]}]
  {:url (str url "/view/" id)
   :method :get
   :as :json})

(defmethod query :fragment/id
  [{:keys [url]} {:keys [id]}]
  {:url (str url "/view/" id)
   :method :get
   :as :json})
and then I need to create a wrapper function so I can fdef the implementation, right?


afaik spec does not work for multimethods, but you can declare implementation function(s), fdef it/them, and make your multimethod call it/them


it looks like multi-specs s/keys check all available keys, even when not specified?


actually not sure if this is multi-specs or s/keys doing this


If you have a qualified key that matches a spec, it will be checked, whether it's specified in s/keys or not.

Alex Miller (Clojure team)21:04:18

this is documented in the s/keys docstring


gotcha - it's just not what I want right now 😂


@alexmiller just read your inside clojure. The api seems very global for closed specs. Have you given much thought to how it might be scoped?

Alex Miller (Clojure team)21:04:08

Scope in what dimension?

Alex Miller (Clojure team)21:04:31

It is very global, like instrumentation


Maybe with a dynamic binding, so thread local.


Thinking of a web server with many concurrent requests where you want the closed spec on the boundary, but internally within processing the open spec may be used (as other sources are merged).


Interesting choice to turn off and on “closedness” for specs. Would it make sense to combine this with select somehow? Like (s/def ::closed-spec (s/select ::open-spec ...))?

Alex Miller (Clojure team)22:04:31

It should work with select too, I just haven’t done that yet. There’s just a lot of copied code there at the moment


no, I mean, turn an open spec into a closed one using select, not by a global flag


(then select would have to support that)

Alex Miller (Clojure team)22:04:16

Closeness is intentionally not part of the spec language and won’t be

👍 4
Alex Miller (Clojure team)22:04:52

@dominicm still thinking about this aspect

👍 4

but when you would like to use a spec in different ways, you would have to toggle this flag all the time?

Alex Miller (Clojure team)22:04:35

That’s the aspect we’re think about still


I guess you can just make a copy of the spec, like (s/def ::foo ::bar), but if you close bar, will foo also be closed?

Alex Miller (Clojure team)22:04:31

Well that’s not a copy, it’s an alias


@borkdude feels like working around the intentional limitation to make closed&open copies.

Alex Miller (Clojure team)22:04:47

The answer is tricky, and may change


@alexmiller btw, why the global on/off switch? My reaction was that I'm going to see someone turning it on globally unconditionally in some project and removing the upside of open specs.

Alex Miller (Clojure team)22:04:34

Do you just mean the no-arity version of close/open?


is s/valid? part of the spec language? why not make it an option to those functions?

Alex Miller (Clojure team)22:04:56

s/valid? is api, not spec language


yeah ok. I think having it as an option is more flexible than setting it globally

Alex Miller (Clojure team)22:04:36

Still might do that. Breaks all existing APIs and specs though


extra arity doesn’t break?

Alex Miller (Clojure team)22:04:01

It will break the protocol

Alex Miller (Clojure team)22:04:37

We’ve also been thinking about a fast s/valid? path. Right now that’s tunneled through the conform* protocol, which does a lot of useless work if just validating. So that could also be a flag, or a separate protocol method


(s/valid2? …) 😉

Alex Miller (Clojure team)22:04:21

Those could all be flags internally on conform*

Alex Miller (Clojure team)22:04:40

We are already in a new namespace, no need to number

Alex Miller (Clojure team)22:04:08

But ideally could minimize breakage for some of those spec libs

Alex Miller (Clojure team)22:04:01

Anyhow, what I’m interested in hearing now is how you would use it

Alex Miller (Clojure team)22:04:16

We can work out the best answers from that


a fast s/valid? path would certainly help things like core specs which quickly become noticable in performance

Alex Miller (Clojure team)22:04:07

It’s not going to be like 10x or anything

Alex Miller (Clojure team)22:04:47

It really matters when you are constructing conformed outputs


I think the closedness aspect is something where people choose different tools or use workarounds for spec right now at API level things, like maybe Reitit and yada (which now uses Schema), but I’ll let those maintainers chime in

Alex Miller (Clojure team)22:04:26

Like how often are you likely to use the same spec in both open and closed check modes on the same spec? And where different, is it per usage or per thread or per what?


@alexmiller I can imagine closed for the incoming boundary, open internally when merging additional internal data, and potentially closed again when sending it on to another consumer (e.g. A service which accepts too much or a database)

🙂 4
☝️ 4

that’s exactly how I would use it too and probably in combination with select


I'm not the maintainer of yada, but I'm influential there. For incoming web requests, I'm more interested in: - not checking extra keys - not seeing extra keys


I don't want to throw if there's extra keys, probably, just silently drop them.


Sorta, but recursive :)


My first reaction is: ugh, no! Mutable global state on spec behavior just feels horribly wrong. I could have a spec defined in one place and just looking at that and the uses of it no longer tells me whether extra keys are permitted or not, because any random code, anywhere in the system can toggle that setting :face_vomiting: 🤯 😠


It seems like the absolute worst of all worlds, to be honest.

☝️ 28