Skip to content

Multi-value query params are being duplicated #5

Description

@cmateiasmypulse

Hi,

I am having an issue with the query parameter values being duplicated when building a liberator resource using with-json-mixin. I'm not entirely sure if that's a bug or not, but hopefully we can get more details on this.
What happens is that when a get request is being made with a multi-value query string ie https://some.url?thing=1&thing=2 I would have expected the parsed params to be something like [1 2] but what I see instead is [1 2 1 2 1 2]. As you would expect, if there are any specs configured the request would not be valid.

To have a better understanding on what is happening I have the following tests:

(deftest with-params-parsed-as-json
  (testing "using with-json-mixing"
    (let [resource
          (->
            (l/build-resource
              (json/with-json-mixin)
              {:allowed-methods
               [:get]

               :handle-ok
               (fn [{:keys [request]}]
                 (let [params (:params request)]
                   {:correct-params
                    (and
                      (= (:thing1 params) [1 2 3])
                      (= (:thing2 params)
                        {:key-1 "value"})
                      (= (:thing3 params) [1 2 1 2 1 2]))}))})
            (keyword-params/wrap-keyword-params)
            (params/wrap-params))
          request (->
                    (ring/request :get "/")
                    (ring/header "Content-Type" json/json-media-type)
                    (ring/query-string
                      "thing1=[1,2,3]&thing2={\"key1\":\"value\"}&thing3=1&thing3=2"))
          response (call-resource resource request)]
      (is (= 200 (:status response)))
      (is (= {:correct-params true}
            (:body response)))))

  (testing "without json encoder/decoder"
    (let [resource
          (->
            (l/build-resource
              (json/with-json-media-type)
              (json/with-body-parsed-as-json)
              (json/with-params-parsed-as-json)
              {:allowed-methods
               [:get]

               :handle-ok
               (fn [{:keys [request]}]
                 (let [params (:params request)]
                   {:correct-params
                    (and
                      (= (:thing1 params) [1 2 3])
                      (= (:thing2 params)
                         {:key-1 "value"})
                      (= (:thing3 params) [1 2]))}))})
            (keyword-params/wrap-keyword-params)
            (params/wrap-params))
          request (->
                    (ring/request :get "/")
                    (ring/header "Content-Type" json/json-media-type)
                    (ring/query-string
                      "thing1=[1,2,3]&thing2={\"key1\":\"value\"}&thing3=1&thing3=2"))
          response (call-resource resource request)]
      (is (= 200 (:status response)))
      (is (= {:correct-params true}
             (:body response))))))

As you can see from above the issue occurs when using with-json-mixin, but when leaving out with-json-encoder and with-json-decoder everything seems to be working as expected. It may seem as the issue is somehow related to those 2 mixings even though they're not doing a lot apart from adding a map to the context. Something strange seems to happen within the merge-actions function as this is the point where those params values are being duplicated

(defn merge-actions
  [left right]
  (fn [context]
    (letfn [(execute-and-update [context f]
              (liberator/update-context context (f context)))]
      (let [left-result (execute-and-update context
                          (liberator-util/make-function left))
            right-result (execute-and-update left-result
                           (liberator-util/make-function right))]
        right-result))))

and more specifically within the liberator update-context which seems to concatenate the vectors within the params unless it's explicitly decorated with the replace metadata as mentioned here. The params are decorated with the replace metadata, but within with-params-parsed-as-json that's the last mixin to be added as part of with-json-mixin and by then the params values have already been duplicated.

As a workaround I've created an internal mixin that takes care of decorating the params with the replace metadata and placed it before with-json-mixin:

(defn- with-replaceable-params []
  {:initialize-context
   (fn [{:keys [request]}]
     {:request {:params (with-meta (:params request) {:replace true})}})})

(defn resource-handler
  [dependencies & {:as overrides}]
  (build-resource
    (with-replaceable-params)
    (with-json-mixin dependencies)    
    overrides))

It would be great to see your thoughts on this, and if you need more details please let me know.

Thanks,
Cris

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions