Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transformers #116

Merged
merged 21 commits into from
May 8, 2018
Merged
108 changes: 85 additions & 23 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,54 +1,116 @@
# 0.6.2-SNAPSHOT
# 0.7.0-SNAPSHOT

* Fix `rational?` mapping for JSON Schema, fixes [#113](https://github.com/metosin/spec-tools/issues/113)
* Remove `::swagger/extension` expansion in Swagger2 generation.
* Date-conforming is now [ISO8601](https://en.wikipedia.org/wiki/ISO_8601)-compliant on Clojure too, thanks to [Fabrizio Ferrai](https://github.com/f-f).
* Add support for self-contained symmetric spec encoding & decoding, fixes [#96](https://github.com/metosin/spec-tools/issues/96)
* `st/decode` to transform and validate value from external format into valid value defined by the spec
* `st/encode` to transform (without validation) a value into external format
* new `encode` and `decode` namespaces to define transformations for `Spec`s. `json` and `string` are supported by default, but free to extend whatever (`xml`, `avro` etc.)
* type-based encoders can be defined via `st/type-conforming` (defaults no none)
* new `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!`.
* **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](https://github.com/metosin/spec-tools/issues/96).
* renamed ns `spec-tools.conform` into `spec-tools.transform`, covering both encoding & decoding of values
* `st/type-transformer`, supporting both `:type` and `Spec` level transformations
* Spec-driven transformations via keys in `encode` 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.

### Transformer

```clj
(defprotocol Transformer
(-name [this])
(-encoder [this spec value])
(-decoder [this spec value]))
```


### Spec-driven transformations

* use `:encode/*` and `:decode/*` keys from Spec instances to declare how the values should be transformed

```clj
(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 simple keyword, encoded in uppercase in string-mode"
: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)}))

; decode also validates
(st/decode ::spec "kikka")
; => :clojure.spec.alpha/invalid
(st/decode ::spec :kikka)
; :kikka

(st/decode ::spec "kikka" st/string-conforming)
; => :kikka
(as-> "KiKka" $
(st/decode ::spec $))
; :clojure.spec.alpha/invalid

; encode fails if no encoder present
(st/encode ::spec "kikka")
; => :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](https://en.wikipedia.org/wiki/Bijection)?

; encode doesn't validate!
(st/encode ::spec "kikka" st/string-conforming)
; => "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`

; not real bijections (https://en.wikipedia.org/wiki/Bijection)
```clj
(as-> "KikKa" $
(doto $ prn)
(st/encode ::spec $ st/string-conforming)
(st/encode ::spec $ st/string-transformer)
(doto $ prn)
(st/decode ::spec $ st/string-conforming)
(st/decode ::spec $ st/string-transformer)
(doto $ prn)
(st/encode ::spec $ st/string-conforming)
(st/encode ::spec $ st/string-transformer)
(prn $))
; "KikKa"
; "KIKKA"
; :kikka
; "KIKKA"
; => nil
```

### Type-driven transformations

* use `:type` information from Specs (mostly resolved automatically)

```clj
(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](https://hackage.haskell.org/package/unjson-0.15.2.0/docs/Data-Unjson.html):

```clj
(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.1 (19.2.2018)
Expand Down
Loading