malli

https://github.com/metosin/malli :malli:
ikitommi 2020-09-20T09:48:31.022900Z

@pithyless good point about composition of things. Not sure myself what is a best practise with everything. Thinking aloud the ways to do things and when they should (or not) be used: Advanced usage: 1. implementing IntoSchema is the last effort in doing things, e.g. adding a new :regal/regex type, which has custom explain, transform etc. 2. using -simple-schema is helper for 1 (but only for leaf-schemas). Basic usage: using schemas as data should be the common way of doing & composing things. One can set transformation rules, humanized error messages, json schema mappings etc. as schema properties. Two options for composing things: 1. Vars. just say (def Over6 (m/schema [:int {:min 6, :description "should be over 6}])) and use the Var. Caveat: inlines the forms: (m/form [:and int? Over6]) ; => [:and int? [:int {:min 6, :description "should be over 6"}]], making large schema hard to read 2. Via registry:, e.g. {"Over6" [:int {:min 6}]} , keeps the reference visible: (m/form [:and int? "Over6"]) ; => [:and int? "Over6"] keeping the schema forms clean too

ikitommi 2020-09-20T09:56:18.024900Z

added support for m/type-properties -based transformations too. And a way for -m/simple-schema to create the Schema Instances based on actual Schema instance properties.

ikitommi 2020-09-20T09:59:13.026700Z

Not sure how useful this is, but as the malli lifecycle already supports this, just made it easy to use:

(testing "with instance-based type-properties"
  (let [Over (m/-simple-schema
               (fn [{:keys [value]} _]
                 (assert (int? value))
                 {:type :user/over
                  :pred #(and (int? %) (> % value))
                  :type-properties {:error/message (str "should be over " value)
                                    :decode/string mt/-string->long
                                    :json-schema/type "integer"
                                    :json-schema/format "int64"
                                    :json-schema/minimum value}}))]

    (testing "over6"
      (let [schema [Over {:value 6}]]
        (testing "form"
          (is (= [:user/over {:value 6}] (m/form schema))))
        (testing "validation"
          (is (false? (m/validate schema 6)))
          (is (true? (m/validate schema 7))))
        (testing "properties"
          (is (= {:error/message "should be over 6"
                  :decode/string mt/-string->long
                  :json-schema/type "integer"
                  :json-schema/format "int64"
                  :json-schema/minimum 6}
                 (m/type-properties schema)))
          (is (= {:value 6}
                 (m/properties schema))))))

    (testing "over42"
      (let [schema [Over {:value 42}]]
        (testing "form"
          (is (= [:user/over {:value 42}] (m/form schema))))
        (testing "validation"
          (is (false? (m/validate schema 42)))
          (is (true? (m/validate schema 43))))
        (testing "properties"
          (is (= {:error/message "should be over 42"
                  :decode/string mt/-string->long
                  :json-schema/type "integer"
                  :json-schema/format "int64"
                  :json-schema/minimum 42}
                 (m/type-properties schema)))
          (is (= {:value 42}
                 (m/properties schema))))))))

ikitommi 2020-09-20T09:59:32.027100Z

🍺 to the first who finds a valid use case for this.

ikitommi 2020-09-20T10:01:05.028400Z

so m/-simple-schema taks either a props map or a function of properties children => props so the props (including :type-properties) can be derived from schema properties.

schmee 2020-09-20T16:51:14.029900Z

I’m trying out Malli for the first time:

(def schema [:map-of int? uuid?])
(def m {"0" "2ac307dc-4ec8-4046-9b7e-57716b7ecfd2"
        "1" "820e5003-6fff-480b-9e2b-ec3cdc5d2f78"})
(m/decode schema m mt/json-transformer)
user=> {"0" #uuid "2ac307dc-4ec8-4046-9b7e-57716b7ecfd2"
        "1" #uuid "820e5003-6fff-480b-9e2b-ec3cdc5d2f78"}
I expected that this code would convert the keys to ints, can I modify the code in some way to make that happen?

ikitommi 2020-09-20T16:55:57.032500Z

hi @schmee. The mt/json-transformer doesn’t transform ints from strings as ints can be presented in JSON. But - actually, this only applies to values, so I think the keys should still be converted (as all keys are strings in JSON. Could you write an issue out of this?

ikitommi 2020-09-20T16:56:22.033100Z

to make it work today, you can use mt/string-transformer which covers that out-of-the-box.

schmee 2020-09-20T16:56:48.033500Z

perfect, thanks! I’ll make an issue :thumbsup:

schmee 2020-09-20T16:58:50.034200Z

https://github.com/metosin/malli/issues/259

ikitommi 2020-09-20T16:58:57.034700Z

re-wrote m/-predicate-schema, `m/-partial-predicate-schema` and `m/-leaf-schema` using m/-simple-schema. -39 loc.

schmee 2020-09-20T16:59:13.035200Z

feel free to rename the issue to something that makes more sense to you!

ikitommi 2020-09-20T18:05:16.037Z

Actually, the fix was simple, we already had :map-of type transformation. Just needed add a optional argument to the mt/json-transformer that is the string-decoders, which are used for :map-of keys. The full impl looks like:

ikitommi 2020-09-20T18:06:05.037100Z

ikitommi 2020-09-20T18:08:27.037500Z

and now:

(deftest map-of-json-keys-transform
  (let [schema [:map-of int? uuid?]]
    (doseq [data [{:0 "2ac307dc-4ec8-4046-9b7e-57716b7ecfd2"
                   :1 "820e5003-6fff-480b-9e2b-ec3cdc5d2f78"}
                  {"0" "2ac307dc-4ec8-4046-9b7e-57716b7ecfd2"
                   "1" "820e5003-6fff-480b-9e2b-ec3cdc5d2f78"}]]

      (is (= {0 #uuid"2ac307dc-4ec8-4046-9b7e-57716b7ecfd2"
              1 #uuid"820e5003-6fff-480b-9e2b-ec3cdc5d2f78"}
             (m/decode schema data mt/json-transformer))))))

ikitommi 2020-09-20T18:09:02.037800Z

@schmee fixed in master.

🎉 1
schmee 2020-09-20T19:01:22.038500Z

haha, that is one fast fix, thanks! 😄

schmee 2020-09-20T19:01:28.038800Z

looking forward to playing around more with Malli :thumbsup:

👍 1