Quick question about malli as I seem to struggle with reading comprehension today. When it comes to value transformations and the interceptors. Is it intended behaviour that addition to any interceptor to either enter or leave stage prevents the original value transformation from occurring? For the sake of example: I'm dealing with unreliable field in a map that I want to convert to a boolean:
(def test-schema [:map [:x :boolean]])
(m/decode test-schema {:x "false"} (mt/string-transformer))
The above works, however if the field is positive or negative int it won't. To add support for such i tried the following: (malli version 0.12.0)
(defn int->boolean
[v]
(cond
(boolean? v) v
(pos-int? v) true
(neg-int? v) false
:else v))
(def test-schema [:map [:x [:boolean {:decode/string {:leave int->boolean}}]]])
(m/decode test-schema {:x "false"} (mt/string-transformer))
Expected: m/decode returns {:x false}
actual: m/decode returns {:x "false"}
I'm reworking that example to a separate transformer now but I thought it curious that the addition of an leave or enter stage interceptor prevents the original transformer from running. Did I misunderstand the interceptors in the documentation?
edit: fix typosgood question for sure
https://github.com/metosin/malli/blob/master/src/malli/transform.cljc#L403
it looks like the interceptor from the properties overrides/replaces the one from the transformer
it's not an interceptor in the sense that it would intercept / wrap the transformer, it's wrapping the value to be transformed
if that makes sense?
if your int->boolean is a separate transformer, it does get chained, so that's the right way to go here I guess
Sorry I need a moment to process that to fully understand your comment. Specifically: > it's not an interceptor in the sense that it would intercept / wrap the transformer, it's wrapping the value to be transformed I understand the interception here is specific to that value. However I'm not really following what you mean by wrapping in this case. To me wrap implies that default functionality would be kept at some level, but it isn't and the function here never receives more than 1 argument (input value of the field after "enter" interceptor so it cannot even attempt to call the previous functionality so it would have to happen outside. Yet as you stated, addition of an interceptor like this replaces the original one. Now I'm doubting I fully understand the purposes of these interceptors at all if their usage degrades the original function. Sorry i need to re-read the documentation again and try to come to an understanding about this before I can comment more. Anyway my resolution is the separate transformer as you said. Thank you kindly for your answer and your time. Have a good working week.
I just meant it's not (your-interceptor (the-actual-transformer value)) but rather (your-interceptor value)
in other libraries/contexts, interceptors often means the former, in my experience, so I understand the confusion
To close out this discussion: the example above, when converted to a custom transformer can look like this
(defn int->boolean
[v]
(cond
(boolean? v) v
(or (= "1" v) (pos-int? v)) true
(or (= "0" v) (= 0 v) (neg-int? v)) false
:else v))
(def test-schema [:map
[::m/default
[:map-of
:keyword
[:boolean {:decode/custom-int-transformer int->boolean}]]]])
(m/decode test-schema
{:int-pos 1
:int-zero 0
:int-neg -1
:str-pos "1"
:str-zero "0"
:str-neg "-1"
:bool-true "true"
:bool-false "false"
:bool-other "other"}
(mt/transformer
mt/string-transformer
{:name :custom-int-transformer}
mt/default-value-transformer))
;; decode outputs:
{:int-pos true,
:int-zero false,
:int-neg false,
:str-pos true,
:str-zero false,
:str-neg "-1", ;; note that the negative number in string didn't transform. Not important for me
:bool-true true,
:bool-false false,
:bool-other "other"}