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?)
.
there is also spec-tools.spec
with most predicates wrapped.
ah, i tried spec-tools.spec/keyword?
oh, that should work too.
:thinking_face: perhaps because it's nested inside another (non-spec-tools-wrapped spec)
it should be ok. Can you paste an example?
(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\""
ping @mgrbyte
It seems to be working correctly?
It's fine for decoding, not sure how to encode
i.e given JSON: {"species/id": "species/c-elegans"} want to turn it back into the clojure form {:species/id :species/c-elegans}
ok, just a sec.
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}
or:
am just trying out those samples now
@ikitommi 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.
@ikitommi 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...
np. hope tou get it working
@ikitommi 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)
/me goes to grep the docs
metosin/c2 on github could be turned into a sample with keywords.
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
am sure I'm doing something dumb and it's not capi/spec-tools
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. 🎈
No worries, thank you 🙂
enjoy the vappu! (had to google 🎈 ) 😁
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)
which leads me to think that that keywords cannot be "round-tripped":
(m/decode mformats "application/json"
"{\"species/id\": \"test/this\"}")
gives -> #:species{:id "test/this"}
:thinking_face: