Fork me on GitHub
#malli
<
2021-03-04
>
borkdude13:03:26

I have the exact same problem with babashka: waiting for spec2 so not including spec1: and this can result in years of having nothing perhaps

mikethompson13:03:26

I didn't hear that podcast, but yeah, I feel like Clojure needs an outcome. We're stuck in something of a twilight zone. And this is an important issue. And right now Malli is just better. Unless there is something pending with spec ... it feels like we need permission to get on with making Malli "it"

borkdude13:03:53

In some way, no matter how good your library is, you can't compete with core since people will just believe that "core is better" no matter what you do, which isn't fair maybe. But "better" is probably not the right way to describe it: spec and malli have different approaches. spec is more of an RDF approach where each unique attribute name describes a schema

mikethompson13:03:35

I'm the same as you: should re-frame support spec or Malli. I've held off for a long time.

borkdude13:03:15

Why should re-frame have to choose here? Can't this be decided in some add-on lib? Why do you need to address this concern in re-frame itself?

borkdude13:03:47

Re-frame events are already associated with globally namespaced keywords which maybe aligns well with spec

mikethompson13:03:27

But if ever spec is ratified as the one true way for Clojure, I'd like for it to be the one true way for re-frame too

borkdude13:03:16

Maybe it can be made pluggable

juhoteperi13:03:09

Reitit supports all three so the application can choose.

juhoteperi13:03:48

Pluggable solution is probably also quite important in Cljs apps, because either Spec or Malli both add about 100KB JS to the output bundle (before gzip).

juhoteperi13:03:53

I was surprised by this recently when I checked project output report in app that uses Malli, but re-chain uses Spec internally: https://github.com/ingesolvoll/re-chain/issues/6

ikitommi15:03:50

Interesting discussion. About Malli cljs-size. The core has been developed DCE in mind, you can make a Malli bundle with just validation of strings, numbers, maps, vector and sets and it’s few kilos zipped. Currently, many extensions (humanized errors, generation, json schema etc) are implemented using multimethods, so pulling anything out of those, makes it much bigger.

ikitommi15:03:08

The Code Size Expression Problem.

ikitommi15:03:59

here’s the bare-bones malli.

ikitommi15:03:39

@mikethompson what would the spec/malli integration look like? do you need something?

juhoteperi16:03:38

And Reitit Malli coercion is built for Ring model, so it includes lots of features that are unnecessary for frontend routing. When we get around to implementing separate frontend coercion, it will drop dependency on some unused parts.

juhoteperi16:03:03

Like the json schema generation parts.

emccue16:03:40

^i'm not opposed to the json schema stuff but it does feel odd that it isn't a separate artifact

juhoteperi16:03:15

It is separate namespace on Malli. On Reitit coercion impl. the parameter validation and generating the json-schemas for routes is closely related so they are on the same module currently.

ikitommi16:03:10

yes, there should be a lite-version of reitit coercion, with just the encoding & decoding part - without throwing exceptions. Frontend would use that: simple, pure and small.

ikitommi16:03:21

malli is designed so that malli.core is the essential ns, all others are optional (ok, there is nowadays malli.impl too). It has been easier to optimize the whole as everyhing is in single repo.

arundilipan17:03:35

Hi everyone, I’m not sure if this is a bug, but I’m running into a quirk with the validation and error reporting of :catn schemas

arundilipan17:03:01

For example if you had a schema like:

[:catn 
  [:amount [:fn {:error/fn '(fn [{:keys [value]} _] (str "Received value " value ", should be > 0"))
        :decode/string malli.transform/-string->double}
    '#(> % 0)]]
  [:type [:enum "A" "B" "C"]]]

arundilipan17:03:01

If I try and (malli.core/explain schema [1.0 "D"]) , and humanize it, the error I get back is "Received value 1.0, should be > 0" , rather than "should be one of "A", "B", or "C"

borkdude17:03:47

Isn't the idea of :catn that you provide names for the schema elements?

borkdude17:03:56

Else you should just use :cat

arundilipan17:03:45

Fixed the example, I do intend to use :catn to provide names for the schema elements

ikitommi20:03:31

@arundilipan seems to work:

(-> [:catn
     [:amount [:fn {:error/fn '(fn [{:keys [value]} _] (str "Received value " value ", should be > 0"))
                    :decode/string malli.transform/-string->double}
               '#(> % 0)]]
     [:type [:enum "A" "B" "C"]]]
    (m/explain [1.0 "D"])
    (malli.error/humanize))
; => [nil ["should be either A, B or C"]]

borkdude20:03:59

should :catn not give back error messages by name?

ikitommi20:03:47

error messages by name?

borkdude20:03:10

{:amount nil :type ["should be ..."]}

arundilipan20:03:34

That’s for :map i think

borkdude20:03:54

are the positions in :cat always unambiguous? I know for spec they are certainly not with s/cat

ikitommi20:03:57

hmm. not sure. the current humanize just mimics the raw result, not parsed one.

ikitommi20:03:12

but the info is there:

ikitommi20:03:17

(-> [:catn
     [:amount [:double {:min 0}]]
     [:type [:enum "A" "B" "C"]]]
    (m/explain [1.0 "D"]))
;{:schema [:catn [:amount [:double {:min 0}]] [:type [:enum "A" "B" "C"]]],
; :value [1.0 "D"],
; :errors (#Error{:path [:type 0], :in [1], :schema [:enum "A" "B" "C"], :value "D"})}

ikitommi20:03:12

e.g. it’s a sequence, errors are positioned by :in.

borkdude20:03:23

maybe for tools like expound it would be nice to have the humanized error along with the path?

borkdude20:03:46

so using the explain output you can get to the error message by path

borkdude20:03:56

using get-in or so

ikitommi20:03:37

for map, the explain info is:

(-> [:map
     [:amount [:double {:min 0}]]
     [:type [:enum "A" "B" "C"]]]
    (m/explain {:amount 1.0
                :type "D"}))
;{:schema [:map [:amount [:double {:min 0}]] [:type [:enum "A" "B" "C"]]],
; :value {:amount 1.0, :type "D"},
; :errors (#Error{:path [:type 0], :in [:type], :schema [:enum "A" "B" "C"], :value "D"})}

ikitommi20:03:32

but yes, the branch name is available in the error data for :catn, to be printed nicely etc.

ikitommi20:03:02

@arundilipan merged a humanized error for :double, so this works now too:

(-> [:catn
     [:amount [:double {:min 0}]]
     [:type [:enum "A" "B" "C"]]]
    (m/explain [1.0 "D"])
    (malli.error/humanize))
; => => [nil ["should be either A, B or C"]] 

borkdude21:03:42

user=> (require '[malli.error :as e])
nil
user=> (require '[malli.core :as m])
nil
user=> (e/humanize (m/explain [:cat int? int? [:? int?] [:? string?]] [1 2 :foo]))
[nil nil ["should be an int" "should be a string" "unknown error"]]
Looks legit, except maybe for the "unknown error"?

borkdude21:03:47

That's probably better expressed as:

(m/explain [:cat int? int? [:orn [:x int?] [:y string?]]] [1 2 :foo])
Just wanted to see what malli would make of it

ikitommi22:03:33

good catch @borkdude, the regex error types didn’t have humanized forms. fixed in master:

(-> [:cat int? int?]
    (m/explain [1])
    (me/humanize))
; => [nil ["end of input"]]

(-> [:cat int? int?]
    (m/explain [1 2 3])
    (me/humanize))
; => [nil nil ["input remaining"]]

(-> [:cat int? int? [:? int?] [:? string?]]
    (m/explain [1 2 :foo])
    (me/humanize))
; => [nil nil ["should be an int" "should be a string" "input remaining"]]

ikitommi22:03:17

with :or:

(-> [:cat int? int? [:or int? string?]]
    (m/explain [1 2 :foo])
    (me/humanize))
; => [nil nil ["should be an int" "should be a string"]]

borkdude22:03:00

yeah, that already worked right?

👍 3