Clojure ring wrap-json-params messing up JSON arrays - clojure

I'm currently doing some REST API stuff in clojure, and I am using the ring.middleware.format library with compojure to transform JSON to and from clojure data structures.
I am having a huge issue, in that and JSON posted to the ring app will have all arrays replaced with the first item that was in the array. I.E. it will turn this JSON posted to it from
{
"buyer":"Test Name",
"items":[
{"qty":1,"size":"S","product":"Red T-Shirt"},
{"qty":1,"size":"M","product":"Green T-Shirt"}
],
"address":"123 Fake St",
"shipping":"express"
}
to this
{
"buyer": "Test Name",
"items": {
"qty": 1,
"size": "M",
"product": "Green T-Shirt"
},
"address": "123 Fake St",
"shipping": "express"
}
It does it for any arrays, including when an array is the root element.
I am using the following code in clojure to return the json:
(defroutes app-routes
(GET "/"
[]
{:body test-data})
(POST "/"
{data :params}
{:body data}))
;{:body (str "Printing " (count (data :jobs)) " jobs")}))
(def app
(-> (handler/api app-routes)
(wrap-json-params)
(wrap-json-response)))
The GET route has no issues with arrays and outputs properly, so it has to be either the way I am getting the data or the wrap-restful-params middleware.
Any ideas?

I am having similar problem with ring-json-params. So I ended up using raw request body and parsing the JSON string myself.
I used the folliwng code:
(defroutes app-routes
(POST "/"
{params :body}
(slurp params)))
I use the clj-json.core library for parsing JSON.
Hope this helps. If you figured out a better way then please share. I am a Clojure/Compojure newbie!!!

I ran into this problem recently and actually figured out what the problem is: it's that you have wrap-nested-params middleware getting evaluated after your wrap-json-params middleware, which causes objects stored in JSON arrays/Clojure vectors to be flattened by grabbing the first element contained within.
user=> bod-map
{:address "100 Bush Street", :contacts [{:name "Dudely Mcdooderson", :title "engineer", :tax_id "555"}], :name "Dudely Inc.", :tax_id "5234234"}
user=> (ring.middleware.nested-params/nested-params-request {:params bod-map})
{:params {"tax_id" "5234234", "name" "Dudely Inc.", "contacts" {:name "Dudely Mcdooderson", :title "engineer", :tax_id "555"}, "address" "100 Bush Street"}}
You can get around this by just making sure that wrap-nested-params is evaluated first in your middleware ordering.

I know it's been some time but I did just stumble upon the same issue. The only way I got it to work was to use the ring.middleware.format/wrap-restful-format before the compojure.handler/api.
I'm not sure why that is but if I put the compujure.handler/api wrapper first, it messes up the array parameteres

Related

Clojure, re-graph fetched data from graphql successfully, but callback didn't activate

So I use re-graph version 0.1.11 and I try fetching data from the endpoint. After fetching data, I checked network tab in the browser and I found the expected data after that it should activate my callback function, but it doesn't seem to work (but sometimes it works and after refreshing the page a few times it doesn't work again). Here is the code.
;; how I init re-graph
(rf/dispatch [::re-graph/init
::conf/graphql-client-name
{:ws-url url
:http-url url
:ws-reconnect-timeout 500
:resume-subscriptions? true}])
(re-frame.core/reg-event-fx
::fetch-expected-data
(fn [cofx event]
(let [app-db (:db cofx)
some-params (-> event second (cljs.core/js->clj :keywordize-keys true))
token (-> app-db (lens/get-in (auth-db/lens-token :group-level-x)))]
(re-frame.core/dispatch
[:re-graph.core/query
::conf/graphql-client-name
"query findExpectedData($query: FetchExpectedDataInput!, $token: String!) {
findExpectedData(query: $query, token: $token){
value1
value2
...
}
}"
{:query some-params
:token token}
;; this is where the problem occurs
;; even though i found the data in the network tab, but
;; this callback doesn't seem to work (sometimes it works sometimes it doens't)
[::fetched-data-completed]]))))
(re-frame.core/reg-event-fx
::fetched-data-completed
(fn [cofx [_ {:keys [data errors] :as payload}]]
(let [app-db (:db cofx)
error-message (-> errors :errors first :message)]
(if (or (nil? errors) (empty? errors))
(do (bla bla when success))
(pr error-message)))))
I'm stuck with this problem for a few months. maybe because I fetch a lot of data at the same time? or could be something else anyone knows?. By the way the actual code I use defmacro, but it works the same way as the above code.
So I managed to find the answer to my own question. It seems like app-db has not been initialized properly so I fixed that problem and everything works fine. hope it helps someone who struggle with this problem.

Using clj-http.cookies to set cookie in request

I am trying to create a client cookie for clj-http so that I can set a cookie for a request. Currently I have the cookie data in the format [cookie-name cookie-val]
I then call to-basic-client-cookie like so:
(clj-http.cookies/to-basic-client-cookie [cookie-name cookie-val])
However, this produces a null pointer exception. Anyone know if I am calling this wrong? Pretty new to clojure, so sorry if this is a dumb question.
It's looks like the value should be a map, and the map must at least contain the :value key:
user> (cookies/to-basic-client-cookie ["foo" {:value "bar"}])
#object[org.apache.http.impl.cookie.BasicClientCookie2 0x1d0338fc "
[version: 0][name: foo][value: bar][domain: null][path: null][expiry: null]"]
In the code you can see all the other possible cookie content keys:
(defn ^BasicClientCookie2
to-basic-client-cookie
"Converts a cookie seq into a BasicClientCookie2."
[[cookie-name cookie-content]]
(doto (BasicClientCookie2. (name cookie-name)
(name (:value cookie-content)))
(.setComment (:comment cookie-content))
(.setCommentURL (:comment-url cookie-content))
(.setDiscard (:discard cookie-content true))
(.setDomain (:domain cookie-content))
(.setExpiryDate (:expires cookie-content))
(.setPath (:path cookie-content))
(.setPorts (int-array (:ports cookie-content)))
(.setSecure (:secure cookie-content false))
(.setVersion (:version cookie-content 0))))

Get information out of go block

I have the following ClojureScript code to make a POST request:
(defn perform-post [resource]
"Performs a post and returns the body :)"
(go (let [response (<! (http/post resource))]
(:body response))))
When I make a call to a resource which returns a number
(js/console.log (perform-post post-create-recipe-url))
This prints:
bufObject { buf={...}, n=1, cljs$lang$protocol_mask$partition0$=2,
more...}
bufObject { head=1, tail=0, length=1, meer...}
arr
["6276677237104933520", undefined]
I want to obtain the "6276677237104933520" (the post body) information as a "return" value.
How can I accomplish this? I tried <!! but it does not work since it is not defined.
Blocking semantics (<!!) is not available on ClojureScript platform.
You can retrieve value from a channel only within go block:
(go (js/console.log (<! (perform-post post-create-recipe-url))))

Use an empty path parameter in nested Compojure routes

How to match URLs without a trailing backspace in Compojure nested contexts?
Problem I'm trying to solve:
I want to match the following routes:
/users/
/users/:user-id
/users/:user-id/resources/
The first to access all users,
second for a single user entity,
last for all resources of a given user.
Using the following defroutes works:
(defroutes
(GET "/users/" ...)
(GET "/users/:user-id" ...)
(GET "/users/:user-id/resources/" ...))
However, as my API grow I want to use contexts to clarify the code.
(defroutes
(context "/users"
(users-routes) ;; All users operations
(context "/:user-id" [user-id]
(user-routes user-id) ;; All per-user operations
(context "/resources" []
(resources-routes user-id))))) ;; All user - resources operations
Which means that user-routes should look like:
(defn user-routes [user-id]
(routes
(GET "" [uid] ...)))
Which gives, at compile time:
clojure.lang.ExceptionInfo: Parse error in route string
data: {:failure
{:index 0,
:reason
[{:tag :regexp, :expecting #"(https?:)?//"}
{:tag :string, :expecting ":"}
{:tag :string, :expecting "*"}
{:tag :regexp, :expecting #"\\."}
{:tag :regexp, :expecting #"(:[^\p{L}_*{}\\]|[^:*{}\\])+"}],
:line 1,
:column 1,
:text nil}}
clojure.lang.Compiler$CompilerException: clojure.lang.ExceptionInfo: Parse error in route string {:failure Parse error at line 1, column 1:
nil
^
Expected one of:
#"(https?:)?//"
":"
"*"
#"\\."
#"(:[^\p{L}_*{}\\]|[^:*{}\\])+"
}, compiling:(/Users/laurent/dev/suricate/web/src/web/routes/api.clj:134:5)
I believe this is due to the way urls are parsed (with Clout) hence my question,
What's the correct way to match URLs without a trailing backspace in Compojure nested contexts?

Clojure read from a file and add to JSON object

I have a simple text file which contains simple email strings such as:
email1#example.com
email2#example.com
email3#example.com
email4#example.com
I want to be able to return the contents of this file as a json response through my REST api which is a simple GET request.
Is there a way I can read the file line by line (I can do that) and append to a JSON object such that I can easily render the contents on a webpage.
{
"emails": [
"email1#example.com",
"email2#example.com",
"email3#example.com",
"email4#example.com"
]
}
I want my REST API to look like:
(GET "/emails" [] {
"emails": [
"email1#example.com",
"email2#example.com",
"email3#example.com",
"email4#example.com"
]
})
but I want to render the JSON upon request as the file can be modified.
You can use the following:
(GET "/emails" []
(clojure.pprint/cl-format nil
"{\"emails\": [~{~S~^,~}]}"
(clojure.string/split-lines (slurp "/path/to/addresses.txt"))))
I came up with the following solution using the Chesire library
(GET "/emails" []
(generate-string {:emails (clojure.string/split-lines (slurp "/path/to/file"))} {:pretty true}))