Giter Club home page Giter Club logo

muotti's Introduction

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

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?])
Loading

muotti's People

Contributors

esuomi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

muotti's Issues

missing LICENSE file

Hi,
Thanks for putting out this neat little library,

can you please put this under a license, this is necessary for corporate use, preferably MIT.

global cache leads to collisions

I'm using multiple independent configs, that have similar source/target values, and your global cache causes bugs
e.g. https://github.com/esuomi/muotti/blob/master/src/main/clj/muotti/core.clj#L97

Instead it makes more senes to create a new localized cache when ->transformer is called, tied to that specific configuration.

(defn ->transformer
  "Creates a new [[Transformer]] instance from given map of adjacencies and related configuration. By default uses
  [[default-adjacencies]], but the map of adjacency lists to validation and transformation functions can be overridden.

  ```clojure
  ; create new transformer with built-in defaults:
  (def defaults-t (->transformer))

  ; create new transformer with custom transformation configuration:
  (def custom-t (-> transformer {:transformations [:keyword :string] {:validator   keyword?
                                                                      :transformer name}
                                                  [:string :number]  {:validator   string?
                                                                      :transformer parse-long}})
  ```"
  ([] ->transformer default-config)
  ([{:keys [transformations]
     :as   config}]
   (let [resolved-paths (atom {})
         graph          (adjacencies-as-graph transformations)]
     (reify Transformer
       (transform [_ from to value]
         (if-let [cached-chain (get @resolved-paths [from to])]
           (do
             (log/tracef "Reusing cached chain %s for transformation [%s -> %s]" cached-chain from to)
             (resolve-chain value cached-chain))
           (let [chains (resolve-transformer-chains graph from to)]
             (if (= ::unknown-path chains)
               chains
               (reduce
                 (fn [_ chain]
                   (let [result (resolve-chain value chain)]
                     (if-not (= ::failed-resolve result)
                       (do
                         (log/debugf "Path %s produces usable result for transformation [%s -> %s], caching path for future lookups" chain from to)
                         (swap! resolved-paths assoc [from to] chain)
                         (reduced result))
                       ::invalid-value)))
                 ::invalid-value
                 chains)))))
       (visualize-dot [_]
         (lio/dot-str graph))
       (visualize-mermaid [_]
         (->mermaid graph))
       (config [_]
         config)))))

Also, as a side note, if you're interested in optimal performance, that cache will perform better (both lookups & insertions) if it's a java.util.HashMap or ConcurrentHashMap, malli does the same thing.

more comprehensive Malli schema registry support

Currently just the basics are covered, while Malli supports a wide range of schemas.

Likely desirable additions are

  • malli.core/base-schemas :and, :or, :orn, :not, :map, :map-of, :vector, :sequential, :set, :enum, :maybe, :tuple, :multi, :re, :fn, :ref, :=>, :function and :schema
    • associative schema support means eg. [:vector [:vector any?] -> [:map] => [[:a 1] [:b 2] [:c 3]] -> {:a 1 :b 2 :c 3}
    • :maybe should interact with :muotti/default (see #1) in such way that nil is considered valid, non-defaultable value
    • etc. etc.
  • malli.core/predicate-schemas all which are type-based, eg. pos?, char?, vector? etc.

Library description is a chore to read, needs rewriting.

The description blurb until v1.0.0 was

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.

and after that I've changed it to

Muotti is a graph based value transformer library which aims to solve complex value transformation by utilizing a digraph of known transformation steps to produce a final transformer chain which performs the transformation.

This is, however, a terribly self-repeating mouthful. While technically correct and complete, the more I look at it, the less I like it. So please, if you're better than me at describing things in English, please pitch in! :)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.