Fork me on GitHub
#malli
<
2021-07-28
>
greg01:07:33

(def Schema (m/schema [:map
                        [:y {:limit 20} int?]]))
Given the above schema schema: 1. How to access properties of :y? I tried (m/properties (mu/get Schema :y)) but it returns nil (because (mu/get Schema :y) returns :int and (m/properties int?) ) 2. How to update :x to double it, e.g by passing #(* % 2)? m/update combined with m/update-properties doesn't work for the same reason as above. 3. How to convert [:y {:limit 20} int?] to [:y {:limit 20} [:and int? [:> 0]] without loosing properties.
(mu/update Schema :y (fn [s] [:and s [:> 0]])) ;; => [:map [:y [:and int? [:> 0]]]]
                                      ; I expected:  [:map [:y {:limit 20} [:and int? [:> 0]]]]
Calling the update removes the original properties. Some workaround I think would be writing custom get & update based on m/children. Doesn't look a difficult taks, just thought about asking before reinventing a wheel 🙂

Tuomas08:07:41

I had a similar issue and went with an approach like

(def Schema (m/schema [:map
                       [:y [int? {:limit 20}]]]))
(m/validate Schema {:y "1"})
(m/validate Schema {:y 1})
(m/properties (mu/get Schema :y))
But I'm a novice and not really sure how this should be done

greg10:07:52

I think it would work for some cases, but it might be not a good idea to store child-owned props at schema level. Let's say I got this Schema

(def Schema (m/schema [:map
                       [:x number?]
                       [:y [number? {:default 0}]])
If I do the simple :y update to ensure its >= 0 I will get this:
(-> Schema
    (mu/update :y (fn [s] [:and s [:>= 0]])))
; => => [:map 
         [:x number?] 
         [:y [:and [number? {:default 0}] [:>= 0]]]]
And I think this kind of properties, to work properly, they need to be kept in top level of schema for given child, e.g.:
[:map 
 [:x number?] 
 [:y [:and {:default 0} number? [:>= 0]]]
So to make this work you need both: • moving props from child (`[:y]`) to schema (`[number?]`) • adding custom update anyway (to move properties from original schema (`[number?]`) to a new enclosing schema (`[:and]`) But when doing this properties maneuver, how do you know which are owned by child and which by schema? You don't unless you explicitly specify it e.g. in update method. Conceptually I think it makes more sense to keep child-owned properties at child level, and schema-owned properties at schema level.

👍 3
greg10:07:32

Actually I went this path, and I implemented this custom update for properties manouver when updating a schema (e.g. moving from [number?] to [:and] ), but as I wrote, I don't think it is good idea. As I think now, it really makes more sense to just add a few new utility methods: get-child, get-child-props, update-child, update-child-props, update-child-schema. It is a bit weird that child properties disappear on child's schema update. It might be a bug. Who is John Galt? :man-shrugging:

greg11:07:02

I wrote a few utils fns to sort out my problems with manipulating map schemas:

(defn get-child [s k] (->> (m/children s) (filter #(= k (first %))) (first)))
(defn get-child-props [s k] (-> (get-child s k) second))
(defn get-child-schema [s k] (-> (get-child s k) (nth 2)))

(defn update-child [s key f & args]
  (let [new-children (->> (m/children s)
                          (map (fn [child]
                                 (let [[k _p _v] child]
                                   (if (= key k) (apply f child args) child)))))]
    (m/into-schema (m/type s) (m/properties s) new-children)))

(defn update-child-props [s k f & args]
  (update-child s k (fn [c]
                      (let [[k p v] c]
                        [k (apply f p args) v]))))

(defn update-child-schema [s k f & args]
  (update-child s k (fn [c]
                      (let [[k p v] c]
                        [k p (apply f v args)]))))

(comment
 (def Schema (m/schema [:map [:y {:default 20} int?]]))
 (get-child-schema Schema :y)                             ;; => int?
 (get-child-props Schema :y)                              ;; => {:default 20}
 ;; updates of child props
 (update-child-props Schema :y update :default dec)       ;; => [:map [:y {:default 19} int?]]
 (update-child-props Schema :y assoc :limit 50)           ;; => [:map [:y {:default 20, :limit 50} int?]]
 ;; update of child schema preserving child props
 (update-child-schema Schema :y (fn [s] [:and s [:> 0]])) ;; => [:map [:y {:default 20} [:and int? [:> 0]]]]
 ;; update of child schema removing the props
 (mu/update Schema :y (fn [s] [:and s [:> 0]])))          ;; => [:map [:y [:and int? [:> 0]]]]
what do you think?

👍 3
ikitommi13:07:57

Looks good. Few comments: • mu/find gets the entry:

(def Schema (m/schema [:map [:y {:limit 20} int?]]))

(mu/find Schema :y)
; => [:y {:limit 20} int?]
• when calling m/into-schema you should always use m/parent instead of m/type as first argument - m/type makes a registry lookup, m/parent points directly to the IntoSchema instance that created the schema. Faster AND adds support for non-registered schemas, e.g.
(def Over6
  (m/-simple-schema
    {:type :user/over6 ;; dummy-type, not registered
     :pred #(and (int? %) (> % 6))
     :type-properties {:error/message "should be over 6"
                       :decode/string mt/-string->long
                       :json-schema/type "integer"
                       :json-schema/format "int64"
                       :json-schema/minimum 6
                       :gen/gen generate-over6}}))

(= Over6 (m/parent [Over6 {:json-schema/example 42}]))
; => true
• for now, we could have a mu/-update-entry-properties helper, PR welcome • for future (1.0.0), the entry design should be revisited, not happy how it is now

greg14:07:47

thanks a lot for the comments, I'll prepare a PR when I find spare time

Ben Sless10:07:27

Figured out today a decoder can be used to reject wrong values: {:compile (fn [schema _] (let [s (set (m/children schema))] (fn [v] (s v))))}

ikitommi13:07:18

bit like mt/strip-extra-keys-transformer 😉

Ben Sless08:07:09

Similar, but just "forces" invalid enum values to be nil.

Ben Sless10:07:46

Sort of coercion

👍 6
greg11:07:02
replied to a thread: (def Schema (m/schema [:map [:y {:limit 20} int?]])) Given the above schema schema: 1. How to access properties of `:y`? _I tried `(m/properties (mu/get Schema :y))` but it returns `nil` (because `(mu/get Schema :y)` returns `:int` and `(m/properties int?)` )_ 2. How to update :x to double it, e.g by passing `#(* % 2)`? _m/update combined with m/update-properties doesn't work for the same reason as above._ 3. How to convert `[:y {:limit 20} int?]` to `[:y {:limit 20} [:and int? [:&gt; 0]]` without loosing properties. (mu/update Schema :y (fn [s] [:and s [:&gt; 0]])) ;; =&gt; [:map [:y [:and int? [:&gt; 0]]]] ; I expected: [:map [:y {:limit 20} [:and int? [:&gt; 0]]]] Calling the update removes the original properties. Some workaround I think would be writing custom `get` & `update` based on `m/children`. Doesn't look a difficult taks, just thought about asking before reinventing a wheel :slightly_smiling_face:

I wrote a few utils fns to sort out my problems with manipulating map schemas:

(defn get-child [s k] (->> (m/children s) (filter #(= k (first %))) (first)))
(defn get-child-props [s k] (-> (get-child s k) second))
(defn get-child-schema [s k] (-> (get-child s k) (nth 2)))

(defn update-child [s key f & args]
  (let [new-children (->> (m/children s)
                          (map (fn [child]
                                 (let [[k _p _v] child]
                                   (if (= key k) (apply f child args) child)))))]
    (m/into-schema (m/type s) (m/properties s) new-children)))

(defn update-child-props [s k f & args]
  (update-child s k (fn [c]
                      (let [[k p v] c]
                        [k (apply f p args) v]))))

(defn update-child-schema [s k f & args]
  (update-child s k (fn [c]
                      (let [[k p v] c]
                        [k p (apply f v args)]))))

(comment
 (def Schema (m/schema [:map [:y {:default 20} int?]]))
 (get-child-schema Schema :y)                             ;; => int?
 (get-child-props Schema :y)                              ;; => {:default 20}
 ;; updates of child props
 (update-child-props Schema :y update :default dec)       ;; => [:map [:y {:default 19} int?]]
 (update-child-props Schema :y assoc :limit 50)           ;; => [:map [:y {:default 20, :limit 50} int?]]
 ;; update of child schema preserving child props
 (update-child-schema Schema :y (fn [s] [:and s [:> 0]])) ;; => [:map [:y {:default 20} [:and int? [:> 0]]]]
 ;; update of child schema removing the props
 (mu/update Schema :y (fn [s] [:and s [:> 0]])))          ;; => [:map [:y [:and int? [:> 0]]]]
what do you think?

👍 3
ikitommi13:07:57
replied to a thread: (def Schema (m/schema [:map [:y {:limit 20} int?]])) Given the above schema schema: 1. How to access properties of `:y`? _I tried `(m/properties (mu/get Schema :y))` but it returns `nil` (because `(mu/get Schema :y)` returns `:int` and `(m/properties int?)` )_ 2. How to update :x to double it, e.g by passing `#(* % 2)`? _m/update combined with m/update-properties doesn't work for the same reason as above._ 3. How to convert `[:y {:limit 20} int?]` to `[:y {:limit 20} [:and int? [:&gt; 0]]` without loosing properties. (mu/update Schema :y (fn [s] [:and s [:&gt; 0]])) ;; =&gt; [:map [:y [:and int? [:&gt; 0]]]] ; I expected: [:map [:y {:limit 20} [:and int? [:&gt; 0]]]] Calling the update removes the original properties. Some workaround I think would be writing custom `get` & `update` based on `m/children`. Doesn't look a difficult taks, just thought about asking before reinventing a wheel :slightly_smiling_face:

Looks good. Few comments: • mu/find gets the entry:

(def Schema (m/schema [:map [:y {:limit 20} int?]]))

(mu/find Schema :y)
; => [:y {:limit 20} int?]
• when calling m/into-schema you should always use m/parent instead of m/type as first argument - m/type makes a registry lookup, m/parent points directly to the IntoSchema instance that created the schema. Faster AND adds support for non-registered schemas, e.g.
(def Over6
  (m/-simple-schema
    {:type :user/over6 ;; dummy-type, not registered
     :pred #(and (int? %) (> % 6))
     :type-properties {:error/message "should be over 6"
                       :decode/string mt/-string->long
                       :json-schema/type "integer"
                       :json-schema/format "int64"
                       :json-schema/minimum 6
                       :gen/gen generate-over6}}))

(= Over6 (m/parent [Over6 {:json-schema/example 42}]))
; => true
• for now, we could have a mu/-update-entry-properties helper, PR welcome • for future (1.0.0), the entry design should be revisited, not happy how it is now