- Support transforming multi-spec to JSON Schema. PR #281
- FIX :reason doesn't compose as expected #171
- Update Dependencies
[org.clojure/spec.alpha "0.5.238"] is available but we use "0.3.218"
- Deprecate
spec-tools.openapi/openapi-spec
- Update Dependencies
- :dependencies [[org.clojure/spec.alpha "0.2.187"]]
+ :dependencies [[org.clojure/spec.alpha "0.3.218"]]
- Support validating values transformed via encode/decode functions. #241. PR #248 by Wanderson Ferreira.
- Fix calling valid? within a custom type transform fn causing an StackOverflowError. #240. PR #247 by Wanderson Ferreira.
- Add OpenAPI 3 schema generation. PR #236 by Roman Rudakov
- Enforce collection type for collection data specs. #237. PR #239 by Wanderson Ferreira
- Always generate non-empty
:body
parameter name for Swagger, fixes metosin/reitit#399
- You can use
:json-schema
and:swagger
spec data to overwrite the generated JSON Schema and Swagger, respectively. PRs #229 by Wanderson Ferreira and #231 by Tommi Reiman - Add support for coercing strings to ratios. #209. PR #218 by Wanderson Ferreira
- Allow disabling title inference. #198. PR #221 by Wanderson Ferreira
- Fix JSON Schema for
bytes?
. PR #230 by Joe Lane - Fix Swagger for
sequential?
. #193. PR #227 by Wanderson Ferreira - Fix
spec-tools.core/merge
with symbol specs. #201. PR #220 by Wanderson Ferreira - Fix decimal coercion for numbers. PR #217 by Wanderson Ferreira
- Fix how
strip-extra-keys-transformer
works withs/or
. #178. PR #219 by Wanderson Ferreira - Fix multi-spec parsing with ClojureScript. PR #225 by Toropenko Sergey
- Support for
decimal?
coercion by Wanderson Ferreira - Add ability to coerce multi-specs, fixes #84
- Add URI transform support. #194 by Teemu Heikkilä
- Removed the jackson-databind dependency. #158
- BREAKING (minor): When encoding dates to strings, the timezone is now encoded as
Z
instead of+0000
. This makes the output RFC3339-compatible and keeps it ISO-8601-compatible.
- BREAKING (minor): When encoding dates to strings, the timezone is now encoded as
;; the new behavior - version 0.10.0 and later
user=> (st/encode inst? (java.util.Date.) st/json-transformer)
"2019-06-26T06:49:15.538Z"
;; the old behavior - version 0.9.3 and earlier
user=> (st/encode inst? (java.util.Date.) st/json-transformer)
"2019-06-26T06:50:02.233+0000"
- Updated dependency on jackson-databind to fix a vulnerability. #189
- Fix dynamic conforming with composite specs, fixes #184
- Coercion doesn't reverse lazy sequences, fixes #176, by salokristian.
spec-tools.spell
namespace for closing map specs functionally using spell-spec.- requires explicit dependencies to
com.bhauman/spell-spec
&expound
spec-tools.spell/closed
to close a spec (non recursive)spec-tools.spell/closed-key
to functionally create a closeds/keys
spec
- requires explicit dependencies to
(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#406- Less verbose
st/Spec
form, all:spec-tools.parse
keys are stripped, fixes #159 - BREAKING:
nil
specs are allowed, resolved asany?
- More robust walker, named specs can be used with
s/or
,s/and
,s/coll-of
,s/map-of
,s/tuple
ands/nilable
, fixes #165.- Thanks to Andrew Rudenko and Nicholas Hurden for contributing!
- Both
st/json-transformer
andst/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}
- BREAKING:
st/select-spec
now usesst/coerce
instead ofst/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}]}
- Identify Leaf Specs
- Leaf Spec Records have
{:leaf? true}
data - Non-leaf Spec can encode to
::s/invalid
, fixing both #146 & #147 - thanks to Alex Coyle!
- Leaf Spec Records have
- Remove an implicit dependency on test.check. #150
- Make
fail-on-extra-keys-transformer
work again. #151
- fixed a issue coercion issue
- updated deps:
[org.clojure/spec.alpha "1.10.439"] is available but we use "1.10.339"
- Fix fishy gen* call in your Spec protocol.
- Support Spec Records with Swagger on cljs by Miloslav Nenadál
- Swagger parameters read Spec
:description
, fixes #135 - JSON Schema objects get
:title
property from qualified Spec registry name - All top-level data-specs & nested map data-spec have name derived from
:name
, fixes #124 - New
st/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 usest/coerce
, falling back to conforming-based approach- BREAKING: enhanced parsing results from
spec-tools.parse/parse-spec
:- all parse result keys have been qualified:
:keys
=>::parse/keys
:keys/req
=>::parse/keys-req
:keys/opt
=>::parse/keys-opt
- new parser keys
::parse/items
,::parse/item
,::parse/key
and::parse/value
s/and
ands/or
are parsed into composite types:
- all parse result keys have been qualified:
(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?)))))
- Update deps:
[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"
- Not setting a Swagger response model doesn't emit empty schema
{}
. - Spec keys with
swagger
namespace are merged into Swagger schemas, overriding values fromjson-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"}
- updated deps:
[com.fasterxml.jackson.core/jackson-databind "2.9.6"] is available but we use "2.9.5"
- Fix
rational?
mapping for JSON Schema, fixes #113 - Remove
::swagger/extension
expansion in Swagger2 generation. - Date-conforming is now ISO8601-compliant on Clojure too, thanks to Fabrizio Ferrai.
- new
st/IntoSpec
protocol to convert non-recursively vanillaclojure.spec
Specs intost/Spec
:s. Used inst/encode
,st/decode
,st/explain
,st/explain-data
,st/conform
andst/conform!
. - BREAKING: Bye bye conforming, welcome transformers!
- Guide: https://github.com/metosin/spec-tools#spec-driven-transformations
- removed:
st/type-conforming
,st/json-conforming
,st/string-conforming
- new
st/Transformer
protocol to drive spec-driven value transformations - spec values can be both encoded (
st/encode
) & decoded (st/decode
) using a transformer, fixes #96. - renamed ns
spec-tools.conform
intospec-tools.transform
, covering both encoding & decoding of values st/type-transformer
, supporting both:type
andSpec
level transformations- Spec-driven transformations via keys in
encode
anddecode
namespaces. st/encode
,st/decode
,st/explain
,st/explain-data
,st/conform
andst/conform!
take the transformer instance an optional third argumentst/json-transformer
,st/string-transformer
,strip-extra-keys-transformer
andfail-on-extra-keys-transformer
are shipped out-of-the-box.
(defprotocol Transformer
(-name [this])
(-encoder [this spec value])
(-decoder [this spec value]))
- use
: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"
Spec Bijections?
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"
- use
: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
- 0.6.0 deployed correctly
- BREAKING: the transforming functions in
spec-tools.conform
just transform, dont' validate. Fixes #92. Thanks to Benjamin Albrecht - Fixed
s/gen
triggersIllegalArgumentException
for nested aliased specs #94 by @johanwiren. - New
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"]]
- BREAKING:
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"
- remove
bigdec?
in favor ofdecimal?
(1.9.0-beta4 changes) - updated deps:
[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 withspec-tools.visitor/visit
instead ofspec-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
andand
keys are parsed correctly for JSON Schema & Swagger, Fixes #79 -
BREAKING:
spec-tools.type
is nowspec-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
fors/keys
specs.parse-form
: multimethod to parse info out of a form
-
Spec Records of
s/and
are fully resolved now, fixes metosin/compojure-api#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"
- map
spec-tools.spec
predicate symbols intoclojure.core
counterparts for JSON Schema / Swagger mappings.
- resolve
:type
from first predicate ofs/and
, thanks to Andy Chambers - better error messages when trying to create non-homogeneous data-specs for Vectors & Sets
-
Swagger2 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.- see the docs for details.
-
BREAKING: More configurable Spec Visitor
- `spec-tools.visitor/visit takes optionally 4th argument, an options-map, passed into all sub-visits & accepts
- changed the extension multimethod from
visit
tovisit-spec
(to better support static analysis for arity errors) - the
accept
function is now 4-arity (was 3-arity), taking the options-map as 4th argument - the
spec-tools.json-schema/transform
also has optional 4-arity with the options-map as 4th argument
-
visitor (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, likeclojure.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 onst/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"
- Remove hard dependency on ClojureScript, thanks to Kenny Williams. #52
- Initial release.