Improve message from schema errors in plugins (#8865)

This commit is contained in:
Alonso Torres 2026-04-08 16:14:51 +02:00 committed by GitHub
parent e8e7900911
commit 6ce2aadfae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 134 additions and 97 deletions

View File

@ -13,3 +13,10 @@
unit tests or backend code for logs or error messages."
[key & _args]
key)
(defn c
"This function will be monkeypatched at runtime with the real function in frontend i18n.
Here it just returns the key passed as argument. This way the result can be used in
unit tests or backend code for logs or error messages."
[x]
x)

View File

@ -0,0 +1,105 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.common.schema.messages
(:require
[app.common.data :as d]
[app.common.i18n :as i18n :refer [tr]]
[app.common.schema :as sm]
[malli.core :as m]))
;; --- Handlers Helpers
(defn- translate-code
[code]
(if (vector? code)
(tr (nth code 0) (i18n/c (nth code 1)))
(tr code)))
(defn- handle-error-fn
[props problem]
(let [v-fn (:error/fn props)
result (v-fn problem)]
(if (string? result)
{:message result}
{:message (or (some-> (get result :code)
(translate-code))
(get result :message)
(tr "errors.invalid-data"))})))
(defn- handle-error-message
[props]
{:message (get props :error/message)})
(defn- handle-error-code
[props]
(let [code (get props :error/code)]
{:message (translate-code code)}))
(defn interpret-schema-problem
[acc {:keys [schema in value type] :as problem}]
(let [props (m/properties schema)
tprops (m/type-properties schema)
field (or (:error/field props)
in)
field (if (vector? field)
field
[field])]
(if (and (= 1 (count field))
(contains? acc (first field)))
acc
(cond
(or (nil? field)
(empty? field))
acc
(or (= type :malli.core/missing-key)
(nil? value))
(assoc-in acc field {:message (tr "errors.field-missing")})
;; --- CHECK on schema props
(contains? props :error/fn)
(assoc-in acc field (handle-error-fn props problem))
(contains? props :error/message)
(assoc-in acc field (handle-error-message props))
(contains? props :error/code)
(assoc-in acc field (handle-error-code props))
;; --- CHECK on type props
(contains? tprops :error/fn)
(assoc-in acc field (handle-error-fn tprops problem))
(contains? tprops :error/message)
(assoc-in acc field (handle-error-message tprops))
(contains? tprops :error/code)
(assoc-in acc field (handle-error-code tprops))
:else
(assoc-in acc field {:message (tr "errors.invalid-data")})))))
(defn- apply-validators
[validators state errors]
(reduce (fn [errors validator-fn]
(merge errors (validator-fn errors (:data state))))
errors
validators))
(defn collect-schema-errors
[schema validators state]
(let [explain (sm/explain schema (:data state))
errors (->> (reduce interpret-schema-problem {} (:errors explain))
(apply-validators validators state))]
(-> (:errors state)
(merge errors)
(d/without-nils)
(not-empty))))

View File

@ -9,14 +9,17 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.i18n :as i18n :refer [tr]]
[app.common.schema :as sm]
[app.common.schema.messages :as csm]
[app.common.types.component :as ctk]
[app.common.types.container :as ctn]
[app.common.types.file :as ctf]
[app.common.types.tokens-lib :as ctob]
[app.main.data.helpers :as dsh]
[app.main.store :as st]
[app.util.object :as obj]))
[app.util.object :as obj]
[cuerdas.core :as str]))
(defn locate-file
[id]
@ -262,6 +265,15 @@
(let [s (set values)]
(if (= (count s) 1) (first s) "mixed")))
(defn error-messages
[explain]
(->> (:errors explain)
(reduce csm/interpret-schema-problem {})
(mapcat (comp seq val))
(map (fn [[field {:keys [message]}]]
(tr "plugins.validation.message" (name field) message)))
(str/join ". ")))
(defn handle-error
"Function to be used in plugin proxies methods to handle errors and print a readable
message to the console."
@ -269,7 +281,9 @@
(fn [cause]
(let [message
(if-let [explain (-> cause ex-data ::sm/explain)]
(sm/humanize-explain explain)
(do
(js/console.error (sm/humanize-explain explain))
(error-messages explain))
(ex-data cause))]
(js/console.log (.-stack cause))
(not-valid plugin-id :error message))))

View File

@ -10,84 +10,10 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.schema :as sm]
[app.util.i18n :as i18n :refer [tr]]
[app.common.schema.messages :as csm]
[cuerdas.core :as str]
[malli.core :as m]
[rumext.v2 :as mf]))
;; --- Handlers Helpers
(defn- translate-code
[code]
(if (vector? code)
(tr (nth code 0) (i18n/c (nth code 1)))
(tr code)))
(defn- handle-error-fn
[props problem]
(let [v-fn (:error/fn props)
result (v-fn problem)]
(if (string? result)
{:message result}
{:message (or (some-> (get result :code)
(translate-code))
(get result :message)
(tr "errors.invalid-data"))})))
(defn- handle-error-message
[props]
{:message (get props :error/message)})
(defn- handle-error-code
[props]
(let [code (get props :error/code)]
{:message (translate-code code)}))
(defn- interpret-schema-problem
[acc {:keys [schema in value type] :as problem}]
(let [props (m/properties schema)
tprops (m/type-properties schema)
field (or (:error/field props)
in)
field (if (vector? field)
field
[field])]
(if (and (= 1 (count field))
(contains? acc (first field)))
acc
(cond
(or (nil? field)
(empty? field))
acc
(or (= type :malli.core/missing-key)
(nil? value))
(assoc-in acc field {:message (tr "errors.field-missing")})
;; --- CHECK on schema props
(contains? props :error/fn)
(assoc-in acc field (handle-error-fn props problem))
(contains? props :error/message)
(assoc-in acc field (handle-error-message props))
(contains? props :error/code)
(assoc-in acc field (handle-error-code props))
;; --- CHECK on type props
(contains? tprops :error/fn)
(assoc-in acc field (handle-error-fn tprops problem))
(contains? tprops :error/message)
(assoc-in acc field (handle-error-message tprops))
(contains? tprops :error/code)
(assoc-in acc field (handle-error-code tprops))
:else
(assoc-in acc field {:message (tr "errors.invalid-data")})))))
(defn- use-rerender-fn
[]
(let [state (mf/useState 0)
@ -97,24 +23,6 @@
(fn []
(render-fn inc)))))
(defn- apply-validators
[validators state errors]
(reduce (fn [errors validator-fn]
(merge errors (validator-fn errors (:data state))))
errors
validators))
(defn- collect-schema-errors
[schema validators state]
(let [explain (sm/explain schema (:data state))
errors (->> (reduce interpret-schema-problem {} (:errors explain))
(apply-validators validators state))]
(-> (:errors state)
(merge errors)
(d/without-nils)
(not-empty))))
(defn- wrap-update-schema-fn
[f {:keys [schema validators]}]
(fn [& args]
@ -124,7 +32,7 @@
errors
(when-not valid?
(collect-schema-errors schema validators state))
(csm/collect-schema-errors schema validators state))
extra-errors
(not-empty (:extra-errors state))]
@ -136,7 +44,6 @@
(not extra-errors)
valid?)))))
(defn- make-initial-state
[initial-data]
(let [initial (if (fn? initial-data) (initial-data) initial-data)

View File

@ -216,3 +216,4 @@
;; We set the real translation function in the common i18n namespace,
;; so that when common code calls (tr ...) it uses this function.
(set! app.common.i18n/tr tr)
(set! app.common.i18n/c c)

View File

@ -8931,3 +8931,6 @@ msgstr "Troubleshooting guide"
#, unused
msgid "workspace.viewport.click-to-close-path"
msgstr "Click to close the path"
msgid "plugins.validation.message"
msgstr "Field %s is invalid: %s"