This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2024-04-05
Channels
- # announcements (7)
- # beginners (10)
- # calva (14)
- # clj-otel (8)
- # clojure (42)
- # clojure-europe (20)
- # clojure-nl (1)
- # clojure-norway (22)
- # clojure-spec (8)
- # clojure-uk (7)
- # core-async (10)
- # cursive (1)
- # events (1)
- # hyperfiddle (20)
- # introduce-yourself (1)
- # jobs-discuss (11)
- # lsp (48)
- # missionary (3)
- # music (1)
- # off-topic (7)
- # overtone (9)
- # pedestal (21)
- # rdf (1)
- # releases (3)
- # shadow-cljs (22)
- # specter (13)
- # squint (1)
- # yamlscript (3)
I'm trying to spec this situation, where I have maps with a :type
keyword that correspond to an actual "spec", and a :request
which is that value that should be "spec'ed" by it.
I'm thinking at this point that it involves a combination of multi-spec
and conform/unform/conformer
but I'm not quite figuring out how to put it all together.
Any tips?
(def req1
{:type :x
:request {:a 1}})
(def req2
{:type :y
:request {:b 1}})
(s/def ::a int?)
(s/def ::b int?)
(s/def ::conformed-request
(s/conformer
(fn [{:keys [type request] :as m}]
[type request])
(fn [[type request]]
{:type type :request request})))
(->> {:type :x
:request {:a 1}}
(s/conform ::conformed-request)
#_(s/unform ::conformed-request))
(defmulti request-spec :type)
(defmethod request-spec :x [_]
;; not correct, this is the spec for :request in a map with :type :x
;; I could first conform to [spec-key value] and then validate that. Is there a way to do that without predicates?
#_(s/keys :req-un [::a]))
does not seem to use s/multi-spec ?
there's a multi-spec example at https://clojure.org/guides/spec#_multi_spec if that helps
But in that case and most the examples I've found, the multimethod returns some spec for the overall thing that is being dispatched on.
In my case the data is nested, so I have maps with :type
and :request
and :type
is a spec-key and :request
is the thing to be spec'ed according to that key
So I could dispatch on :type in the multimethod but then i'd need to say in the defmethod impl to return a spec that says "in this context, the :request
key should be spec'd according to the dispatch value. But I don't think that's possible so don't think multi-spec
is the answer here, or I'm looking at it wrong.
I could preprocess this data to turn it into {spec-key request-data}
and then call s/valid?
on it, but I'm wondering if there's a better way.
(defmulti request-spec :type)
(defmethod request-spec :x [_]
;; this is the spec for the value at the :request key when type is :x
#_(s/keys :req-un [::a])
;; what actually goes here?
)
(s/def ::request-data
(s/and (s/keys :req-un [::request ::type])
(s/multi-spec request-spec :type)))
I think the basic mismatch is that :request means more than one thing here. generally spec expects a key to mean on thing. however, I think you could handle this by: • creating a ::x-req-spec for :request when spec is x, and an overall message spec ::x-msg for the map using it • creating a ::y-req-spec for :request when spec is y, and an overall message spec ::y-msg for the map using it • have each defmethod return the latter
Ah that makes sense, thanks!
And just to make sure, conformer
s definitely aren't the right path to go down here right? Was starting to mess around with this, but not really getting anywhere
(s/def ::conformed-request
(s/conformer
(fn [{:keys [type request] :as m}]
(if-not (and type request)
::s/invalid
(assoc m
type request)))))
(s/def ::x (s/keys :req-un [::a]))
(s/def ::y (s/keys :req-un [::b]))
(s/def :outer/request
(s/and
(s/keys :req-un [:inner/request ::type])
::conformed-request
;;was hoping this would "recheck" the conformed thing but doesn't seem to work that way
(s/keys)))
conformers are almost never the right path to go down :)