Fork me on GitHub
#malli
<
2021-03-19
>
willier03:03:39

spec->malli was mentioned here a while ago. is anyone aware if such a thing exists?

ikitommi06:03:07

@emccue currently itโ€™s a let. Could be something else too. What kind of better errors would you expect? The reference information is available over there, just not used I guess in explain:

(m/explain
  [:map {:registry {"ID" :int}}
   [:id "ID"]]
  {:id "123"})
;{:schema [:map {:registry {"ID" :int}} [:id "ID"]],
; :value {:id "123"},
; :errors (#Error{:path [:id], :in [:id], :schema :int, :value "123"})}

ikitommi06:03:23

@willier not yet. interested in doing? ๐Ÿ˜‰

willier08:03:01

hmm, probably need a black belt in macro-fu to do this job

nilern08:03:07

Or a spec-tools belt?

borkdude08:03:31

@willier you can inspect specs at runtime. spec-tools has something called a walker and coax is a library from exoscale to do coercion based on specs, I think they are doing something similar

๐Ÿ‘ 4
ikitommi08:03:01

spec-tools has both walker and a visitor ๐Ÿ™‚ visitor is the right tool for the job i believe: https://cljdoc.org/d/metosin/spec-tools/0.10.5/doc/spec-visitor

willier09:03:58

oh interesting... i will have a look into it - thanks!

ikitommi09:03:30

also, there are progression tests in spec-tools for broken forms: if the core would be fixed: https://github.com/metosin/spec-tools/blob/master/test/cljc/spec_tools/visitor_all_test.cljc

ikitommi09:03:32

oh, no more, tests disabled and the last form is fixed. great!

danielneal09:03:19

If you have a map that references other schemas, like this [:map ::some/ref ::some-other/ref] how do you make ::some/ref and ::some-other/ref optional?

danielneal09:03:50

I tried [:and {:optional true} ::some/ref] but that was invalid

nilern09:03:23

You just have to use the sugar-free [:map [::some/ref {:optional true} ::some/ref]] You could get clever and [:map (#(vector % {:optional true} %) ::some/ref)] or name and reuse that little fn

nilern09:03:57

[:map ::some/ref] is just some sugar in :map, we don't have "first-class properties" beyond that. And :optional is specific to map entries while :maybe does something else.

danielneal09:03:14

aha ok thanks ๐Ÿ™‚

ikitommi10:03:29

this should also work: [:map [::some/ref {:optional true}]], no need to repeat the schema for refs.

โ˜๏ธ 4
danielneal10:03:04

If I'm walking a schema and want to convert all schemas of type ::sq/ref into :string, but I also want to deref all other refs, how do I do this/ This is what I've got so far:

(defn output-schema
  [schema]
  (malli/walk
   schema
   (malli/schema-walker
    (fn [schema]
      (malli/type schema)
      (cond
        (= schema ::sq/ref) :string
        (= (malli/type schema) :map)
        .... do some other stuff
        :else schema)))
   {::malli/walk-schema-refs true
    ::malli/walk-refs true}))
But the schema is expanded by the time the check happens, so it fails

ikitommi11:03:48

what does (malli/walk schema (malli/schema-walker identity) {::malli/walk-schema-refs true, ::malli/walk-refs true}) do?

danielneal11:03:33

yep, that expands all

ikitommi11:03:16

that might expand all automatically

ikitommi11:03:37

(m/walk
  [:schema
   {:registry {"Country" [:map
                          [:name [:enum :FI :PO]]
                          [:neighbors [:vector [:ref "Country"]]]]
               "Burger" [:map
                         [:name string?]
                         [:description {:optional true} string?]
                         [:origin [:maybe "Country"]]
                         [:price pos-int?]]
               "OrderLine" [:map
                            [:burger "Burger"]
                            [:amount int?]]
               "Order" [:map
                        [:lines [:vector "OrderLine"]]
                        [:delivery [:map
                                    [:delivered boolean?]
                                    [:address [:map
                                               [:street string?]
                                               [:zip int?]
                                               [:country "Country"]]]]]]}}
   "Order"]
  (m/schema-walker #(mu/update-properties % assoc :type (m/type %)))
  {::m/walk-schema-refs true})
;[:schema
; {:registry {"Country" [:map [:name [:enum :FI :PO]] [:neighbors [:vector [:ref "Country"]]]],
;             "Burger" [:map
;                       [:name string?]
;                       [:description {:optional true} string?]
;                       [:origin [:maybe "Country"]]
;                       [:price pos-int?]],
;             "OrderLine" [:map [:burger "Burger"] [:amount int?]],
;             "Order" [:map
;                      [:lines [:vector "OrderLine"]]
;                      [:delivery
;                       [:map [:delivered boolean?] [:address [:map [:street string?] [:zip int?] [:country "Country"]]]]]]},
;  :type :schema}
; [:malli.core/schema
;  {:type :malli.core/schema}
;  [:map
;   {:type :map}
;   [:lines
;    [:vector
;     {:type :vector}
;     [:malli.core/schema
;      {:type :malli.core/schema}
;      [:map
;       {:type :map}
;       [:burger
;        [:malli.core/schema
;         {:type :malli.core/schema}
;         [:map
;          {:type :map}
;          [:name [string? {:type string?}]]
;          [:description {:optional true} [string? {:type string?}]]
;          [:origin
;           [:maybe
;            {:type :maybe}
;            [:malli.core/schema
;             {:type :malli.core/schema}
;             [:map
;              {:type :map}
;              [:name [:enum {:type :enum} :FI :PO]]
;              [:neighbors [:vector {:type :vector} [:ref {:type :ref} "Country"]]]]]]]
;          [:price [pos-int? {:type pos-int?}]]]]]
;       [:amount [int? {:type int?}]]]]]]
;   [:delivery
;    [:map
;     {:type :map}
;     [:delivered [boolean? {:type boolean?}]]
;     [:address
;      [:map
;       {:type :map}
;       [:street [string? {:type string?}]]
;       [:zip [int? {:type int?}]]
;       [:country
;        [:malli.core/schema
;         {:type :malli.core/schema}
;         [:map
;          {:type :map}
;          [:name [:enum {:type :enum} :FI :PO]]
;          [:neighbors [:vector {:type :vector} [:ref {:type :ref} "Country"]]]]]]]]]]]]]

ikitommi11:03:53

not sure if thatโ€™s near what you want to do.

danielneal11:03:52

Ah I think I said :type wrong, the type is :malli.core/schema, what I'm checking for is the schema itself to be ::sq/ref

danielneal11:03:25

So is what you're suggesting to do two walks, one to capture information and put it in the properties, and then a second to do the other transformations?

danielneal11:03:01

I suppose I could put some property on the ::sq/ref schema itself, but it feels like I'm missing a trick

nilern11:03:54

To do it in one traversal you would need a more Visitor-like prewalk where you manually deref the ones you want. I think that can be done with -walk and Walker but only schema-walker and walk are documented and stable ATM

danielneal11:03:59

yeah that makes sense, you kind of prewalk it and choose whether to deref or swap as you descend

ikitommi12:03:31

ok, I guess I missed the original point. But there are public walkers like https://github.com/metosin/malli/blob/master/src/malli/util.cljc#L37-L51

danielneal15:03:29

is it safe to use those functions and protocols?

nilern15:03:12

> extender api: public vars, name starts with -, e.g. malli.core/-collection-schema. Not needed with basic use cases, might evolve during the alpha, follow https://github.com/metosin/malli/blob/master/CHANGELOG.md for details

genRaiy17:03:21

doing the graphviz thing is a bit whack it turns out cos it passes through all of the constraints to be visualised and it soon gets ugly

genRaiy17:03:42

So I guess you really have to pare back everything to the simplest of all possible models for nice visuals ๐Ÿ™‚

nilern17:03:06

Maybe we could have custom visualization props like we have :error/message?

genRaiy18:03:05

yes, or like {:swagger}

genRaiy18:03:19

if one had a Member, it would be nice for example to have {:viz/parent Org} on the map and {:viz/type string} on the name property

genRaiy18:03:32

and then one could select for those properties when transforming to DOT

genRaiy21:03:47

@ikitommi I might have been going about this wrong, so maybe this is now a better explanation of what I would like to have ...

genRaiy21:03:09

(def Id [:re #"^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"])
(def Name [:string {:min 3 :max 255}])

(def org-ref :ref)
(def org-ref-viz [:map [org-ref :string]])
(def org-ref-description "Reference (name or ID) of the organisation")
(def org-ref-valid [:map {:title org-ref-description} [org-ref Name]])
(def org-ref-swagger {:swagger {:description org-ref-description
                                :example     "Acme tech, Houston TX"}})
(def Org-Ref [:map {:title (name org-ref)}
              [org-ref org-ref-swagger Name]])

(def org-id :id)
(def org-id-viz [:map [org-id :string]])
(def org-id-description "The organisation ID")
(def org-id-valid [:map [org-id Id]])
(def org-id-swagger {:swagger {:description org-id-description
                               :example     (->id)}})

(def Org-Id [:map {:title (name org-id)}
             [org-id org-id-swagger Id]])

(def org-viz {"Org" (mu/merge org-id-viz org-ref-viz)})
(def Org (mu/merge Org-Id Org-Ref))

genRaiy21:03:50

I can't find a way to create org-ref-full by combining org-ref-valid with org-ref-swagger

genRaiy21:03:50

I have added org-ref-viz as that's what I would like to derive from org-ref-valid

genRaiy21:03:33

At the moment I am OK with doing it the way as it is above cos there is still a lot of value but obviously there seems to be quite a bit of boiler-plate