- Add support for or/and key-groups in s/keys when generating JSON-Schema #279
- 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
:bodyparameter name for Swagger, fixes metosin/reitit#399
- You can use
:json-schemaand:swaggerspec 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/mergewith symbol specs. #201. PR #220 by Wanderson Ferreira - Fix decimal coercion for numbers. PR #217 by Wanderson Ferreira
- Fix how
strip-extra-keys-transformerworks 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
Zinstead 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.spellnamespace for closing map specs functionally using spell-spec.- requires explicit dependencies to
com.bhauman/spell-spec&expound spec-tools.spell/closedto close a spec (non recursive)spec-tools.spell/closed-keyto functionally create a closeds/keysspec
- 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 errorsspec-tools.core/mergeis now visitable by Erik Assum.
st/coercedoesn't reverse list order, fixes compojure-api#406- Less verbose
st/Specform, all:spec-tools.parsekeys are stripped, fixes #159 - BREAKING:
nilspecs are allowed, resolved asany? - More robust walker, named specs can be used with
s/or,s/and,s/coll-of,s/map-of,s/tupleands/nilable, fixes #165.- Thanks to Andrew Rudenko and Nicholas Hurden for contributing!
- Both
st/json-transformerandst/string-transformeralso 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-specnow usesst/coerceinstead 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-transformerwork 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
:titleproperty from qualified Spec registry name - All top-level data-specs & nested map data-spec have name derived from
:name, fixes #124 - New
st/coercefunction 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/decodefirst 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/keyand::parse/value s/andands/orare 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
swaggernamespace are merged into Swagger schemas, overriding values fromjson-schemanamespaced 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/extensionexpansion in Swagger2 generation. - Date-conforming is now ISO8601-compliant on Clojure too, thanks to Fabrizio Ferrai.
- new
st/IntoSpecprotocol to convert non-recursively vanillaclojure.specSpecs intost/Spec:s. Used inst/encode,st/decode,st/explain,st/explain-data,st/conformandst/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/Transformerprotocol 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.conformintospec-tools.transform, covering both encoding & decoding of values st/type-transformer, supporting both:typeandSpeclevel transformations- Spec-driven transformations via keys in
encodeanddecodenamespaces. st/encode,st/decode,st/explain,st/explain-data,st/conformandst/conform!take the transformer instance an optional third argumentst/json-transformer,st/string-transformer,strip-extra-keys-transformerandfail-on-extra-keys-transformerare 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
:typeinformation 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.conformjust transform, dont' validate. Fixes #92. Thanks to Benjamin Albrecht - Fixed
s/gentriggersIllegalArgumentExceptionfor 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-ofdata-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]})
; trueds/specsupports 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})
; trueds/specoption:nameis 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/mergethat 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
:requiredfields 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-specshould recurse withspec-tools.visitor/visitinstead 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"-
orandandkeys are parsed correctly for JSON Schema & Swagger, Fixes #79 -
BREAKING:
spec-tools.typeis nowspec-tools.parsewith public api of:parse-spec: given a spec name, form or instance, maybe returns a spec info map with resolved:typeand optionally other info, e.g.:keys,:keys/reqand:keys/optfors/keysspecs.parse-form: multimethod to parse info out of a form
-
Spec Records of
s/andare 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-specfails 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.specpredicate symbols intoclojure.corecounterparts for JSON Schema / Swagger mappings.
- resolve
:typefrom 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/transformto transform Specs into Swagger Parameter Objects and Schema Objectsspec-tools.swagger.core/swagger-specto 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
visittovisit-spec(to better support static analysis for arity errors) - the
acceptfunction is now 4-arity (was 3-arity), taking the options-map as 4th argument - the
spec-tools.json-schema/transformalso 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-namebut non-private & understands Spec Records. -
added
spec-tools.core/spec-description, to resolve spec description, understands Spec Records. -
JSON Schema generation set
:titlefor Object Schemas based onst/spec-name. -
s/cat&s/altdon't set:minItemsand:maxItemsas 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/namefrom 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.alphaetc.
-
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.