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

Pure map schemas are broken #341

Open
lkonstantinov opened this issue Sep 25, 2017 · 1 comment
Open

Pure map schemas are broken #341

lkonstantinov opened this issue Sep 25, 2017 · 1 comment

Comments

@lkonstantinov
Copy link

lkonstantinov commented Sep 25, 2017

Not sure if thats by design, since Swagger doesn't seem to represent well pure maps, i.e. {s/Str s/Str}, but handling of those is broken atm.

What happens is that the default implementation forces Cheshire to keywordize all incoming maps while deserializing, so instead of {s/Str s/Str}, the request winds up as {s/Keyword s/Str} :(

The good (for my use case) news is that since Compojure API is almost infinitely extensible ;) I was able to:

  1. Override the JSON deserialization
  2. Plug a custom coercer

Something like this (only the important bits):

(ns blah.core
  (:require
           ;; just to show what cs and cc below resolve to
            [compojure.api.coercion.core :as cc]
            [compojure.api.coercion.schema :as cs])

;; coercion wiring

(defn keywordize-some
  "Keywordizes only the top-level map keys that are in the ks collection"
  [ks m]
  (reduce (fn [nm k]
            (if (keyword? k)
              (if-let [existing (get nm k)]
                nm
                (let [k-str (name k)]
                  (if-let [ex2 (get nm k-str)]
                    (-> nm
                        (assoc k ex2)
                        (dissoc k-str))
                    nm)))
              nm))
          m
          ks))

(defn json-coercer [schema x]
  "Coerces a map value with a map schema - otherwise falls back to the original coercer"
  (if (map? x)
    (keywordize-some (keys schema) x)
    (if-let [fallback (cs/json-coercion-matcher schema)]
      (fallback x)
      x)))

(defn jcm [schema]
  (if (map? schema)
    (partial json-coercer schema)
    (cs/json-coercion-matcher schema)))

(defmethod cc/named-coercion :json [_] (cs/create-coercion {:body     {:default (constantly nil)
                                                                       :formats {"application/json"    jcm
                                                                                 "application/msgpack" jcm
                                                                                 "application/x-yaml"  jcm}}
                                                            :string   {:default cs/string-coercion-matcher}
                                                            :response {:default (constantly nil)}}))

(api
    ....
     ;; wire a non-keywordizing JSON deserializer
     ;; the actual keywordization will be done in a custom coercer
     :formats    (-> (:formats compojure.api.api/api-defaults)
                     (assoc-in [:formats muuntaja.format.json/json-type] {:decoder [muuntaja.format.json/make-json-decoder {}]
                                                                          :encoder [muuntaja.format.json/make-json-encoder]}))

     ;; our custom coercer registered via defmethod above
     :coercion   :json
    ...
     }
@opqdonut
Copy link
Member

opqdonut commented Feb 8, 2018

I'm having this problem too. It seems to me src-coerce! passes true for keywordize? e.g. in restructure-param :body

Unfortunately we can't rely on schema to coerce strings to keywords when needed: plumatic/schema#90

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants