This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2019-11-24
Channels
- # announcements (11)
- # beginners (72)
- # calva (11)
- # cider (12)
- # clj-kondo (147)
- # clojure (6)
- # clojure-new-zealand (2)
- # clojuredesign-podcast (2)
- # clojurescript (36)
- # cursive (2)
- # datomic (5)
- # emacs (4)
- # fulcro (57)
- # graalvm (104)
- # graphql (2)
- # jobs (1)
- # joker (1)
- # kaocha (3)
- # malli (51)
- # off-topic (2)
- # portkey (1)
- # reagent (18)
- # shadow-cljs (26)
- # spacemacs (7)
- # tools-deps (5)
- # vim (4)
about closed/open schemas, comments welcome on this: https://github.com/metosin/malli/issues/31#issuecomment-557892061
Hey @ikitommi thanks for your response on https://github.com/metosin/malli/pull/119
This feature is currently blocking me, and I'm relatively familiar with malli's code at this point (lots of tracing to figure out how to implement the above - if you had ideas on what you were thinking in terms of implementation for #120 I could potentially submit a PR
One possible solution that comes to mind is to just extend the Schema
protocol to have a -parent
method and then invoke things as appropriate
Also, regarding the public m/parent
function, perhaps we could also specify passing in a name to traverse up parents until one with the provided name is found
eg (m/parent schema :map)
@rschmukler I think the -parent
is the way to go, but it might need some bigger refactoring on internals: when a Schema is created, the -father
is not yet available, as we are creating the childs from the mothers âconstructorâ, so it needs to be a Delay
or similar value. Deref
on that while creating a child would either return nil
or block indefinitely, depending on the implementation. So, would have to see the code after the change to know if thatâs a good value for adding the (potential) complexity in.
@ikitommi I initially went down that path but then I had to do the key tracking to invoke the child schemas appropriately
I was actually wondering if we might be better of implementing the coercions as a postwalk instead of a prewalk
(m/decode
[:map {:decode/my-thing (fn [schema opts] ...do...thing...)} [:a int?] [:b int?]]
{"a_kikka" 12, "b_kikka" 32}
(mt/transformer {:name :my-thing))
The issue becomes that if the map renames a key, the child schemas don't get mapped
(because the map now has a new key at a different path, and the transformers for the values are resolved via key, after the key is renamed
(Hence why I potentially suggest doing a postwalk instead of a prewalk for coercions) - is there any reason that it's currently implemented as a prewalk?
@ikitommi I have another PR that I'm going to submit that might be a simpler solution for what I'm trying to achieve - feel free to reject it
It has to be prewalk, good example:
(m/decode
[:multi {:dispatch :type
:decode/string '(constantly #(update % :type keyword))}
[:sized [:map [:type [:= :sized] [:size int?]]]
[:human [:map [:type [:= :human]] [:name string?] [:address [:map [:country keyword?]]]]]]
{:type "human"
:name "Tiina"
:age "98"
:address {:country "finland"
:street "this is an extra key"}}
(mt/transformer mt/strip-extra-keys-transformer mt/string-transformer))
;{:type :human
; :name "Tiina"
; :address {:country :finland}}
there will be two-way transformers using interceptos, one could put all the stuff into :leave
to get postwalk
@ikitommi Interceptors is a great idea
@ikitommi are you open to applying the map transformer after the key and value transformer for the :map
schema?
ie. Ideally I'd like to be able to have:
(is (= {:x_key "true", :y 1} (m/decode schema {:x true :y "1"}
(transform/transformer
{:name :custom
:decoders
{:map
(constantly (fn [map]
(if-not (contains? map :x)
map
(-> map
(assoc :x_key (:x map))
(dissoc :x)))))
'boolean? (constantly str)}}))))
Actually this feels inconsistent - perhaps just implementing the interceptors is the way to go
@rschmukler what is your schema in the example?
[:map [:x boolean? :y int?]]
(although lets assume that I composed with transformer/json-transformer
so that the number decodes correctly
I realize that I can always pop an escape hatch and write a :custom/decode
function but since I want to apply it on every map, it seems like a transformer is the idiomatic way to go
I think interceptors solve everything so I'm trying my hand at implementing them right now
decode should end up in valid values against the schema (eg. JSON->Schema), encode is Schema->JSON.
That's a buggy example haha
(mine above)
It is indeed an encode
But, none the less, I believe after the key is renamed during the encode, the subsequent value transformers will fail because they look up by the original key name
(ie. (if-let [entry (find m k)] (assoc m k (t (val entry)))
I believe is the code in the map schema)
Yeah, hoping to have a PR today
Right now I'm having it so that -transformer
and -value-transformer
return {:enter _ :exit _}
maps
and then I'm updating the schema impls to use those on children as needed
(defn- ->interceptor
"Utility function to convert a transformer into an interceptor. Works with transformers
that are already interceptors, as well as sequences of transformers"
[transformer]
(cond
(fn? transformer) {:enter transformer :exit nil}
(and (map? transformer)
(or (contains? transformer :enter)
(contains? transformer :exit))) transformer
(coll? transformer) (reduce
(fn [{:keys [enter exit]} {new-enter :enter new-exit :exit}]
(let [enter (if (and enter new-enter)
(comp enter new-enter)
(or enter new-enter))
exit (if (and exit new-exit)
(comp exit new-exit)
(or exit new-exit))]
{:enter enter :exit exit}))
(map ->interceptor transformer))
(nil? transformer) nil
:else (throw (ex-info "Invalid transformer. Must be a function, sequence, or interceptor map"
{:value transformer}))))
I think that solves it, converting all transformers into an interceptor, even allowing for the vectors that you included
(in 114)
that map
call should be a keep
Then, like I said, basically all Schema
impls need to be aware of :enter
and :exit
when returning their own interceptor (with :enter and :exit) themselves
awesome, looking forward to the PR. (`:exit`=> :leave
would be coherent with interceptor libs)
and then, I think encoder / decoder / encode / decode just need to call (comp exit enter)
Ah, yes, will rename the key, thank you!
Do you want me to rename the -transformer
and -value-transformer
fns to -interceptor
and -value-interceptor
?
(no opinions here)