:body parameter name for Swagger, fixes https://github.com/metosin/reitit/issues/399:json-schema and :swagger spec data to overwrite the generated
JSON Schema and Swagger, respectively. PRs #229 by Wanderson Ferreira
and #231 by Tommi Reimanbytes?. PR #230 by Joe Lanesequential?. #193. PR #227 by Wanderson Ferreiraspec-tools.core/merge with symbol specs. #201. PR #220 by Wanderson Ferreirastrip-extra-keys-transformer works with s/or. #178. PR #219 by Wanderson Ferreiradecimal? coercion by Wanderson FerreiraZ instead of +0000. This makes the output RFC3339-compatible and keeps it ISO-8601-compatible.;; 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"
spec-tools.spell namespace for closing map specs functionally using spell-spec.
com.bhauman/spell-spec & expoundspec-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/values/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-conformingst/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? These fine people already did:
Tommi Reiman, Miikka Koskinen, Vinicius Correa, c0rrzin, Jordan Biserkov & kalekaleEdit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |