• Stars
    star
    116
  • Rank 303,894 (Top 6 %)
  • Language
    Clojure
  • License
    Other
  • Created over 4 years ago
  • Updated 12 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

cs

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

cli

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

interceptor

Small Interceptor lib for clojure
Clojure
81
star
7

multi-master-kubernetes

Multi-master Kubernetes cluster on Exoscale
Python
65
star
8

lingo

spec explain improved
Clojure
52
star
9

telex

simple jdk http wrappers
Clojure
43
star
10

exopaste

Clojure
38
star
11

openbsd-cloud-init

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

egoscale

exoscale golang bindings
Go
29
star
13

deps-modules

Clojure
29
star
14

tools.project

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

terraform-provider-exoscale

Terraform Exoscale provider
Go
25
star
16

collmann

riemann inside collectd
Clojure
24
star
17

python-riemann-wrapper

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

raven

clojure sentry library
Clojure
19
star
19

reporter

event, errors and metric reporting component
Clojure
18
star
20

ping-times

Toy single page application for blog post
Clojure
17
star
21

exoscale-circleci-nodejs

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

cluster-api-provider-exoscale

Go
15
star
23

riemann-acknowledgement

helper streams for riemann to acknowledge alerts
Clojure
15
star
24

clojure-kubernetes-client

Clojure client for Kubernetes API
Clojure
14
star
25

liprug

operational status board
HTML
14
star
26

pullq

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

python-exoscale

Python bindings for the Exoscale APIs
Python
13
star
28

exoscale-cloud-controller-manager

Go
13
star
29

ansible-pgsql-demo

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

yummy

YAML configuration for Clojure application
Clojure
12
star
31

collectd-puppet-reports

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

automata

Moore-machine like finite state transducers
Clojure
11
star
33

vinyl

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

packer-examples

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

clostack

clojure cloudstack client
Clojure
9
star
36

exoip

IP watchdog
Go
8
star
37

riemann-mysql

mysql replication health check for riemann
Go
8
star
38

collectd-cloudstack

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

clojure-kubectl

Clojure wrapper for the Kubectl CLI
Clojure
7
star
40

vagrant-exoscale-boxes

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

collectd-bird

Collectd plugin for BIRD routing daemon
Python
6
star
42

clj-yaml

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

cel-parser

Clojure CEL parser and interpreter
Clojure
5
star
44

rpp-c

rpp: riemann persistent ping
C
5
star
45

workshop-visibility

dotscale workshop on visibility
Puppet
4
star
46

examples

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

vault-plugin-secrets-exoscale

Exoscale Secrets Plugin for Vault
Go
3
star
48

automysqlbackup

automysqlbackup
Shell
3
star
49

pkg-i40e-dkms

Debian packaging for Intel i40e driver
3
star
50

collectd-smartmon

Collectd smartmon script
Shell
3
star
51

go-reporter

Logs, metrics and Sentry for Golang
Go
3
star
52

pkg-kernel-4.4

Debian packaging for Ubuntu kernel
C
2
star
53

collectd-quagga

Collectd plugin for Quagga routing daemon
Python
2
star
54

pkg-ixgbe-dkms

Debian packaging for ixgbe-dkms
2
star
55

packer-plugin-exoscale

Exoscale plugins for HashiCorp Packer
Go
2
star
56

sks-argocd

kubernetes manifests for deploying argocd on sks
HCL
2
star
57

cloak

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

clojure-exoscale

A Clojure library for Exoscale resources
Clojure
2
star
59

packer-post-processor-exoscale-import

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

megaraid-collectd

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

collectd-cumulus

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

angular-jobs

demo angularjs app
Clojure
2
star
63

pkg-intel-microcode

Debian packaging for intel-microcode
2
star
64

go.mk

Base makefile for go projects
Makefile
2
star
65

runstatus-cli

Command-line client for Runstatus
Python
2
star
66

pallet-exoscale-demo

Demo infrastructure
CSS
2
star
67

quali

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

terms

Exoscale terms and conditions repository
1
star
69

zlocker

Zookeeper based isolated command execution
Go
1
star
70

pkg-puppetboard

Debian puppetboard packaging
1
star
71

quickstart-demos

Quickstart demos
HTML
1
star
72

pkg-frr

Debian packaging for frr
1
star
73

pkg-bird2

Debian packaging for bird2
1
star
74

pkg-puppetdb

Debian packaging for puppetdb
1
star
75

lein-replace

Transform your leiningen project with replace expressions
Clojure
1
star
76

pkg-zookeeper

Debian packaging for zookeeper
1
star
77

clobill

clojure hostbill client
Clojure
1
star
78

pkg-ipxe-qemu-256k-compat

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

vault-plugin-auth-exoscale

Vault "exoscale" Authentication Method
Go
1
star
80

pallet-exoscale

pallet provider for exoscale open cloud
Clojure
1
star
81

cloudstack-restrictions

restriction manager for cloudstack
Java
1
star
82

docker-machine-driver-exo

Up to date docker-machine driver
Go
1
star
83

drawio-library

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

packer-builder-exoscale

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

homebrew-tap

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

cert-manager-webhook-exoscale

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

cumulus-cl-ports-puppet

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

csv-to-riemann

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

deps-version

Simple version management for tools.deps
Clojure
1
star
90

pkg-golang

Debian packaging for golang
1
star
91

clj-itsdangerous

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

data.xml

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

ablauf

long-running workflow management
Clojure
1
star