chore(malli)_: New map schema to reduce optional/maybe verbosity

This commit is contained in:
Icaro Motta 2024-06-20 19:10:04 -03:00
parent a92b50afea
commit 8df314b2c2
No known key found for this signature in database
GPG Key ID: 009557D9D014DF07
14 changed files with 205 additions and 119 deletions

View File

@ -6,15 +6,13 @@
[:=>
[:catn
[:props
[:map
[:full-name {:optional true} [:maybe string?]]
[:size {:optional true} [:maybe (into [:enum] (keys style/sizes))]]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
[:static? {:optional true} [:maybe boolean?]]
[:status-indicator? {:optional true} [:maybe boolean?]]
[:online? {:optional true} [:maybe boolean?]]
[:ring? {:optional true} [:maybe boolean?]]
[:profile-picture
{:optional true}
[:maybe :schema.quo/profile-picture-source]]]]]
[:schema.common/map {:optional true :maybe true}
[:full-name string?]
[:size (into [:enum] (keys style/sizes))]
[:customization-color :schema.common/customization-color]
[:static? boolean?]
[:status-indicator? boolean?]
[:online? boolean?]
[:ring? boolean?]
[:profile-picture :schema.quo/profile-picture-source]]]]
:any])

View File

@ -3,17 +3,16 @@
(def ?schema
[:=>
[:cat
[:map {:closed true}
[:accessibility-label {:optional true} [:maybe :keyword]]
[:type {:optional true} [:maybe [:enum :main :danger]]]
[:action {:optional true} [:maybe [:enum :arrow :toggle :input]]]
[:icon {:optional true} [:maybe :keyword]]
[:description {:optional true} [:maybe :string]]
[:state {:optional true} [:maybe [:enum :selected]]]
[:title {:optional true} :string]
[:on-press {:optional true} [:maybe fn?]]
[:input-props {:optional true} [:maybe :map]]
[:customization-color {:optional true}
[:maybe :schema.common/customization-color]]
[:blur? {:optional true} [:maybe :boolean]]]]
[:schema.common/map {:closed true :optional true :maybe true}
[:accessibility-label :keyword]
[:type [:enum :main :danger]]
[:action [:enum :arrow :toggle :input]]
[:icon :keyword]
[:description :string]
[:state [:enum :selected]]
[:title {:no-maybe true} :string]
[:on-press fn?]
[:input-props :map]
[:customization-color :schema.common/customization-color]
[:blur? :boolean]]]
:any])

View File

@ -20,15 +20,14 @@
[:on-options-press {:optional true} [:maybe fn?]]])
(def ^:private ?base
[:map
[:type {:optional true}
[:enum :default :tag :action :balance-neutral :balance-negative :balance-positive]]
[:state {:optional true} [:enum :default :selected :active]]
[:blur? {:optional true} [:maybe :boolean]]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
[:on-press {:optional true} [:maybe fn?]]
[:title-icon {:optional true} [:maybe :keyword]]
[:account-props
[:schema.common/map {:optional true}
[:type [:enum :default :tag :action :balance-neutral :balance-negative :balance-positive]]
[:state [:enum :default :selected :active]]
[:blur? [:maybe :boolean]]
[:customization-color [:maybe :schema.common/customization-color]]
[:on-press [:maybe fn?]]
[:title-icon [:maybe :keyword]]
[:account-props {:no-optional true}
[:map
[:name :string]
[:address :string]

View File

@ -1,11 +1,11 @@
(ns quo.components.list-items.account-list-card.schema)
(def ^:private ?base
[:map
[:action {:optional true} [:enum :icon :none]]
[:blur? {:optional true} [:maybe :boolean]]
[:on-press {:optional true} [:maybe fn?]]
[:account-props
[:schema.common/map {:optional true}
[:action [:enum :icon :none]]
[:blur? [:maybe :boolean]]
[:on-press [:maybe fn?]]
[:account-props {:no-optional true}
[:map {:closed true}
[:type [:enum :default :watch-only]]
[:name :string]
@ -13,7 +13,7 @@
[:emoji :string]
[:size {:optional true} [:enum 80 :size-64 48 32 28 24 20 16]]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]]]
[:networks {:optional true} [:* [:map [:network-name :keyword] [:short-name :string]]]]])
[:networks [:* [:map [:network-name :keyword] [:short-name :string]]]]])
(def ^:private ?on-option-press
[:map

View File

@ -13,20 +13,20 @@
[:=>
[:catn
[:props
[:map
[:default-active {:optional true} [:maybe [:or :int :keyword]]]
[:active-tab-id {:optional true} [:maybe [:or :int :keyword]]]
[:data ?data]
[:fade-end-percentage {:optional true} [:or :double :string]]
[:fade-end? {:optional true} [:maybe :boolean]]
[:blur? {:optional true} [:maybe :boolean]]
[:on-change {:optional true} [:maybe fn?]]
[:on-scroll {:optional true} [:maybe fn?]]
[:scroll-on-press? {:optional true} [:maybe :boolean]]
[:scrollable? {:optional true} [:maybe :boolean]]
[:style {:optional true} [:maybe :map]]
[:container-style {:optional true} [:maybe :map]]
[:size {:optional true} [:maybe [:or :keyword :int]]]
[:in-scroll-view? {:optional true} [:maybe :boolean]]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]]]]
[:schema.common/map {:optional true :maybe true}
[:default-active [:or :int :keyword]]
[:active-tab-id [:or :int :keyword]]
[:data {:no-maybe true} ?data]
[:fade-end-percentage {:no-maybe true} [:or :double :string]]
[:fade-end? :boolean]
[:blur? :boolean]
[:on-change fn?]
[:on-scroll fn?]
[:scroll-on-press? :boolean]
[:scrollable? :boolean]
[:style :map]
[:container-style :map]
[:size [:or :keyword :int]]
[:in-scroll-view? :boolean]
[:customization-color :schema.common/customization-color]]]]
:any])

View File

@ -4,11 +4,13 @@
[:=>
[:catn
[:props
[:map
[:options {:optional true} [:maybe [:enum :add :hold]]]
[:size {:optional true} [:maybe [:enum :size-24 :size-32]]]
[:blur? {:optional true} [:maybe :boolean]]
[:collectible-img-src :schema.common/image-source]
[:collectible-name :string]
[:collectible-id {:optional true} [:maybe :string]]]]]
[:schema.common/map {:optional true :maybe true}
[:options [:enum :add :hold]]
[:size [:enum :size-24 :size-32]]
[:blur? :boolean]
[:collectible-id :string]
[:collectible-img-src {:no-optional true :no-maybe true}
:schema.common/image-source]
[:collectible-name {:no-optional true :no-maybe true}
:string]]]]
:any])

View File

@ -1,23 +1,23 @@
(ns quo.components.wallet.account-card.schema)
(def ^:private ?base
[:map
[:type {:optional true} [:enum :default :watch-only :add-account :empty :missing-keypair]]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
[:metrics? {:optional true} [:maybe :boolean]]
[:on-press {:optional true} [:maybe fn?]]])
[:schema.common/map {:optional true :maybe true}
[:type {:no-maybe true} [:enum :default :watch-only :add-account :empty :missing-keypair]]
[:customization-color :schema.common/customization-color]
[:metrics? :boolean]
[:on-press fn?]])
(def ^:private ?amount
[:map
[:amount {:optional true} [:maybe :string]]])
(def ^:private ?card
[:map
[:balance {:optional true} [:maybe :string]]
[:loading? {:optional true} [:maybe :boolean]]
[:name {:optional true} [:maybe :string]]
[:percentage-value {:optional true} [:maybe :string]]
[:emoji {:optional true} [:maybe :string]]])
[:schema.common/map {:optional true :maybe true}
[:balance :string]
[:loading? :boolean]
[:name :string]
[:percentage-value :string]
[:emoji :string]])
(def ?schema
[:=>

View File

@ -10,11 +10,11 @@
[:keypair-name {:optional true} [:maybe :string]]])
(def ^:private ?default-keypair
[:map
[:profile-picture {:optional true} [:maybe :schema.common/image-source]]
[:derivation-path {:optional true} [:maybe :string]]
[:on-press {:optional true} [:maybe fn?]]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]])
[:schema.common/map {:optional true :maybe true}
[:profile-picture :schema.common/image-source]
[:derivation-path :string]
[:on-press fn?]
[:customization-color :schema.common/customization-color]])
(def ^:private ?recovery-phrase
[:map

View File

@ -4,15 +4,14 @@
[:=>
[:catn
[:props
[:map
[:address {:optional true} [:maybe :string]]
[:blur? {:optional true} [:maybe :boolean]]
[:format {:optional true} [:enum :short :long]]
[:networks {:optional true}
[:maybe [:sequential [:map [:network-name :keyword] [:short-name :string]]]]]
[:full-address? {:optional true} [:maybe :boolean]]
[:schema.common/map {:optional true :maybe true}
[:address :string]
[:blur? :boolean]
[:format {:no-maybe true} [:enum :short :long]]
[:networks [:sequential [:map [:network-name :keyword] [:short-name :string]]]]
[:full-address? :boolean]
;; TODO: size and weight are text schemas and should be imported here
;; https://github.com/status-im/status-mobile/issues/19443
[:size {:optional true} [:maybe :keyword]]
[:weight {:optional true} [:maybe :keyword]]]]]
[:size :keyword]
[:weight :keyword]]]]
:any])

View File

@ -4,12 +4,12 @@
[:=>
[:catn
[:props
[:map {:closed true}
[:status {:optional true} [:maybe [:enum :default :error]]]
[:on-inc-press {:optional true} [:maybe fn?]]
[:on-dec-press {:optional true} [:maybe fn?]]
[:container-style {:optional true} [:maybe :map]]
[:min-value {:optional true} [:maybe :int]]
[:max-value {:optional true} [:maybe :int]]
[:value [:maybe :int]]]]]
[:schema.common/map {:closed true :optional true :maybe true}
[:status [:enum :default :error]]
[:on-inc-press fn?]
[:on-dec-press fn?]
[:container-style :map]
[:min-value :int]
[:max-value :int]
[:value {:no-optional true} :int]]]]
:any])

View File

@ -4,13 +4,13 @@
[:=>
[:catn
[:props
[:map {:closed true}
[:type [:enum :token :collectible]]
[:amount {:optional true} [:maybe [:or :string :int]]]
[:token {:optional true} [:maybe :string]]
[:token-img-src {:optional true} [:maybe :schema.common/image-source]]
[:collectible-img-src {:optional true} [:maybe :schema.common/image-source]]
[:collectible-name {:optional true} [:maybe :string]]
[:divider? {:optional true} [:maybe :boolean]]
[:container-style {:optional true} [:maybe :map]]]]]
[:schema.common/map {:closed true :optional true :maybe true}
[:type {:no-maybe true} [:enum :token :collectible]]
[:amount [:or :string :int]]
[:token :string]
[:token-img-src :schema.common/image-source]
[:collectible-img-src :schema.common/image-source]
[:collectible-name :string]
[:divider? :boolean]
[:container-style :map]]]]
:any])

View File

@ -5,18 +5,18 @@
[:=>
[:catn
[:props
[:map
[:transaction {:optional true} [:maybe [:enum :send :swap :bridge]]]
[:first-tag {:optional true} [:maybe context-tag-schema/?schema]]
[:second-tag {:optional true} [:maybe context-tag-schema/?schema]]
[:third-tag {:optional true} [:maybe context-tag-schema/?schema]]
[:fourth-tag {:optional true} [:maybe context-tag-schema/?schema]]
[:fifth-tag {:optional true} [:maybe context-tag-schema/?schema]]
[:second-tag-prefix {:optional true} [:maybe :keyword]]
[:third-tag-prefix {:optional true} [:maybe :keyword]]
[:fourth-tag-prefix {:optional true} [:maybe :keyword]]
[:max-fees {:optional true} [:maybe :string]]
[:nonce {:optional true} [:maybe :int]]
[:input-data {:optional true} [:maybe :string]]
[:on-press {:optional true} [:maybe fn?]]]]]
[:schema.common/map {:optional true :maybe true}
[:transaction [:enum :send :swap :bridge]]
[:first-tag context-tag-schema/?schema]
[:second-tag context-tag-schema/?schema]
[:third-tag context-tag-schema/?schema]
[:fourth-tag context-tag-schema/?schema]
[:fifth-tag context-tag-schema/?schema]
[:second-tag-prefix :keyword]
[:third-tag-prefix :keyword]
[:fourth-tag-prefix :keyword]
[:max-fees :string]
[:nonce :int]
[:input-data :string]
[:on-press fn?]]]]
:any])

View File

@ -1,7 +1,89 @@
(ns schema.common
(:require
[malli.core :as malli]
malli.util
[schema.registry :as registry]))
(defn- optional-keys
"Makes map keys optional, non-recursively.
Ignores keys with schema property :no-maybe true."
[?schema]
(let [mapper (fn [[_k properties :as entry]]
(if (:no-optional properties)
(update entry 1 dissoc :no-optional)
(update entry 1 assoc :optional true)))]
(malli.util/transform-entries ?schema #(map mapper %) nil)))
(defn- maybe-keys
"Makes map keys nullable, non-recursively.
Ignores keys with schema property :no-maybe true."
[?schema]
(let [mapper (fn [[_k properties ?key :as entry]]
(let [key-form (malli/form ?key)]
(if (or (:no-maybe properties)
(and (vector? key-form)
(= :maybe (first key-form))))
(update entry 1 dissoc :no-maybe)
(assoc entry 2 [:maybe key-form]))))]
(malli.util/transform-entries ?schema #(map mapper %) nil)))
(defn- ?map-optional-maybe
"This implementation should closely track how the base schema `:map` is
implemented by malli. This is a solution to sort of inherit a base schema and
reuse its implementation, but extend it to support new features.
Usage:
[:schema.common/map {:optional true :maybe true}
[:type [:enum :default :multiuser :group]]
[:customization-color :schema.common/customization-color]
[:blur? boolean?]
[:value {:no-optional true} pos-int?]]
This is the same as if you manually entered the following:
[:map
[:type {:optional true} [:maybe [:enum :default :multiuser :group]]]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
[:blur? {:optional true} [:maybe :boolean]]
[:value [:maybe pos-int?]]]
You can verify the final transformed `:schema.common/map` by
calling `(malli.core/form ?some-schema)`.
Schema `:schema.common/map` may take two properties:
- When `:optional` is non-nil, every key will have its `:optional` property
set to true. If any key uses the property `:no-optional` non-nil, the key is
ignored.
- When `:maybe` is non-nil, every key will have its children wrapped by a
`:maybe` schema. If any key uses the property `:no-maybe` non-nil, the key is
ignored.
"
[]
^{:type ::malli/into-schema}
(reify
malli/AST
(-from-ast [parent ast options]
(malli/-from-ast parent ast options))
malli/IntoSchema
(-type [_]
:schema.common/map)
(-type-properties [this]
(malli/-type-properties this))
(-properties-schema [this options]
(malli/-properties-schema this options))
(-children-schema [this options]
(malli/-children-schema this options))
(-into-schema [_parent props children options]
(cond-> (malli/into-schema :map (dissoc props :optional :maybe) children options)
(:optional props) (optional-keys)
(:maybe props) (maybe-keys)))))
(def ^:private ?theme
[:enum :light :dark])
@ -44,4 +126,5 @@
(registry/register ::image-source ?image-source)
(registry/register ::rpc-call ?rpc-call)
(registry/register ::exception ?exception)
(registry/register ::map (?map-optional-maybe))
(registry/register ::hiccup ?hiccup))

View File

@ -17,7 +17,13 @@
We normalize `?schema` by always registering it as a proper instance of
`malli.core/Schema` to avoid inconsistencies down the road."
[type ?schema]
(swap! registry assoc type (malli/schema ?schema))
(swap! registry assoc
type
;; An into-schema instance should not be converted to schema. This will cause a
;; :malli.core/invalid-schema. One such schema is `:schema.common/map`.
(if (malli/into-schema? ?schema)
?schema
(malli/schema ?schema)))
?schema)
(defn merge