Search code examples
clojureclojure.spec

How to remove extra keys from internal map using spec-tools


I'm trying to use clojure.spec and metosin/spec-tools to validate and conform data in my application. After reading spec-tools documentation it was not clear to me how I should wrap my specifications using spec-tools.core/spec so that the conformed data don't have extra keys (it works on a top level map but not on maps on inner structures).

Some code to help clarify the problem:

(ns prodimg.spec
  (:require [clojure.spec.alpha :as s]
            [spec-tools.core :as st]
            [spec-tools.spec :as st.spec]))

(def ^:private not-blank? #(and (string? %)
                                (not (clojure.string/blank? %))))

(s/def :db/id integer?)

(s/def :model.image/id :db/id)
(s/def :model.image/type not-blank?)
(s/def :model.image/product-id :db/id)

(s/def :model.product/id :db/id)
(s/def :model.product/parent-id (s/nilable :db/id))
(s/def :model.product/name not-blank?)
(s/def :model.product/description string?)
(s/def :model.product/price (s/nilable decimal?))

; ----- request specs -----

; create product

(s/def :req.product.create/images (s/* (s/keys :req-un [:model.image/type])))
(s/def :req.product.create/children
  (s/* (s/keys :req-un [:model.product/name :model.product/description]
               :opt-un [:model.product/price])))

(s/def :req.product/create
  (st/spec (s/keys :req-un [:model.product/name :model.product/description]
                   :opt-un [:model.product/price
                            :model.product/parent-id
                            :req.product.create/images
                            :req.product.create/children])))

Now suppose I have the following data that I want to validate/conform:

(def data {:name "Product"
           :description "Product description"
           :price (bigdec "399.49")
           :extra-key "something"
           :images [{:type "PNG" :extra-key "something else"}])

(st/conform :req.product/create data st/strip-extra-keys-conforming)
; below is the result
; {:name "Product"
   :description "Product description"
   :price 399.49M
   :images [{:type "PNG" :extra-key "something else"}]

I tried changing the :req.product.create/images declaration to include st/spec call wrapping the s/* form, or the s/keys form, or both, but that changes didn't change the result.

Any ideas how I can solve this problem?


Solution

  • Strange, with latest version [metosin/spec-tools "0.5.1"] released 2017-10-31 (so before your post), the only change I had to do was to follow the example in the documentation under section Map conforming, which seems to be one of the attempts you already tried: wrap s/keys with st/spec as follows:

    Change

    (s/def :req.product.create/images (s/* (s/keys :req-un [:model.image/type])))
    

    to

    (s/def :req.product.create/images (s/* (st/spec (s/keys :req-un [:model.image/type]))))
    

    and I get the expected output:

    (st/conform :req.product/create data st/strip-extra-keys-conforming)
    =>
    {:description "Product description",
     :images [{:type "PNG"}],
     :name "Product",
     :price 399.49M}