This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2020-08-07
Channels
- # announcements (10)
- # babashka (39)
- # beginners (230)
- # calva (16)
- # cider (20)
- # clara (4)
- # cljs-dev (16)
- # clojure (35)
- # clojure-europe (8)
- # clojure-filipino (5)
- # clojure-france (1)
- # clojure-nl (6)
- # clojure-uk (9)
- # clojuredesign-podcast (1)
- # clojurescript (55)
- # clojurewerkz (1)
- # core-async (13)
- # cursive (1)
- # data-science (1)
- # datomic (4)
- # events (1)
- # fulcro (26)
- # jobs-discuss (1)
- # kaocha (3)
- # malli (53)
- # observability (9)
- # off-topic (1)
- # project-updates (1)
- # re-frame (15)
- # reagent (1)
- # reitit (11)
- # rum (8)
- # sci (29)
- # shadow-cljs (7)
- # vim (12)
- # xtdb (13)
Hoe does one combine validate and transform? E.g. this doesn't crash:
(prn (m/decode int? :foo mt/string-transformer))
I'm not saying it should, just wondering how to do it. Not clear from the READMEShould I first call m/valid, if not valid, then m/explain and else m/decode, effectively traversing the structure twice?
the m/decoder
doesn't have to walk the structure, it returns an function to transform just the parts that need to be decoded. In case there is nothing to do, it returns identity
@ikitommi The concrete example I was going to try:
$ cat deps.edn
{:deps {metosin/malli {:git/url "" :sha "2bd749f7148e28a379f1e628a32188e7f6cf0bc4"}
borkdude/edamame {:git/url "" :sha "64c7eb43950eb500ba7429dded48257cd15355ae"}}}
$ cat src/edamalli/core.clj
(ns edamalli.core
(:require [edamame.core :as e]
[malli.core :as m]
[malli.transform :as mt]))
(defrecord WrappedNum [obj loc])
(defn postprocess [{:keys [:obj :loc]}]
(if (number? obj) (->WrappedNum obj loc) obj))
(defn -main [& args]
(prn (e/parse-string "[:foo 42]" {:postprocess postprocess}))
;; TODO:
;; - validate that the WrappedNum contains value < 42
;; - then transform it to only that number
;; - else raise error, printing the location metadata of that number
)
$ clojure -m edamalli.core
[:foo #edamalli.core.WrappedNum{:obj 42, :loc {:row 1, :col 7, :end-row 1, :end-col 9}}]
@ikitommi I am parsing a schema into a malli-schema. The original might contain recursive references to other "entities" in the schema. I do not know this up front. Are there any trade-offs in putting all my potential recursive entity references in a [:ref ], even if they turn out not to be?
@borkdude would [:foo 42]
be transformed to [:foo 42]
, as would [:foo :bar #{42}]
to itself and {:a 41}
would fail on the fact that there was a number that was not 42?
@ikitommi No, [:foo (WrappedNum. x y)]
would be transformed to [:foo x]
only if x < 42, else error with explain using y
I just want to feed this data to malli and not intertwine parsing data from text to sexprs with malli validation
The use case for this is: normally edamame doesn't let you have location metadata for numbers and strings, but using a wrapped value you can have that
so I want to use malli like normally, but just use the location metadata in the wrapped value for reporting errors and discard it if the value is valid
yes, this is kinda tricky with current malli, as there is not yet a parsing api, like conform.
also, there is no custom overridable validator, so one needs to describe the given data structure (here, a tuple with keyword and a record).
ok, so one would write a schema using the records and if everything's ok, then postwalk yourself, unwrapping them?
but, something like this:
;; schemas
(def <42 [:and int? [:< 42]])
(def Schema [:tuple keyword? [:map {:encode/success :obj, :encode/failure :loc} [:obj <42]]])
;; validator and encoders for both success & failure
(def valid? (m/validator Schema))
(def success (m/encoder Schema (mt/transformer {:name :success})))
(def failure (m/encoder Schema (mt/transformer {:name :failure})))
;; in action
(defn parse-validate-and-transform [s]
(let [x (e/parse-string s {:postprocess postprocess})]
(if (valid? x) (success x) (failure x))))
=>
(parse-validate-and-transform "[:foo 41]")
; => [:foo 41]
(parse-validate-and-transform "[:foo 42]")
; => [:foo {:row 1, :col 7, :end-row 1, :end-col 9}]
yes, postwalk would do. or a recursive schema definition. if the wrapped records can be anywhere
$ clojure -m edamalli.core
Execution error (ExceptionInfo) at malli.core/-fail! (core.cljc:76).
:sci-not-available {:code ":obj"}
That was unexpected, I don't need sci in this example?(def Schema [:tuple keyword? [:map {:encode/success (fn [x] (:obj x)), :encode/failure (fn [x] (:loc x)) [:obj <42]}]])
pushed e19872273c3660fbc482dcff4c2d8439dbcbb2a6
, which should allow naked keywords as functions.
(def Schema [:tuple keyword? [:map {:encode/success :obj, :encode/failure :loc} [:obj <42]]])
(defn parse-validate-and-transform [s]
(let [x (e/parse-string s {:postprocess postprocess})]
(if (valid? x)
(prn "SUCCESS" (success x))
(prn "ERROR" (failure x)))))
$ clojure -m edamalli.core
"ERROR" [:foo {:row 1, :col 7, :end-row 1, :end-col 9}]
haha, when I do this:
:encode/failure {:message "should be lower than 42"}
I get:
$ clojure -m edamalli.core
Execution error (ExceptionInfo) at sci.impl.utils/throw-error-with-location (utils.cljc:54).
Could not resolve symbol: should [at line 1, column 1]
I have no idea what I'm doing, since I don't know these APIs well. I'll take a look after work again some time
@ikitommi I now have this:
(def Schema [:tuple keyword? [:map {:encode/success :obj, :encode/failure {:message "should be lower than 42"}} [:obj <42]]])
Output:
"eval!" "should be lower than 42"
Execution error (ExceptionInfo) at malli.core/-fail! (core.cljc:76).
:sci-not-available {:code "should be lower than 42"}
my bad. but this kinda works (but is bad, should be better when we have the parsing api):
➜ ~ clojure -Sdeps '{:deps {metosin/malli {:sha "230b1767729aad3e02568f1320855e2b45d2d9b5", :git/url ""}, borkdude/edamame {:sha "64c7eb43950eb500ba7429dded48257cd15355ae", :git/url ""}}}'
Checking out: at 230b1767729aad3e02568f1320855e2b45d2d9b5
Clojure 1.10.1
user=> (ns edamalli.core
(:require [edamame.core :as e]
[malli.core :as m]
[malli.transform :as mt]))
(defrecord WrappedNum [obj loc])
(defn postprocess [{:keys [:obj :loc]}]
(if (number? obj) (->WrappedNum obj loc) obj))
(defn fail! [{:keys [:obj :loc]}]
(throw (ex-info (str "so bad " obj "/" loc) {})))
(def <42 [:and int? [:< 42]])
(def Schema [:tuple keyword? [:map {:encode/success :obj,
:encode/failure fail!} [:obj <42]]])
(def valid? (m/validator Schema))
(def success (m/encoder Schema (mt/transformer {:name :success})))
(def failure (m/encoder Schema (mt/transformer {:name :failure})))
(defn parse-validate-and-transform [s]
(let [x (e/parse-string s {:postprocess postprocess})]
(if (valid? x) (success x) (failure x))))
edamalli.core=> (parse-validate-and-transform "[:foo 41]")
[:foo 41]
edamalli.core=> (parse-validate-and-transform "[:foo 42]")
Execution error (ExceptionInfo) at edamalli.core/fail! (REPL:2).
so bad 42/{:row 1, :col 7, :end-row 1, :end-col 9}
@zclj there is a small (have not measured) penalty for using ref-schemas, one function hop basically as the values are memoized.
ok, that's fine since I have to do something to solve it anyway, by post-walking or such. Doing it up-front with malli ref considerable make the design simpler. Thanks for the info!
rollback on the perf info @zclj . Validation & explain perf is about the same but transforming values is potentially much slower. Why? Malli can't optimize over :ref
s. Transformation behind :ref
could be no-op, but it won't be removed as it's wrapped in a function. Still, orders of magnitude faster than with spec(-tools)
thanks for the update! In my use-case I will also do generation from the schema, where I will blow the stack if I don't use :ref for recursive references. Are there any implications for using :ref for potentially non-recursive entities in that case?
rollback on the perf info @zclj . Validation & explain perf is about the same but transforming values is potentially much slower. Why? Malli can't optimize over :ref
s. Transformation behind :ref
could be no-op, but it won't be removed as it's wrapped in a function. Still, orders of magnitude faster than with spec(-tools)