This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2018-04-30
Channels
- # aleph (12)
- # beginners (23)
- # boot (12)
- # cider (40)
- # cljs-dev (8)
- # cljsrn (20)
- # clojars (1)
- # clojure (122)
- # clojure-canada (2)
- # clojure-dev (21)
- # clojure-gamedev (2)
- # clojure-italy (3)
- # clojure-nl (12)
- # clojure-norway (1)
- # clojure-sanfrancisco (3)
- # clojure-spec (59)
- # clojure-uk (114)
- # clojurescript (50)
- # clojurex (1)
- # cursive (2)
- # datascript (2)
- # datomic (26)
- # emacs (5)
- # fulcro (19)
- # garden (1)
- # hoplon (54)
- # leiningen (42)
- # luminus (14)
- # off-topic (24)
- # om (5)
- # onyx (7)
- # re-frame (2)
- # reagent (31)
- # reitit (3)
- # ring-swagger (39)
- # shadow-cljs (8)
- # sql (3)
- # tools-deps (13)
Hi everyone! I send a JSON-encoded request, with no content-type headers. That gives an error
"errors":"(not (map? nil))","type":"compojure.api.exception/request-validation","coercion":"schema","value":null,"in":["request","body-params"]}
. However, adding a content-type "application/json" (using Curl) returns a valid response.
How can I have ring-swagger encode a request body to JSON, if no content-type is specified?does anyone have an example of a compojure-api app that uses json as API format, spec coercion, and a spec that uses a keyword? Trying to grok how to translate between clojure and json when I have for example (s/def :my/thing keyword?)
.
Current attempt uses (muuntaja/encode m "application/json data)
which produces "my/thing", but obviously the coercion doesn't pick that up... do I need an extra transformation mw? :thinking_face:
@mgrbyte Rich didn't mean specs to be extended, so you need to wrap specs into spec-tools.core/spec
to it to work. e.g. (s/def :my/thing (st/spec keyword?)
.
:thinking_face: perhaps because it's nested inside another (non-spec-tools-wrapped spec)
Have just done a WIP commit..
https://github.com/WormBase/names/tree/feature/json-api-format
Currently working on one case:
lein test :only integration.test-new-gene
Data being sent via the test client api here:
https://github.com/WormBase/names/blob/feature/json-api-format/test/wormbase/api_test_client.clj#L21
Specs are defined in src/wormbase/specs/{species,gene}.clj
endpoints defined in src/wormbase/names/gene.clj
Top level handler in src/wormbase/names/service.clj
Basically I had an EDN only api, but being asked to provide JSON too by my peers, as they don't want to use an EDN client library/are concerned about api adoption/interop
the spec that doesn't seem to be coercing is :species/id
:
https://github.com/WormBase/names/blob/feature/json-api-format/src/wormbase/specs/species.clj#L14
which is used via:
https://github.com/WormBase/names/blob/feature/json-api-format/src/wormbase/specs/gene.clj#L21
via top level spec:
https://github.com/WormBase/names/blob/feature/json-api-format/src/wormbase/specs/gene.clj#L55
for the above test
thanks for the detailed info, should be easy to find the cause from that. There is a Vappu just about to begin here in Finland, so can poke it later. 🎈
FWIW, here's a simpler reproduction:
(require '[clojure.spec.alpha :as s])
(require '[spec-tools.core :as stc])
(require '[spec-tools.spec :as sts])
(require '[compojure.api.coercion :as cac])
(require '[compojure.api.request :as car)
(s/def :species/id stc/keyword?)
(def c! #(cac/coerce-request! :species/id :body-params :body false false %))
(def m {:body-params {:species/id "species/c-elegans"}
::car/coercion :spec})
(c! m)
"fails" with:
ExceptionInfo Request validation failed: #compojure.api.coercion.core.CoercionError{:spec #Spec{:form (clojure.spec.alpha/keys :req [:species/id]), :type :map, :keys #{:species/id}, :keys/req #{:species/id}}, :problems ({:path [:species/id], :pred clojure.core/keyword?, :val "species/c-elegans", :via [:species/id], :in [:species/id]})} clojure.core/ex-info (core.clj:4739)
(ns user)
(require '[compojure.api.sweet :refer :all])
(require '[ring.util.http-response :refer :all])
(require '[clojure.spec.alpha :as s])
(require '[spec-tools.spec :as sts])
(s/def :species/id sts/keyword?)
(def app
(api
{:coercion :spec}
(POST "/body" []
:body-params [species/id :- :species/id]
:return :species/id
(prn id)
(ok id))))
(->
{:request-method :post
:uri "/body"
:body-params {:species/id "species/c-elegans"}
:headers {"content-type" "application/json"}}
(app)
:body
slurp)
; :species/c-elegans
; => "\"species/c-elegans\""
i.e given JSON: {"species/id": "species/c-elegans"} want to turn it back into the clojure form {:species/id :species/c-elegans}
or else am looking for a pattern of how data is sent to the app in e.g a post request
(ns user)
(require '[muuntaja.core :as m])
(require '[compojure.api.sweet :refer :all])
(require '[ring.util.http-response :refer :all])
(require '[clojure.spec.alpha :as s])
(require '[spec-tools.core :as st])
(require '[spec-tools.spec :as sts])
(s/def :species/id sts/keyword?)
(s/def :species/map (s/keys :req [:species/id]))
(def app
(api
{:coercion :spec}
(POST "/body" []
:body-params [species/id :- keyword?]
:return :species/map
(ok {:species/id id}))))
(->
{:request-method :post
:uri "/body"
:body-params {:species/id "species/c-elegans"}
:headers {"content-type" "application/json"}}
(app)
:body
slurp
(->> (m/decode (m/create) "application/json"))
(as-> value
(st/conform :species/map value st/json-conforming)))
; => #:species{:id :species/c-elegans}
(ns user)
(require '[muuntaja.core :as m])
(require '[compojure.api.sweet :refer :all])
(require '[ring.util.http-response :refer :all])
(require '[clojure.spec.alpha :as s])
(require '[spec-tools.core :as st])
(require '[spec-tools.spec :as sts])
(s/def :species/id sts/keyword?)
(s/def :species/map (s/keys :req [:species/id]))
(def app
(api
{:coercion :spec}
(POST "/body" []
:body [body :species/map]
:return :species/map
(ok body))))
(->
{:request-method :post
:uri "/body"
:body-params {:species/id "species/c-elegans"}
:headers {"content-type" "application/json"}}
(app)
:body
(as-> value
(m/decode (m/create) "application/json" value)
(st/conform :species/map value st/json-conforming)))
; => #:species{:id :species/c-elegans}
@U055NJ5CC ah, so I was missing the conforming step:
(st/conform :species/map value st/json-conforming)))
also, I've not been using spec-tools for conforming thus far (using clojure.spec directly)
thank you v. much; so I should probably change all my handlers to manually conform using st/json-conforming thinking_face will think about how to incorporate this.
@U055NJ5CC Sorry, I'm quite confused now. Running the last sample you posted, I can see it works as I'd expect. My app must be doing something different/have a bug somewhere that I can't see. thanks again for your help, I'll keep looking...
@U055NJ5CC found the error.
Thought json-conforming was the default (and it is)
My mistake was trivial; was using :body
instead of :body-params
in my resource definition. (I'll admit to be not entirely clear on the diference between using the two)
(ns user)
(require '[muuntaja.core :as m])
(require '[compojure.api.sweet :refer :all])
(require '[ring.util.http-response :refer :all])
(require '[clojure.spec.alpha :as s])
(require '[spec-tools.core :as st])
(require '[spec-tools.spec :as sts])
(s/def :species/id sts/keyword?)
(s/def :species/map (s/keys :req [:species/id]))
(def app
(api
{:coercion :spec}
(POST "/body" []
:body [body :species/map]
:return :species/map
(ok body))))
(->
{:request-method :post
:uri "/body"
:body-params {:species/id "species/c-elegans"}
:headers {"content-type" "application/json"}}
(app)
:body
(as-> value
(m/decode (m/create) "application/json" value)
(st/conform :species/map value st/json-conforming)))
; => #:species{:id :species/c-elegans}