• Stars
    star
    116
  • Rank 295,635 (Top 6 %)
  • Language
    Clojure
  • License
    Other
  • Created about 4 years ago
  • Updated 5 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Clojure.spec coercion library for clj(s)

coax

Clojure.spec coercion library for clj(s)

Most Coax "public" functions take a spec and a value and try to return a new value that conforms to that spec by altering the input value when possible and most importantly when it makes sense.

Coax is centred around its own registry for coercion rules, when a coercion is not registered it can infer in most cases what to do to coerce a value into something that conforms to a spec. It also supports "coerce time" options to enable custom coercion from any spec type, including spec forms (like s/coll-of & co) or just idents (predicates, registered specs).

Coax initially started as a fork of spec-coerce, but nowadays the internals and the API are very different. Wilker Lรบcio's approach gave us a nice outline of how such library could expose its functionality.

What

The typical (infered) example would be :

(s/def ::foo keyword?)
(c/coerce ::foo "bar") -> :bar

registering coercers

You can register a coercer per spec if needed

(s/def ::foo string?)
(c/def ::foo (fn [x opts] (str "from-registry: " x)))
(c/coerce ::foo "bar") -> "from-registry: bar"

Overrides

Overrides allow to change the defaults, essentially all the internal conversion rules are open via the options to coerce, they will be merged with the internal registry at coerce time.

(s/def ::foo keyword?)
(c/coerce ::foo "bar" {::c/idents {::foo (fn [x opts] (str "keyword:" x))}}) -> "keyword:bar"

Coercers are functions of 2 args, the value, and the options coerce received. They return either a coerced value or :exoscale.coax/invalid, which indicates we didn't know how to coerce that value, in which case s/coerce will set the value to the input.

Overrides also works on any qualified-ident (registered specs or symbols/fns), which is something spec-coerce cannot do currently.

The typical example would be :

(s/def ::foo (s/coll-of keyword?))
;; we'll namespace all keywords in that coll-of
(c/coerce ::foo ["a" "b"] {::c/idents {`keyword? (fn [x opts] (keyword "foo" x)})}) -> [foo/a foo/b]

You can specify multiple overrides per coerce call.

Another thing we added is the ability to reach and change the behaviour of coercer generators via ::c/forms, essentially allowing you to support any spec form like inst-in, coll-of, .... You could easily for instance generate open-api definitions using these.

(s/coerce ::foo (s/coll-of keyword?)
          {::c/forms {`s/coll-of (fn [[_ spec]] (fn [x opts] (do-something-crazy-with-spec+the-value spec x opts)))}})

Documentation

cljdocbadge

Installation

coax is available on Clojars.

Add this to your dependencies:

Clojars Project

More Usage examples (taken directly from spec-coerce)

Learn by example:

(ns exoscale.coax.example
  (:require
    [clojure.spec.alpha :as s]
    [exoscale.coax :as c]))

; Define a spec as usual
(s/def ::number int?)

; Call the coerce method passing the spec and the value to be coerced
(c/coerce ::number "42") ; => 42

; Like spec generators, when using `and` it will use the first item as the inference source
(s/def ::odd-number (s/and int? odd?))
(c/coerce ::odd-number "5") ; => 5

; When inferring the coercion, it tries to resolve the upmost spec in the definition
(s/def ::extended (s/and ::odd-number #(> % 10)))
(c/coerce ::extended "11") ; => 11

; Nilables are considered
(s/def ::nilable (s/nilable ::number))
(c/coerce ::nilable "42") ; => 42
(c/coerce ::nilable "foo") ; => "foo"

; The coercion can even be automatically inferred from specs given explicitly as sets of a homogeneous type
(s/def ::enum #{:a :b :c})
(c/coerce ::enum ":a") ; => :a

; If you wanna play around or use a specific coercion, you can pass the predicate symbol directly
(c/coerce `int? "40") ; => 40

; Parsers are written to be safe to call, when unable to coerce they will return the original value
(c/coerce `int? "40.2") ; => "40.2"
(c/coerce `inst? "date") ; => "date"

; To leverage map keys and coerce a composed structure, use coerce-structure
(c/coerce-structure {::number      "42"
                      ::not-defined "bla"
                      :sub          {::odd-number "45"}})
; => {::number      42
;     ::not-defined "bla"
;     :sub          {::odd-number 45}}

; coerce-structure supports overrides, so you can set a custom coercer for a specific context
(c/coerce-structure {::number      "42"
                      ::not-defined "bla"
                      :sub          {::odd-number "45"}}
                     {::c/idents {::not-defined `keyword?
; => {::number      42
;     ::not-defined :bla
;     :sub          {::odd-number 45}}

; If you want to set a custom coercer for a given spec, use the exoscale.coax registry
(defrecord SomeClass [x])
(s/def ::my-custom-attr #(instance? SomeClass %))
(c/def ::my-custom-attr #(map->SomeClass {:x %}))

; Custom registered keywords always takes precedence over inference
(c/coerce ::my-custom-attr "Z") ; => #user.SomeClass{:x "Z"}

(c/coerce ::my-custom-attr "Z") {::c/idents {::my-custom-attr keyword}}) ; => :Z

Examples from predicate to coerced value:

; Numbers
(c/coerce `number? "42")                                   ; => 42.0
(c/coerce `integer? "42")                                  ; => 42
(c/coerce `int? "42")                                      ; => 42
(c/coerce `pos-int? "42")                                  ; => 42
(c/coerce `neg-int? "-42")                                 ; => -42
(c/coerce `nat-int? "10")                                  ; => 10
(c/coerce `even? "10")                                     ; => 10
(c/coerce `odd? "9")                                       ; => 9
(c/coerce `float? "42.42")                                 ; => 42.42
(c/coerce `double? "42.42")                                ; => 42.42
(c/coerce `zero? "0")                                      ; => 0

; Numbers on CLJS
(c/coerce `int? "NaN")                                     ; => js/NaN
(c/coerce `double? "NaN")                                  ; => js/NaN

; Booleans
(c/coerce `boolean? "true")                                ; => true
(c/coerce `boolean? "false")                               ; => false
(c/coerce `true? "true")                                   ; => true
(c/coerce `false? "false")                                 ; => false

; Idents
(c/coerce `ident? ":foo/bar")                              ; => :foo/bar
(c/coerce `ident? "foo/bar")                               ; => 'foo/bar
(c/coerce `simple-ident? ":foo")                           ; => :foo
(c/coerce `qualified-ident? ":foo/baz")                    ; => :foo/baz
(c/coerce `keyword? "keyword")                             ; => :keyword
(c/coerce `keyword? ":keyword")                            ; => :keyword
(c/coerce `simple-keyword? ":simple-keyword")              ; => :simple-keyword
(c/coerce `qualified-keyword? ":qualified/keyword")        ; => :qualified/keyword
(c/coerce `symbol? "sym")                                  ; => 'sym
(c/coerce `simple-symbol? "simple-sym")                    ; => 'simple-sym
(c/coerce `qualified-symbol? "qualified/sym")              ; => 'qualified/sym

; Collections
(c/coerce `(s/coll-of int?) ["5" "11" "42"])               ; => [5 11 42]
(c/coerce `(s/coll-of int?) ["5" "11.3" "42"])             ; => [5 "11.3" 42]
(c/coerce `(s/map-of keyword? int?) {"foo" "42" "bar" "31"})
; => {:foo 42 :bar 31}

; Branching
; tests are realized in order
(c/coerce `(s/or :int int? :bool boolean?) "40")           ; 40
(c/coerce `(s/or :int int? :bool boolean?) "true")         ; true
; returns original value when no options can handle
(c/coerce `(s/or :int int? :bool boolean?) "foo")          ; "foo"

; Tuple
(c/coerce `(s/tuple int? string?) ["0" 1])                 ; => [0 "1"]

; Others
(c/coerce `uuid? "d6e73cc5-95bc-496a-951c-87f11af0d839")   ; => #uuid "d6e73cc5-95bc-496a-951c-87f11af0d839"
(c/coerce `inst? "2017-07-21")                             ; => #inst "2017-07-21T00:00:00.000000000-00:00"
(c/coerce `nil? "foo")                                     ; => "foo"
(c/coerce `nil? nil)                                       ; => nil

;; Clojure only:
(c/coerce `uri? "http://site.com") ; => (URI. "http://site.com")
(c/coerce `decimal? "42.42") ; => 42.42M
(c/coerce `decimal? "42.42M") ; => 42.42M

;; Throw exception when coercion fails
(c/coerce! ::number "abc") ; => throws (ex-info "Failed to coerce value" {:spec ::number :val "abc" ...})
(c/coerce! :simple-keyword "abc") ; => "abc", coerce! doesn't do anything on simple keywords

;; Conform the result after coerce
(c/conform ::number "40")          ; 40

;; Throw on coerce structure
(c/coerce-structure {::number "42"} {::c/op c/coerce!})

;; Conform on coerce structure
(c/coerce-structure {::number "42"} {::c/op c/conform})

Caching

Coax applies caching of coercers function to cut the cost of walking specs and generating coercers per call, it makes the coercion process orders of magnitude faster once cached (depends on what the coercion does of course). It is on by default. The cache is under exoscale.coax/coercer-cache, it's just an atom holding a map of [spec, options] -> coercer. In most cases you shouldn't have to care about this, for instance when you define static coercers via coax/def we'll make sure the cache is updated accordingly. But during development you might need to be aware of the existence of that cache (ex if you defined a bugged coercer, or while doing REPL dev).

In any case you can turn off the cache by passing :exoscale.coax/cache? false to the options of coerce/conform/coerce-structure, alternatively you can manually fiddle with the cache under exoscale.coax/coercer-cache, for instance via (reset! exoscale.coax/coercer-cache {}).

License

  • License Copyright ยฉ 2020 Exoscale - Distributed under ISC License

  • spec-coerce original license Copyright ยฉ 2017 Wilker Lรบcio - Distributed under the MIT License.

More Repositories

1

pithos

UNMAINTAINED - cassandra-backed object store. Retired backend for Exoscale's Simple Object Storage offering.
Clojure
282
star
2

ex

In which we deal with exceptions the clojure way
Clojure
128
star
3

riemann-grid

simple grid to view riemann states
CSS
111
star
4

seql

Simplfied EDN Query Language for SQL
Clojure
109
star
5

cs

A simple, yet powerful CloudStack API client for python and the command-line.
Python
85
star
6

cli

Command-line tool for everything at Exoscale: compute, storage, dns.
Go
82
star
7

interceptor

Small Interceptor lib for clojure
Clojure
81
star
8

multi-master-kubernetes

Multi-master Kubernetes cluster on Exoscale
Python
65
star
9

lingo

spec explain improved
Clojure
52
star
10

telex

simple jdk http wrappers
Clojure
43
star
11

exopaste

Clojure
38
star
12

openbsd-cloud-init

dependency-free initialization for OpenBSD cloud templates
Perl
34
star
13

egoscale

exoscale golang bindings
Go
29
star
14

deps-modules

Clojure
29
star
15

tools.project

Helpers to work with our tools.deps projects
Clojure
27
star
16

terraform-provider-exoscale

Terraform Exoscale provider
Go
25
star
17

collmann

riemann inside collectd
Clojure
24
star
18

python-riemann-wrapper

time and report exception in riemann for functions
Python
20
star
19

raven

clojure sentry library
Clojure
19
star
20

reporter

event, errors and metric reporting component
Clojure
18
star
21

ping-times

Toy single page application for blog post
Clojure
17
star
22

exoscale-circleci-nodejs

Deploying a NodeJS Application to Exoscale via CircleCI
JavaScript
16
star
23

cluster-api-provider-exoscale

Go
15
star
24

riemann-acknowledgement

helper streams for riemann to acknowledge alerts
Clojure
15
star
25

clojure-kubernetes-client

Clojure client for Kubernetes API
Clojure
14
star
26

liprug

operational status board
HTML
14
star
27

python-exoscale

Python bindings for the Exoscale APIs
Python
13
star
28

pullq

a variation on the idea of review-gator
Clojure
13
star
29

exoscale-cloud-controller-manager

Go
13
star
30

ansible-pgsql-demo

Deploying postgresql with high availability in seconds using Ansible on Exoscale
Makefile
13
star
31

yummy

YAML configuration for Clojure application
Clojure
12
star
32

collectd-puppet-reports

python collectd module to gather metrics from puppet reports
Python
11
star
33

automata

Moore-machine like finite state transducers
Clojure
11
star
34

packer-examples

How to build Exoscale templates with Packer and Qemu
Shell
9
star
35

vinyl

A Clojure facade for the FoundationDB record-layer
Clojure
9
star
36

clostack

clojure cloudstack client
Clojure
9
star
37

exoip

IP watchdog
Go
8
star
38

riemann-mysql

mysql replication health check for riemann
Go
8
star
39

collectd-cloudstack

collectd plugin for collecting usefull metrics from cloudstack API
Python
8
star
40

clojure-kubectl

Clojure wrapper for the Kubectl CLI
Clojure
7
star
41

vagrant-exoscale-boxes

Tooling to generate automatically vagrant dummy boxes for exoscale Open Cloud
Python
7
star
42

collectd-bird

Collectd plugin for BIRD routing daemon
Python
6
star
43

clj-yaml

YAML encoding and decoding for Clojure using SnakeYAML
HTML
5
star
44

cel-parser

Clojure CEL parser and interpreter
Clojure
5
star
45

rpp-c

rpp: riemann persistent ping
C
5
star
46

workshop-visibility

dotscale workshop on visibility
Puppet
4
star
47

examples

tutorials and other publicly available code by Exoscale
HTML
4
star
48

vault-plugin-secrets-exoscale

Exoscale Secrets Plugin for Vault
Go
3
star
49

automysqlbackup

automysqlbackup
Shell
3
star
50

pkg-i40e-dkms

Debian packaging for Intel i40e driver
3
star
51

collectd-smartmon

Collectd smartmon script
Shell
3
star
52

go-reporter

Logs, metrics and Sentry for Golang
Go
3
star
53

pkg-kernel-4.4

Debian packaging for Ubuntu kernel
C
2
star
54

collectd-quagga

Collectd plugin for Quagga routing daemon
Python
2
star
55

pkg-ixgbe-dkms

Debian packaging for ixgbe-dkms
2
star
56

packer-plugin-exoscale

Exoscale plugins for HashiCorp Packer
Go
2
star
57

sks-argocd

kubernetes manifests for deploying argocd on sks
HCL
2
star
58

cloak

Simple utility to prevent outputing secrets in unwanted places
Clojure
2
star
59

clojure-exoscale

A Clojure library for Exoscale resources
Clojure
2
star
60

packer-post-processor-exoscale-import

The Packer Exoscale Import post-processor plugin
Go
2
star
61

megaraid-collectd

collectd exec module to watch over megaraid status
Perl
2
star
62

angular-jobs

demo angularjs app
Clojure
2
star
63

collectd-cumulus

Collectd plugin for Cumulus switches (system)
Python
2
star
64

pkg-intel-microcode

Debian packaging for intel-microcode
2
star
65

go.mk

Base makefile for go projects
Makefile
2
star
66

runstatus-cli

Command-line client for Runstatus
Python
2
star
67

pallet-exoscale-demo

Demo infrastructure
CSS
2
star
68

quali

spec helper to (de-)qualify maps
Clojure
1
star
69

terms

Exoscale terms and conditions repository
1
star
70

zlocker

Zookeeper based isolated command execution
Go
1
star
71

pkg-puppetboard

Debian puppetboard packaging
1
star
72

quickstart-demos

Quickstart demos
HTML
1
star
73

pkg-frr

Debian packaging for frr
1
star
74

pkg-bird2

Debian packaging for bird2
1
star
75

pkg-puppetdb

Debian packaging for puppetdb
1
star
76

lein-replace

Transform your leiningen project with replace expressions
Clojure
1
star
77

pkg-zookeeper

Debian packaging for zookeeper
1
star
78

clobill

clojure hostbill client
Clojure
1
star
79

pkg-ipxe-qemu-256k-compat

Debian packaging for ipxe-qemu-256k-compat
1
star
80

vault-plugin-auth-exoscale

Vault "exoscale" Authentication Method
Go
1
star
81

pallet-exoscale

pallet provider for exoscale open cloud
Clojure
1
star
82

drawio-library

Official Exoscale Draw.io library for creating diagrams
1
star
83

cloudstack-restrictions

restriction manager for cloudstack
Java
1
star
84

docker-machine-driver-exo

Up to date docker-machine driver
Go
1
star
85

packer-builder-exoscale

Packer Builder plugin for Exoscale Compute instance templates
Go
1
star
86

homebrew-tap

Homebrew Formulae to Exoscale's cli binary
Ruby
1
star
87

cert-manager-webhook-exoscale

A cert-manager webhook for creating an ACME DNS01 solver webhook for Exoscale
Go
1
star
88

cumulus-cl-ports-puppet

Puppet module to manage switch port configuration on Cumulus Linux
Ruby
1
star
89

csv-to-riemann

Clojure app converting CSV files into Riemann events
Clojure
1
star
90

deps-version

Simple version management for tools.deps
Clojure
1
star
91

pkg-golang

Debian packaging for golang
1
star
92

clj-itsdangerous

clojure based partial implementation of pallets/itsdangerous
Clojure
1
star
93

data.xml

Functions to parse XML into lazy sequences and lazy trees and emit these as text
Clojure
1
star
94

ablauf

long-running workflow management
Clojure
1
star