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 [:catn
[:props [:props
[:map [:schema.common/map {:optional true :maybe true}
[:full-name {:optional true} [:maybe string?]] [:full-name string?]
[:size {:optional true} [:maybe (into [:enum] (keys style/sizes))]] [:size (into [:enum] (keys style/sizes))]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]] [:customization-color :schema.common/customization-color]
[:static? {:optional true} [:maybe boolean?]] [:static? boolean?]
[:status-indicator? {:optional true} [:maybe boolean?]] [:status-indicator? boolean?]
[:online? {:optional true} [:maybe boolean?]] [:online? boolean?]
[:ring? {:optional true} [:maybe boolean?]] [:ring? boolean?]
[:profile-picture [:profile-picture :schema.quo/profile-picture-source]]]]
{:optional true}
[:maybe :schema.quo/profile-picture-source]]]]]
:any]) :any])

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,89 @@
(ns schema.common (ns schema.common
(:require (:require
[malli.core :as malli]
malli.util
[schema.registry :as registry])) [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 (def ^:private ?theme
[:enum :light :dark]) [:enum :light :dark])
@ -44,4 +126,5 @@
(registry/register ::image-source ?image-source) (registry/register ::image-source ?image-source)
(registry/register ::rpc-call ?rpc-call) (registry/register ::rpc-call ?rpc-call)
(registry/register ::exception ?exception) (registry/register ::exception ?exception)
(registry/register ::map (?map-optional-maybe))
(registry/register ::hiccup ?hiccup)) (registry/register ::hiccup ?hiccup))

View File

@ -17,7 +17,13 @@
We normalize `?schema` by always registering it as a proper instance of We normalize `?schema` by always registering it as a proper instance of
`malli.core/Schema` to avoid inconsistencies down the road." `malli.core/Schema` to avoid inconsistencies down the road."
[type ?schema] [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) ?schema)
(defn merge (defn merge