This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2020-12-05
Channels
- # adventofcode (50)
- # announcements (1)
- # asami (29)
- # babashka (56)
- # beginners (19)
- # calva (62)
- # cider (12)
- # cljs-dev (1)
- # clojure (42)
- # clojure-europe (214)
- # clojure-france (4)
- # clojure-italy (1)
- # clojurescript (58)
- # community-development (4)
- # cryogen (6)
- # cursive (7)
- # data-science (1)
- # events (3)
- # figwheel-main (1)
- # fulcro (21)
- # lambdaisland (3)
- # malli (17)
- # mid-cities-meetup (1)
- # off-topic (38)
- # pathom (3)
- # reagent (7)
- # reclojure (1)
- # reveal (15)
- # rewrite-clj (11)
- # shadow-cljs (30)
- # sql (21)
- # test-check (14)
- # tools-deps (1)
- # vim (21)
- # xtdb (5)
is there a recommended way to write a function that will dispatch to an implementation according to which of several schemas match my input arguments? I can construct my own of course that just does a linear search, and i could create a multi schema to compose them together (though i would need to use a compound discriminator for my case). I guess what i'm wondering is if there's a way to do pattern matching using malli schemas, but preferably in an open way like multimethods so i can just accrete new cases instead of modifying a case expression.
maybe i'm just rubbing up against open arbitrary predicate dispatch at that point. hm
perhaps i could add an optional attribute on each schema (in my case they are open maps so this is fine) and supply a :default attribute. then i can run a default transformer on my inbound value to inject a "type" and from there just use regular multimethods to dispatch on that attribute.
yeah that works. sorta rough and not sure the macro is a good idea but in case anyone is curious:
(defn dispatchable [dispatch-key schema]
(mu/update-properties
schema
(fn [props]
(let [dispatch-decoder (fn [value] (with-meta value {:dispatch dispatch-key}))]
(assoc-in props [:decode/dispatch] {:leave dispatch-decoder})))))
(defn dispatch-key [schema value]
(some-> (m/decode schema value (mt/transformer {:name :dispatch})) meta :dispatch))
(defmacro defdispatchable [symbol & body]
`(def ~symbol (dispatchable ~(name symbol) (do [email protected]))))
(defdispatchable token-file-auth
[:map [:tokenFile :string]])
(defdispatchable client-key-auth
[:map
[:client-key-data :string]
[:client-certificate-data :string]])
(def combined
[:or token-file-auth client-key-auth])
(defmulti handle-auth (fn [context] (dispatch-key combined context)))
(defmethod handle-auth "token-file-auth" [context]
(println "token file!"))
(defmethod handle-auth "client-key-auth" [context]
(println "client key!"))
(handle-auth {:tokenFile "st"})
token file!
=> nil
(handle-auth {:client-key-data "st"
:client-certificate-data "sfd"})
client key!
Actually, i think this is much better:
(def schema-resolver-transformer
(mt/transformer
{:default-decoder
{:compile (fn [schema _]
(fn [value]
(if (instance? IObj value)
(vary-meta value assoc :resolved schema)
value)))}}))
(defn resolve-schema [schema value]
(some-> (m/decode schema value schema-resolver-transformer) (meta) :resolved))
(def token-file-auth
[:map {:dispatch :token-file}
[:tokenFile :string]])
(def client-key-auth
[:map {:dispatch :client-key}
[:client-key-data :string]
[:client-certificate-data :string]])
(def combined
[:or token-file-auth client-key-auth])
(defn dispatch-fn [context]
(let [schema (resolve-schema combined context)]
(:dispatch (m/properties schema))))
(defmulti handle-auth #'dispatch-fn)
(defmethod handle-auth :token-file [context]
(println "token file!"))
(defmethod handle-auth :client-key [context]
(println "client key!"))
Tommi, let me know if there's a better way to do this ^. Providing a way to resolve the concrete schema that is used for each node in a tree of data allows me to put useful data into the schema properties and recover that data for a given value so that i can act on it (like dispatching to different functions in my code based on which schema matched). Here i am attaching the schema to the data as metadata (which makes sense but will not work for values that don't implement IObj)
@sfyire @borkdude I think one needs to add extra entry into :config-paths
into .clj-kondo/config.edn
. I have the following:
✗ cat .clj-kondo/config.edn
{:config-paths ["configs/malli"]}
forgot all about that, will add it to README if that is the required glue here.e.g. with that, clj-kondo pics up the emitted file, looking something like this:
✗ cat .clj-kondo/configs/malli/config.edn
{:lint-as #:malli.schema{defn schema.core/defn}
:linters {:type-mismatch {:namespaces {user {square {:arities {1 {:args [:int], :ret :nat-int}}}
plus {:arities {1 {:args [:int], :ret :int}
2 {:args [:int :int], :ret :int}}}}}}}}
@rutledgepaulv your second options looks interesting, tagging values. I most likely would do it like that. One problem I see with this approach is that the meta-data can get out-of-sync: if your say (dissoc data :tokenFile)
, it still is tagged as :token-file
, but it not anymore valid against that schema. Might not be a problem if you just use it once, but relying of :resolved
meta in general has not guarantees.
The Schema parsing is currently WIP (first release coming soon), with that, you will have a variant to :or
(and :alt
), which will have named branches, like spec does, so you can say ~about this:
(def token-file-auth
[:map
[:tokenFile :string]])
(def client-key-auth
[:map
[:client-key-data :string]
[:client-certificate-data :string]])
;; using named :or variant
(def combined
[:or*
[:token-file token-file-auth]
[:client-key client-key-auth]])
(m/parse combined {:token-file "kikka"})
; => #Branch{:key :token-file
:value {:token-file "kikka"}}
thanks! the schema parsing stuff looks like that would address my issue too
you could also use m/encode
and return a tuple yourself, with both the schema/name and the value.
yeah. in my case i only need to know the concrete version of the top level schema so tuples would be fine