malli icon indicating copy to clipboard operation
malli copied to clipboard

time humanize error brings "unknown error"

Open awb99 opened this issue 1 year ago • 2 comments

I made a schema that is using malli.experimental.time. I check if one key is of either local-datetime zoned-datetime or instant. malli/validate is working as it should , explain does not give back errors, the explained error I get is this: {:exit-date ["unknown error" "unknown error" "unknown error" "unknown error"]}

This is the sourcecode to reproduce the error

(ns quanta.trade.report.roundtrip.validation
  (:require
   [tick.core :as t]
   [malli.core :as m]
   [malli.registry :as mr]
   [malli.error :as me]
   [malli.experimental.time :as time]
  ;(:import
  ; (java.time Duration Period LocalDate LocalDateTime LocalTime Instant
  ;            ZonedDateTime OffsetDateTime ZoneId OffsetTime))
  )

(def r
  (mr/composite-registry
   m/default-registry
   (mr/registry (time/schemas))))

(def above-zero 0.0000000000000001)

(def Roundtrip
  [:map
   [:asset :string]
   [:side [:enum :long :short]]
   [:qty [:double]]
   [:entry-price [:double {:min quanta.trade.report.roundtrip.validation/above-zero}]]
   [:exit-price [:double {:min quanta.trade.report.roundtrip.validation/above-zero}]]
   [:entry-date [:or :time/local-date :time/local-date-time :time/zoned-date-time :time/instant]]
   [:exit-date  [:or :time/local-date :time/local-date-time :time/zoned-date-time :time/instant]]
   [:entry-idx {:optional true} [:int]]
   [:exit-idx {:optional true} [:int]]])

(defn validate-roundtrip [rt]
  (m/validate Roundtrip rt {:registry r}))

(defn human-error-roundtrip [rt]
  (->> (m/explain Roundtrip rt {:registry r})
       (me/humanize)))

(comment

  ;; test with a roundtrip that is ok

  (def rt1 {:asset "QQQ" 
            :side :long
            :qty 1.0
            :entry-price 105.0
            :exit-price 110.0
            :entry-idx 15
            :exit-date (t/zoned-date-time)
            :entry-date (t/date)})
  
  (validate-roundtrip rt1)
  ;; => true

  (human-error-roundtrip rt1)
  ;; => nil
  

  (human-error-roundtrip
   {:asset "QQQ" :side :long
    :entry-price 105.0
    :exit-price 110.0
    :entry-date (t/instant)})
  ;; => {:qty ["missing required key"], :exit-date ["missing required key"]}

  (human-error-roundtrip
 {:asset "QQQ" :side :long
  :entry-price 105.0
  :exit-price 110.0
  :entry-idx "asdf"})
  ;; => {:qty ["missing required key"],
  ;;     :entry-date ["missing required key"],
  ;;     :exit-date ["missing required key"],
  ;;     :entry-idx ["should be an integer"]}

  (human-error-roundtrip
   {:asset "QQQ" :side :long :qty 1.0
    :entry-price 105.0
    :exit-price 110.0
    :entry-date (t/instant)
    :exit-date 3})
  ;; => {:exit-date ["unknown error" "unknown error" "unknown error" "unknown error"]}

awb99 avatar Oct 14 '24 15:10 awb99

Hi. This is unfortunate. Humanized errors do not have a multimethod to support easy extension of schema-based errors.

With current design, you need to add a property to the schema instance for this:

(defn -local-date-schema [] (-temporal-schema {:type :time/local-date :class LocalDate :type-properties {:min (. LocalDate -MIN) :max (. LocalDate -MAX), :error/message "not a valid 
LocalTime instance"}}))

(->> (m/explain (-local-date-schema) 123) (me/humanize))
; => ["not a valid LocalTime instance"]

See #1154

ikitommi avatar Dec 31 '24 09:12 ikitommi

Would you like to add to add the [:type-properties :error/message] data to the time schemas? PR most welcome on this!

ikitommi avatar Jan 01 '25 11:01 ikitommi

I can reproduce with the following code and malli 0.19.1:

(ns repro
  (:require
    [malli.core :as m]
    [malli.error :as me]
    [malli.experimental.time :as met]
    [malli.experimental.time.transform :as mett]
    [malli.registry :as mr]
    [malli.transform :as mt]))

(defn f [s]
  (let [schema :time/offset-date-time
        data   (m/decode schema s mett/time-transformer)]
    (when-not (m/validate schema data)
      (println (me/humanize (m/explain schema data))))
    data))

(mr/set-default-registry!
   (mr/composite-registry
     (m/default-schemas)
     (met/schemas)))

(f "2025-10-31T14:28:12Z")
;=> #object [java.time.OffsetDateTime 0x2dcd71e6 "2025-10-31T14:28:12Z"]
(f "2025-10-31T14:28:Z")
; [unknown error]
;=> "2025-10-31T14:28:Z"

Ideally, I would expect malli to report something like "string does conform to ISO 8601 date time extended format" -- i.e. I'd like to be able to get information about the string before transformation.

Currently I use a hand-crafted regexp to validate manually before transformation, to workaround this:

(def offset-date-time-string
  "Malli schema for ISO 8601 extended format date time"
  [:re {:error/message "should be an ISO 8601 extended format date time"}
   #"^\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d(\.\d\d\d)?([+-][0-2]\d:[0-5]\d|Z)$"])

(me/humanize (ma/explain offset-date-time-string "2025-10-31T14:28:Z"))
;=> ["should be an ISO 8601 extended format date time"]

devurandom avatar Oct 31 '25 14:10 devurandom