This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-02-07
Channels
- # announcements (32)
- # asami (11)
- # babashka (5)
- # babashka-sci-dev (4)
- # beginners (65)
- # biff (11)
- # calva (35)
- # clerk (2)
- # clj-kondo (14)
- # clj-on-windows (4)
- # clojars (4)
- # clojure (122)
- # clojure-canada (1)
- # clojure-europe (31)
- # clojure-italy (6)
- # clojure-nl (1)
- # clojure-norway (7)
- # clojure-spec (3)
- # clojure-uk (2)
- # clojurescript (3)
- # core-async (7)
- # core-logic (1)
- # data-science (13)
- # datalog (3)
- # datavis (3)
- # datomic (15)
- # deps-new (4)
- # emacs (34)
- # figwheel-main (1)
- # fulcro (1)
- # funcool (1)
- # holy-lambda (10)
- # lsp (41)
- # malli (24)
- # membrane (5)
- # midje (1)
- # off-topic (5)
- # polylith (3)
- # proletarian (6)
- # re-frame (6)
- # reitit (6)
- # remote-jobs (4)
- # sci (1)
- # shadow-cljs (96)
- # sql (31)
- # testing (23)
- # xtdb (49)
m/entries
wraps the entries into [::m/varl :int]
like reified structure, how do I unwrap the entry get that :int
out?
I'm trying to create a schema for a map that can contain one of a number of keys, e.g. a login form schema that can accept :username
or :email
. I'm sure it must be possible to create a schema to represent this but I can't find mention of it in the docs. I want to write something like
(def schema
[:map
[:or
[:username [:string]]
[:email [:string]]
[:password [:string]]])
Could use an :fn
schema:
(def my-schema
[:and
[:map
[:x int?]
[:y int?]]
[:fn (fn [{:keys [x y]}] (or x y))]])
Iād go with @UEENNMX0Tās fn approach
Yes, there is no declarative syntax for dependent keys. One could cook up that with :multi
but would be quite ugly. This could be nice (and doable in the user space):
(def schema
[:and
[:map
[:username {:optional true} [:string]]
[:email {:optional true} [:string]]
[:password [:string]]]
[:keys/xor [:username :email]]])
kind of rhymes with the https://github.com/bsless/malli-keys-relations library
Thanks, everyone. I ended up making 2 different schemas and choosing between them at runtime depending on which field was presented in the submitted form.
> Yes, there is no declarative syntax for dependent keys. One could cook up that with :multi
but would be quite ugly. This could be nice (and doable in the user space):
>
(def schema
> [:and
> [:map
> [:username {:optional true} [:string]]
> [:email {:optional true} [:string]]
> [:password [:string]]]
> [:keys/xor [:username :email]]])
>
The only thing about this (and its equivalent :fn
version) is what would happen if neither :username
nor :email
were submitted. Both fields are declared as optional which implies that the form should validate, but that's not correct. Each field is required if the other isn't present.@UEENNMX0T I came back to this problem today and I'm struggling to implement your proposed solution. Given the code:
(humanize (explain (malli/schema [:and
[:map
[:id int?]
[:username string?]]
[:fn (fn [{:keys [id username]}]
(or id username))]])
{:id 1}))
I get {:username ["missing required key"]}
as a result but my intention is that there should be no error as id
has been correctly provided.might need {:optional true}
Hi, I hack something up to convert malli schema to EQL querry, it support recursive query. Here's the code with an example at the bottom that convert the burger schema from http://malli.io
(ns malli-eql
(:require
[malli.core :as m]
[malli.util :as mu]))
(defn trim-branch-path [paths]
(reduce (fn [acc item]
(if (some #{item} (map #(vec (take (count item) %)) acc))
acc
(conj (vec (remove #(#{(butlast item)} %)
acc))
item)))
[]
paths))
(defn -collect [schema]
(let [state (atom {})]
(m/walk
schema
(fn [schema _ _ _]
(let [properties (m/properties schema)]
(doseq [[k v] (-> (m/-properties-and-options properties (m/options schema) identity) first :registry)]
(swap! state assoc-in [:registry k] v))
(swap! state assoc :schema schema)))
{::m/walk-schema-refs true})
@state))
(defn map-vec-tree-seq [form]
(tree-seq #(or (sequential? %)
(associative? %))
(fn [form]
(cond (sequential? form) (next form)
(associative? form) (#(interleave (keys %) (vals %)) form)))
form))
(defn recursive-registry [schema]
(->> (-> schema m/schema -collect :registry)
(map (fn [[k v]]
[k
(->> v
mu/subschemas
trim-branch-path
(map #(if (and (= (m/type (:schema %)) :ref)
(= k (first (m/children (:schema %)))))
(assoc % :in (conj (:in %) '...))
%))
(map :in))]))
(filter (fn [[_k v]] (some #{'...} (map-vec-tree-seq v))))
(into {})))
(defn path->eql [path]
(let [p' (->> path
(remove #{:malli.core/in}))
eql (reduce #(hash-map %2 %1) (if (= '... (last p')) (last p') [(last p')]) (reverse (drop-last p')))]
(or (if (vector? eql)
(first eql)
eql) [])))
(defn deep-merge-eql [forms]
(reduce (fn f'
([] nil)
([a b]
(let [s (when (and (map? b) (sequential? a))
(some
(fn [x'] (when (get x' (ffirst b))
x'))
a))
r (cond (and (vector? a)
(vector? b))
(vec (into #{} (concat a b)))
s (conj (vec (remove #{s} a)) (deep-merge-eql [s b]))
(and (vector? a)
(map? b)
(some #{(ffirst b)} a)) (conj (vec (remove #{(ffirst b)} a)) b)
(and (vector? a)
(map? b)) (conj a b)
(and (map? a)
(map? b)
(= (ffirst a) (ffirst b))) (merge-with f' a b)
(and (map? a)
(map? b)) [a b]
(and (vector? b)
(map? a)
(some #{(ffirst a)} b)) (conj (vec (remove #{(ffirst a)} b)) a)
(and (map? a)
(vector? b)) (conj b a)
(vector? a) (vec (into #{} (conj a b)))
(vector? b) (vec (into #{} (conj b a)))
:else
(vector a b))]
r)))
forms))
(defn schema->paths [schema registry]
(let [paths (atom [])
collect-paths-walker! (fn walker'
([s] (walker' s []))
([s parents]
(m/walk s (fn [schema path _children _options]
(let [is-ref (and (m/-ref-schema? schema)
(m/-ref schema))
ref-name (when is-ref (m/-ref schema))
in (mu/path->in (m/schema s) path)]
(cond (and is-ref
(some #{ref-name} (keys registry)))
(mapv #(swap! paths
conj
(vec (concat parents in %)))
(get registry ref-name))
(and is-ref ref-name)
(walker' (m/deref schema) (vec (concat parents (mu/path->in (m/schema s) path))))
:else (swap! paths conj (vec (concat parents in)))))))))]
(collect-paths-walker! schema)
@paths))
(defn schema->eql [schema]
(->> (schema->paths schema (recursive-registry schema))
trim-branch-path
(map path->eql)
deep-merge-eql))
(schema->eql [:schema
{:registry {"Country" [:map
{:closed true}
[:name [:enum :FI :PO]]
[:neighbors
{:optional true}
[:vector [:ref "Country"]]]],
"Burger" [:map
[:name string?]
[:description {:optional true} string?]
[:origin [:maybe "Country"]]
[:price pos-int?]],
"OrderLine" [:map
{:closed true}
[:burger "Burger"]
[:amount int?]],
"Order" [:map
{:closed true}
[:lines [:vector "OrderLine"]]
[:delivery
[:map
{:closed true}
[:delivered boolean?]
[:address
[:map
[:street string?]
[:zip int?]
[:country "Country"]]]]]]}}
"Order"])
;; => [{:lines
;; [:amount
;; {:burger [:description :name {:origin [:name {:neighbors ...}]} :price]}]}
;; {:delivery
;; [:delivered {:address [:street :zip {:country [:name {:neighbors ...}]}]}]}]
Wow this is eery, I came here to ask a question about building some pathom code from malli. Are you working on something open-source and using pathom?
Iām also playing with pathom and malli, nothing worth publishing atm. But I think it might be useful to have more tool to transform malli schema into other form to be used to generate form/pathom/db schema/ā¦ The actual part relevant to eql in the above code is not much.
i had written code to pull data from a json request using malli, which i realized is too naiive (like nested data). But, also wanting to pass that to pathom to query as well.