Liking cljdoc? Tell your friends :D

Muotti

Noun.

  1. mould/mold (hollow form or matrix for shaping a fluid or plastic substance)
  2. cast (mould used to make cast objects)
  3. die
  4. form (thing that gives shape to other things as in a mold)

(source: https://en.wiktionary.org/wiki/muotti)

Deploy to Clojars Clojars Project cljdoc badge

Muotti is a graph based value transformer library which aims to solve value transformation by utilizing a digraph of known transformations to produce a transformer chain which is then used to perform the actual transformation.

Usage

Given a map of adjacencies - that is, edges of a graph - with validation and transformer functions:

(require '[muotti.core :as muotti])

(def config {:transformations {[:keyword :string] {:validator   keyword?
                                                   :transformer name}
                               [:string :number]  {:validator   string?
                                                   :transformer parse-long}
                               [:string :boolean] {:validator   string?
                                                   :transformer boolean}
                               [:number :string]  {:validator   number?
                                                   :transformer str}}})

a transformer can be created:

(def t (muotti/->transformer config))

which is then immediately usable for transforming values:

(muotti/transform t :keyword :number :123)
; => 123
(muotti/transform t :number :boolean 123)
; => true  ;; non-empty values are treated as ´true´ by clojure.core/boolean

Unresolvable transformations return a special value:

(muotti/transform t :keyword :double :3.14)
; => ::unknown-path

Transformer chain validation errors also return a special value:

(def broken-adjacency {:transformations {[:a :b] {:validator   keyword?
                                                  :transformer str}}})
(def t2 (muotti/->transformer broken-adjacency))
(muotti/transform t2 :a :b "not a number")
;; => ::invalid-value

All possible paths in the graph will be tested to resolve a result:

(def multiple {:transformations {[:in :num] {:transformer #(Integer/parseInt %)}
                                 [:in :str] {:transformer str}
                                 [:str :out] {:transformer #(= "magic!" %)}
                                 [:num :out] {:transformer #(= 6 %)}}})
(def t3 (muotti/->transformer multiple))
(muotti/transform t3 :in :out "6")
;;=> true
(muotti/transform t3 :in :out "magic!")
;;=> true
(muotti/transform t3 :in :out "0")
;;=> false
(muotti/transform t3 :in :out "anything")
;;=> false

Resolving order of paths is not guaranteed to be stable!

Malli integration

Muotti is made to complement Malli's decoding and encoding capabilities through Malli's Value Transformation capability.

Create a Malli transformer and then use it to call eg. malli.core/decode with the transformer:

(require '[malli.core :as malli])
(require '[muotti.malli :as mm])

(def malli-transformer (mm/transformer (muotti/->transformer mm/malli-config)))

(malli/decode
  [:map
   [:a {:muotti/ignore true} :uuid]
   [:b :int]]
  {:a :invalid
   :b "123"}
  malli-transformer)
;;=> {:a nil, :b 123}

Override source and target types

Use :muotti/source and :muotti/target properties to override transformation types.

See muotti.malli-tests/override-types for examples.

Provide default value for nil inputs

Use :muotti/default to provide a default value.

(malli/decode
  [:string {:muotti/default "hello"}]
  nil
  malli-transformer)
;;=> hello

Supported transformations

Muotti's aim is to support all major Malli types and predicates which are too numerous to list here. Instead see either

  1. muotti.malli-tests namespace or
  2. The DOT graph below

DOT/GraphViz support

It is possible to output the graph contained by the transformer as DOT:

(->> (muotti/->transformer mm/malli-config)
     (muotti/visualize-dot)
     (spit "/tmp/graph.dot"))

The resulting file can be input into GraphViz:

dot -Tpng /tmp/graph.dot > graph.png

which results in DOT example output

Mermaid Flowchart support

For easier embedding the graph can also be converted into a Mermaid Flowchart script:

(->> (muotti/->transformer mm/malli-config)
     (muotti/visualize-mermaid)

This results in a string which can be, for example, put directly into GitHub Markdown file:

flowchart TD
	:qualified-symbol([:qualified-symbol]) --> :string([:string])
	:qualified-symbol([:qualified-symbol]) --> :any([:any])
	:double([:double]) --> float?([float?])
	:double([:double]) --> double?([double?])
	:double([:double]) --> :string([:string])
	:double([:double]) --> number?([number?])
	:double([:double]) --> :any([:any])
	:muotti.malli/big-integer([:muotti.malli/big-integer]) --> integer?([integer?])
	:muotti.malli/big-integer([:muotti.malli/big-integer]) --> nat-int?([nat-int?])
	:muotti.malli/big-integer([:muotti.malli/big-integer]) --> number?([number?])
	:int([:int]) --> :double([:double])
	:int([:int]) --> :muotti.malli/big-integer([:muotti.malli/big-integer])
	:int([:int]) --> int?([int?])
	:int([:int]) --> :muotti.malli/ratio([:muotti.malli/ratio])
	:int([:int]) --> :muotti.malli/float([:muotti.malli/float])
	:int([:int]) --> :string([:string])
	:int([:int]) --> :muotti.malli/big-decimal([:muotti.malli/big-decimal])
	:int([:int]) --> integer?([integer?])
	:int([:int]) --> nat-int?([nat-int?])
	:int([:int]) --> number?([number?])
	:int([:int]) --> :any([:any])
	:symbol([:symbol]) --> :string([:string])
	:symbol([:symbol]) --> :any([:any])
	:qualified-keyword([:qualified-keyword]) --> :string([:string])
	:qualified-keyword([:qualified-keyword]) --> :any([:any])
	int?([int?]) --> neg-int?([neg-int?])
	int?([int?]) --> pos-int?([pos-int?])
	:muotti.malli/ratio([:muotti.malli/ratio]) --> ratio?([ratio?])
	:muotti.malli/ratio([:muotti.malli/ratio]) --> number?([number?])
	:muotti.malli/float([:muotti.malli/float]) --> float?([float?])
	:muotti.malli/float([:muotti.malli/float]) --> number?([number?])
	:string([:string]) --> :double([:double])
	:string([:string]) --> :muotti.malli/big-integer([:muotti.malli/big-integer])
	:string([:string]) --> :int([:int])
	:string([:string]) --> :symbol([:symbol])
	:string([:string]) --> :muotti.malli/ratio([:muotti.malli/ratio])
	:string([:string]) --> :keyword([:keyword])
	:string([:string]) --> :uuid([:uuid])
	:string([:string]) --> :boolean([:boolean])
	:keyword([:keyword]) --> :symbol([:symbol])
	:keyword([:keyword]) --> :string([:string])
	:keyword([:keyword]) --> :any([:any])
	:muotti.malli/big-decimal([:muotti.malli/big-decimal]) --> decimal?([decimal?])
	:muotti.malli/big-decimal([:muotti.malli/big-decimal]) --> float?([float?])
	:muotti.malli/big-decimal([:muotti.malli/big-decimal]) --> number?([number?])
	integer?([integer?]) --> rational?([rational?])
	ratio?([ratio?]) --> rational?([rational?])
	:uuid([:uuid]) --> :string([:string])
	:uuid([:uuid]) --> :any([:any])
	:boolean([:boolean]) --> :string([:string])
	:boolean([:boolean]) --> :any([:any])
	number?([number?]) --> pos?([pos?])
	number?([number?]) --> neg?([neg?])
	number?([number?]) --> zero?([zero?])

Can you improve this documentation?Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close