spec-tools.spell
namespace for closing map specs functionally using spell-spec.
com.bhauman/spell-spec
& expound
spec-tools.spell/closed
to close a spec (non recursive)spec-tools.spell/closed-key
to functionally create a closed s/keys
spec(require '[clojure.spec.alpha :as s])
(require '[spec-tools.spell :as spell])
(s/def ::name string?)
(s/def ::use-history boolean?)
(s/def ::config (spell/closed (s/keys :opt-un [::name ::use-history])))
(s/def ::options (spell/closed (s/keys :opt-un [::config])))
(def invalid {:config {:name "John" :use-hisory false :countr 1}})
(s/explain-data ::options invalid)
;#:clojure.spec.alpha{:problems ({:path [:config 0],
; :pred #{:use-history},
; :val :use-hisory,
; :via [:user/options :user/config],
; :in [:config :use-hisory 0],
; :expound.spec.problem/type :spell-spec.alpha/misspelled-key,
; :spell-spec.alpha/misspelled-key :use-hisory,
; :spell-spec.alpha/likely-misspelling-of (:use-history)}
; {:path [:config 0],
; :pred #{:name :use-history},
; :val :countr,
; :via [:user/options :user/config],
; :in [:config :countr 0],
; :expound.spec.problem/type :spell-spec.alpha/unknown-key,
; :spell-spec.alpha/unknown-key :countr}),
; :spec :user/options,
; :value {:config {:name "John", :use-hisory false, :countr 1}}}
(println (spell/explain-str ::options invalid))
; -- Misspelled map key -------------
;
; {:config {:name ..., :countr ..., :use-hisory ...}}
; ^^^^^^^^^^^
;
; should probably be: :use-history
;
; -- Unknown map key ----------------
;
; {:config {:name ..., :use-hisory ..., :countr ...}}
; ^^^^^^^
;
; should be one of: :name, :use-history
;
; -------------------------
; Detected 2 errors
spec-tools.core/merge
is now visitable by Erik Assum.st/coerce
doesn't reverse list order, fixes compojure-api#406st/Spec
form, all :spec-tools.parse
keys are stripped, fixes #159nil
specs are allowed, resolved as any?
s/or
, s/and
, s/coll-of
, s/map-of
, s/tuple
and s/nilable
, fixes #165.
st/json-transformer
and st/string-transformer
also transform values from keywords:(require '[spec-tools.core :as st])
(st/coerce (s/map-of int? int?) {:1 1, :2 2} st/json-transformer)
; {1 1, 2 2}
st/select-spec
now uses st/coerce
instead of st/decode
. Stripping out extra keys from specs:(require '[clojure.spec.alpha :as s])
(require '[spec-tools.core :as st])
(s/def ::height integer?)
(s/def ::weight integer?)
(s/def ::person (s/keys :req-un [::height ::weight]))
(s/def ::persons (s/coll-of ::person :into []))
(s/def ::data (s/keys :req-un [::persons]))
(st/select-spec
::data
{:TOO "MUCH"
:persons [{:INFOR "MATION"
:height 200
:weight 80}]})
; => {:persons [{:weight 80, :height 200}]}
{:leaf? true}
data::s/invalid
, fixing both #146 & #147fail-on-extra-keys-transformer
work again. #151[org.clojure/spec.alpha "1.10.439"] is available but we use "1.10.339"
:description
, fixes #135:title
property from qualified Spec registry name:name
, fixes #124st/coerce
function to coerce a value using form parsing and spec transformers. Can only walk over simple specs, and doesn't require any wrapping of specs. Inspired by spec-coerce.(deftest coercion-test
(testing "predicates"
(is (= 1 (st/coerce int? "1" st/string-transformer)))
(is (= "1" (st/coerce int? "1" st/json-transformer)))
(is (= :user/kikka (st/coerce keyword? "user/kikka" st/string-transformer))))
(testing "s/and"
(is (= 1 (st/coerce (s/and int? keyword?) "1" st/string-transformer)))
(is (= :1 (st/coerce (s/and keyword? int?) "1" st/string-transformer))))
(testing "s/or"
(is (= 1 (st/coerce (s/or :int int? :keyword keyword?) "1" st/string-transformer)))
(is (= :1 (st/coerce (s/or :keyword keyword? :int int?) "1" st/string-transformer))))
(testing "s/coll-of"
(is (= #{1 2 3} (st/coerce (s/coll-of int? :into #{}) ["1" 2 "3"] st/string-transformer)))
(is (= #{"1" 2 "3"} (st/coerce (s/coll-of int? :into #{}) ["1" 2 "3"] st/json-transformer)))
(is (= [:1 2 :3] (st/coerce (s/coll-of keyword?) ["1" 2 "3"] st/string-transformer)))
(is (= ::invalid (st/coerce (s/coll-of keyword?) ::invalid st/string-transformer))))
(testing "s/keys"
(is (= {:c1 1, ::c2 :kikka} (st/coerce (s/keys :req-un [::c1]) {:c1 "1", ::c2 "kikka"} st/string-transformer)))
(is (= {:c1 1, ::c2 :kikka} (st/coerce (s/keys :req-un [(and ::c1 ::c2)]) {:c1 "1", ::c2 "kikka"} st/string-transformer)))
(is (= {:c1 "1", ::c2 :kikka} (st/coerce (s/keys :req-un [::c1]) {:c1 "1", ::c2 "kikka"} st/json-transformer)))
(is (= ::invalid (st/coerce (s/keys :req-un [::c1]) ::invalid st/json-transformer))))
(testing "s/map-of"
(is (= {1 :abba, 2 :jabba} (st/coerce (s/map-of int? keyword?) {"1" "abba", "2" "jabba"} st/string-transformer)))
(is (= {"1" :abba, "2" :jabba} (st/coerce (s/map-of int? keyword?) {"1" "abba", "2" "jabba"} st/json-transformer)))
(is (= ::invalid (st/coerce (s/map-of int? keyword?) ::invalid st/json-transformer))))
(testing "s/nillable"
(is (= 1 (st/coerce (s/nilable int?) "1" st/string-transformer)))
(is (= nil (st/coerce (s/nilable int?) nil st/string-transformer))))
(testing "s/every"
(is (= [1] (st/coerce (s/every int?) ["1"] st/string-transformer))))
(testing "composed"
(let [spec (s/nilable
(s/nilable
(s/map-of
keyword?
(s/or :keys (s/keys :req-un [::c1])
:ks (s/coll-of (s/and int?) :into #{})))))
value {"keys" {:c1 "1" ::c2 "kikka"}
"keys2" {:c1 true}
"ints" [1 "1" "invalid" "3"]}]
(is (= {:keys {:c1 1 ::c2 :kikka}
:keys2 {:c1 true}
:ints #{1 "invalid" 3}}
(st/coerce spec value st/string-transformer)))
(is (= {:keys {:c1 "1" ::c2 :kikka}
:keys2 {:c1 true}
:ints #{1 "1" "invalid" "3"}}
(st/coerce spec value st/json-transformer))))))
st/decode
first tries to use st/coerce
, falling back to conforming-based approachspec-tools.parse/parse-spec
:
:keys
=> ::parse/keys
:keys/req
=> ::parse/keys-req
:keys/opt
=> ::parse/keys-opt
::parse/items
, ::parse/item
, ::parse/key
and ::parse/value
s/and
and s/or
are parsed into composite types:(testing "s/or"
(is (= {::parse/items [{:spec int?, :type :long} {:spec keyword?, :type :keyword}]
:type [:or [:long :keyword]]}
(parse/parse-spec (s/or :int int? :keyword keyword?)))))
(testing "s/and"
(is (= {::parse/items [{:spec int?, :type :long} {:spec keyword?, :type :keyword}]
:type [:and [:long :keyword]]}
(parse/parse-spec (s/and int? keyword?)))))
[org.clojure/spec.alpha "0.2.176"] is available but we use "0.1.143"
[org.clojure/clojurescript "1.10.339"] is available but we use "1.10.329"
[com.fasterxml.jackson.core/jackson-databind "2.9.7"] is available but we use "2.9.6"
{}
.swagger
namespace are merged into Swagger schemas, overriding values from json-schema
namespaced keys:(require '[spec-tools.core :as st])
(require '[spec-tools.swagger.core :as swagger])
(swagger/transform
(st/spec
{:spec string?
:json-schema/default ""
:json-schema/example "json-schema-example"
:swagger/example "swagger-example"}))
; {:type "string"
; :default ""
; :example "swagger-example"}
[com.fasterxml.jackson.core/jackson-databind "2.9.6"] is available but we use "2.9.5"
rational?
mapping for JSON Schema, fixes #113::swagger/extension
expansion in Swagger2 generation.st/IntoSpec
protocol to convert non-recursively vanilla clojure.spec
Specs into st/Spec
:s. Used in st/encode
, st/decode
, st/explain
, st/explain-data
, st/conform
and st/conform!
.st/type-conforming
, st/json-conforming
, st/string-conforming
st/Transformer
protocol to drive spec-driven value transformationsst/encode
) & decoded (st/decode
) using a transformer, fixes #96.spec-tools.conform
into spec-tools.transform
, covering both encoding & decoding of valuesst/type-transformer
, supporting both :type
and Spec
level transformationsencode
and decode
namespaces.st/encode
, st/decode
, st/explain
, st/explain-data
, st/conform
and st/conform!
take the transformer instance an optional third argument
st/json-transformer
, st/string-transformer
, strip-extra-keys-transformer
and fail-on-extra-keys-transformer
are shipped out-of-the-box.(defprotocol Transformer
(-name [this])
(-encoder [this spec value])
(-decoder [this spec value]))
:encode/*
and :decode/*
keys from Spec instances to declare how the values should be transformed(require '[clojure.spec.alpha :as s])
(require '[spec-tools.core :as st])
(require '[clojure.string :as str])
(s/def ::spec
(st/spec
{:spec #(and (simple-keyword? %) (-> % name str/lower-case keyword (= %)))
:description "a lowercase keyword, encoded in uppercase in string-mode"
:decode/string #(-> %2 name str/lower-case keyword)
:encode/string #(-> %2 name str/upper-case)}))
(st/decode ::spec :kikka)
; :kikka
(as-> "KiKka" $
(st/decode ::spec $))
; :clojure.spec.alpha/invalid
(as-> "KiKka" $
(st/decode ::spec $ st/string-transformer))
; :kikka
(as-> "KiKka" $
(st/decode ::spec $ st/string-transformer)
(st/encode ::spec $ st/string-transformer))
; "KIKKA"
no, as there can be multiple valid representations for a encoded value. But it's quaranteed that a decoded values X is always encoded into Y, which can be decoded back into X, y -> X -> Y -> X
(as-> "KikKa" $
(doto $ prn)
(st/encode ::spec $ st/string-transformer)
(doto $ prn)
(st/decode ::spec $ st/string-transformer)
(doto $ prn)
(st/encode ::spec $ st/string-transformer)
(prn $))
; "KikKa"
; "KIKKA"
; :kikka
; "KIKKA"
:type
information from Specs (mostly resolved automatically)(as-> "2014-02-18T18:25:37Z" $
(st/decode inst? $))
; :clojure.spec.alpha/invalid
;; decode using string-transformer
(as-> "2014-02-18T18:25:37Z" $
(st/decode inst? $ st/string-transformer))
; #inst"2014-02-18T18:25:37.000-00:00"
;; encode using string-transformer
(as-> "2014-02-18T18:25:37Z" $
(st/decode inst? $ st/string-transformer)
(st/encode inst? $ st/string-transformer))
; "2014-02-18T18:25:37.000+0000"
:type
gives you encoders & decoders (and docs) for free, like Data.Unjson:
(s/def ::kw
(st/spec
{:spec #(keyword %) ;; anonymous function
:type :keyword})) ;; encode & decode like a keyword
(st/decode ::kw "kikka" st/string-transformer)
;; :kikka
(st/decode ::kw "kikka" st/json-transformer)
;; :kikka
spec-tools.conform
just transform, dont' validate. Fixes #92. Thanks to Benjamin Albrechts/gen
triggers IllegalArgumentException
for nested aliased specs #94 by @johanwiren.spec-tools.data-spec/or
, thanks to Dmitri Sotnikov:(require '[clojure.spec.alpha :as s])
(require '[spec-tools.data-spec :as ds])
(s/conform
(ds/spec
::user
[(ds/or {:map {:alias string?}
:string string?})])
[{:alias "Rudi"}, "Rudolf"])
; [[:map {:alias "rudi"}] [:string "Rudolf"]]
map-of
data-spec keys are also data-specs. So this works now:(require '[clojure.spec.alpha :as s])
(require '[spec-tools.data-spec :as ds])
(s/valid?
(ds/spec ::ints {[int?] [int?]})
{[1 2 3] [4 5 6]})
; true
ds/spec
supports 1-arity version, allowing extra options :keys-spec
& :keys-default
.(require '[clojure.spec.alpha :as s])
(require '[spec-tools.data-spec :as ds])
(s/valid?
(ds/spec
{:name ::optiona-user
:spec {(ds/req :id) int?
:age pos-int?
:name string?}
:keys-default ds/opt})
{:id 123})
; true
ds/spec
option :name
is only required if non-qualified map keys are present.(require '[clojure.spec.alpha :as s])
(require '[spec-tools.data-spec :as ds])
(s/valid?
(ds/spec
{:spec [{::alias string?}]})
[{::alias "kikka"}
{::alias "kukka"}])
; true
spec-tools.core/merge
that selects only the specced keys from each conformed result, then merges those results onto the original input. This avoids overwriting conformed values with unconformed values while preserving all unspecced keys of the input. Fixes #90. By Arttu Kaipiainen.
updated deps:
[org.clojure/clojure "1.9.0"] is available but we use "1.9.0-beta4"
bigdec?
in favor of decimal?
(1.9.0-beta4 changes)[org.clojure/clojure "1.9.0-beta4"] is available but we use "1.9.0-beta4"
[org.clojure/spec.alpha "0.1.143"] is available but we use "0.1.134"
don't publish empty :required
fields for JSON Schemas, by acron0
added parsers for s/merge
& st/spec
.
Don't fail on recursive spec visits, fixes #75
BREAKING: spec-tools.visitor/visit-spec
should recurse with spec-tools.visitor/visit
instead of spec-tools.visitor/visit-spec
updated deps:
[org.clojure/clojure "1.9.0-beta2"] is available but we use "1.9.0-alpha19"
[org.clojure/clojurescript "1.9.946"] is available but we use "1.9.908"
or
and and
keys are parsed correctly for JSON Schema & Swagger, Fixes #79
BREAKING: spec-tools.type
is now spec-tools.parse
with public api of:
parse-spec
: given a spec name, form or instance, maybe returns a spec info map with resolved :type
and optionally other info, e.g. :keys
, :keys/req
and :keys/opt
for s/keys
specs.parse-form
: multimethod to parse info out of a formSpec Records of s/and
are fully resolved now, fixes https://github.com/metosin/compojure-api/issues/336
updated deps:
[org.clojure/spec.alpha "0.1.134"] is available but we use "0.1.123"
spec-tools.core/create-spec
fails with qualified keyword if they don't link to a spec, thanks to Camilo Roca
updated deps:
[org.clojure/clojure "1.9.0-alpha19"] is available but we use "1.9.0-alpha17"
[org.clojure/clojurescript "1.9.908"] is available but we use "1.9.660"
spec-tools.spec
predicate symbols into clojure.core
counterparts for JSON Schema / Swagger mappings.:type
from first predicate of s/and
, thanks to Andy ChambersSwagger2 integration (moved from spec-swagger)
spec-tools.swagger.core/transform
to transform Specs into Swagger Parameter Objects and Schema Objectsspec-tools.swagger.core/swagger-spec
to create valid Swagger Object.BREAKING: More configurable Spec Visitor
visit
to visit-spec
(to better support static analysis for arity errors)accept
function is now 4-arity (was 3-arity), taking the options-map as 4th argumentspec-tools.json-schema/transform
also has optional 4-arity with the options-map as 4th argumentvisitor (and by so, json-schema generation) supports also direct predicate specs, via form inference:
(require '[spec-tools.json-schema :as json-schema])
(json-schema/transform int?)
; {:type "integer", :format "int64"}
added spec-tools.core/spec-name
, to resolve spec name, like clojure.spec.alpha/spec-name
but non-private & understands Spec Records.
added spec-tools.core/spec-description
, to resolve spec description, understands Spec Records.
JSON Schema generation set :title
for Object Schemas based on st/spec-name
.
s/cat
& s/alt
don't set :minItems
and :maxItems
as they are Regexs.
moved many helper functions to spec-tools.impl
Spec Record describe*
uses the map syntax, e.g. (st/spec clojure.core/string? {}
=> (st/spec {:spec clojure.core/string?})
Spec Records inherit ::s/name
from underlaying specs, fixes #56
fixed explain*
for Spec Records
updated deps:
[org.clojure/clojure "0.1.123"] is available but we use "0.1.108"
[org.clojure/clojure "1.9.0-alpha17"] is available but we use "1.9.0-alpha16"
[org.clojure/clojurescript "1.9.562"] is available but we use "1.9.542"
BREAKING: update spec to alpha16
:
clojure.spec
=> clojure.spec.alpha
, cljs.spec
=> cljs.spec.alpha
etc.updated deps:
[org.clojure/spec.alpha "0.1.108"]
[org.clojure/clojure "1.9.0-alpha16"] is available but we use "1.9.0-alpha15"
[org.clojure/clojurescript "1.9.542"] is available but we use "1.9.518"
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close