(ns metabase.util.malli (:refer-clojure :exclude [fn defn defn- defmethod]) (:require #?@(:clj ([metabase.util.malli.defn :as mu.defn] [metabase.util.malli.fn :as mu.fn] [net.cgrand.macrovich :as macros] [potemkin :as p])) [clojure.core :as core] [malli.core :as mc] [malli.destructure] [malli.error :as me] [malli.util :as mut] [metabase.util.i18n :as i18n]) #?(:cljs (:require-macros [metabase.util.malli]))) | |
#?(:clj (p/import-vars [mu.fn fn instrument-ns?] [mu.defn defn defn-])) | |
Pass into mu/humanize to include the value received in the error message. | (core/defn humanize-include-value [{:keys [value message]}] ;; TODO Should this be translated with more complete context? (tru "{0}, received: {1}" message (pr-str value)) (str message ", " (i18n/tru "received") ": " (pr-str value))) |
Explains a schema failure, and returns the offending value. | (core/defn explain [schema value] (-> (mc/explain schema value) (me/humanize {:wrap humanize-include-value}))) |
(def ^:private Schema [:and any? [:fn {:description "a malli schema"} mc/schema]]) | |
Schema for localized string. | (def localized-string-schema #?(:clj [:fn {:error/message "must be a localized string"} i18n/localized-string?] ;; TODO Is there a way to check if a string is being localized in CLJS, by the `ttag`? ;; The compiler seems to just inline the translated strings with no annotation or wrapping. :cljs :string)) |
Update a malli schema with an arbitrary map of properties | (metabase.util.malli/defn with {:style/indent [:form]} [mschema props] (mut/update-properties (mc/schema mschema) merge props)) |
Update a malli schema to have a :description (used by umd/describe, which is used by api docs), and a :error/fn (used by me/humanize, which is used by defendpoint). They don't have to be the same, but usually are. (with-api-error-message [:string {:min 1}] (deferred-tru "Must be a string with at least 1 character representing a User ID.")) Kondo gets confused by :refer [defn] on this, so it's referenced fully qualified. | (metabase.util.malli/defn with-api-error-message {:style/indent [:form]} ([mschema :- Schema error-message :- localized-string-schema] (with-api-error-message mschema error-message error-message)) ([mschema :- :any description-message :- localized-string-schema specific-error-message :- localized-string-schema] (mut/update-properties (mc/schema mschema) assoc ;; override generic description in api docs and :errors key in API's response :description description-message ;; override generic description in :specific-errors key in API's response :error/fn (constantly specific-error-message)))) |
Convenience for disabling [[defn]] and [[metabase.util.malli.fn/fn]] input/output schema validation. Since input/output validation is currently disabled for ClojureScript, this is a no-op. | #?(:clj (defmacro disable-enforcement {:style/indent 0} [& body] (macros/case :clj `(binding [mu.fn/*enforce* false] ~@body) :cljs `(do ~@body)))) |
Impl for [[defmethod]] for regular Clojure. Impl for [[defmethod]] for ClojureScript. | #?(:clj (defmacro -defmethod-clj [multifn dispatch-value & fn-tail] (let [dispatch-value-symb (gensym "dispatch-value-") error-context-symb (gensym "error-context-")] `(let [~dispatch-value-symb ~dispatch-value ~error-context-symb {:fn-name '~(or (some-> (resolve multifn) symbol) (symbol multifn)) :dispatch-value ~dispatch-value-symb} f# ~(mu.fn/instrumented-fn-form error-context-symb (mu.fn/parse-fn-tail fn-tail))] (.addMethod ~(vary-meta multifn assoc :tag 'clojure.lang.MultiFn) ~dispatch-value-symb f#))))) #?(:clj (defmacro -defmethod-cljs [multifn dispatch-value & fn-tail] `(core/defmethod ~multifn ~dispatch-value ~@(mu.fn/deparameterized-fn-tail (mu.fn/parse-fn-tail fn-tail))))) |
Like [[schema.core/defmethod]], but for Malli. | #?(:clj (defmacro defmethod [multifn dispatch-value & fn-tail] (macros/case :clj `(-defmethod-clj ~multifn ~dispatch-value ~@fn-tail) :cljs `(-defmethod-cljs ~multifn ~dispatch-value ~@fn-tail)))) |
Returns the value if it matches the schema, else throw an exception. | #?(:clj (defn validate-throw [schema-or-validator value] (let [is-validator? (fn? schema-or-validator)] (if-not ((if is-validator? schema-or-validator (mc/validator schema-or-validator)) value) (throw (ex-info "Value does not match schema" (when-not is-validator? {:error (explain schema-or-validator value)}))) value)))) |
Returns a new schema that is the same as map-schema, but with the key k associated with the value v. If kvs are provided, they are also associated with the schema. | (core/defn map-schema-assoc [map-schema & kvs] (if kvs (if (next kvs) (let [key (first kvs) val (first (next kvs)) ret (mut/assoc map-schema key val)] (recur ret (nnext kvs))) (throw (ex-info "map-schema-assoc expects even number of arguments after schema-map, found odd number" {}))) map-schema)) |