(Question also asked on Slack #reitit channel, but asking here so I can easily post code)
I am trying to figure out how to send a Clojure data structure as part of a :multipart
file upload to a Reitit route. The code below works, but I can only submit native values (like a string or number) as the :foo
parameter and use simple predicates like string?
or int?
.
If I try {:bar string?}
as spec for the :foo
parameter and submit (clj->js {:bar "baz"})
as "foo"
key, I get the exception "java.lang.ClassCastException: class java.lang.Character cannot be cast to class java.util.Map$Entry (java.lang.Character and java.util.Map$Entry are in module java.base of loader 'bootstrap')"
, full stack trace below.
Is there a way to get this working?
;; Reitit route
["/api5/test-upload"
{:name :test-upload
:coercion reitit.coercion.malli/coercion
:post {:summary "upload a file"
:parameters {:multipart {:foo string? ;; Works, but cannot be {:foo {:bar string?}}
:file {:filename string?
:content-type string?
:size int?
:tempfile [:fn (partial instance? File)]}}}
:responses {200 {:body {:name string?, :size int? :foo any?}}}
:handler (fn [{{{:keys [file foo]} :multipart} :parameters}]
{:status 200
:body {:name (:filename file)
:size (:size file)
:foo foo}})}}]
;; Clojurescript
(rf/reg-event-fx
:upload-file
(fn upload-image [_ [_ {:keys [body] :as something}]]
{:http-xhrio {:method :post
:uri "/api5/test-upload"
:timeout 8000
:body body
:response-format (transit/transit-response-format)
:on-success [:on-success]
:on-failure [:on-failure]}}))
(defn upload-file
[]
(let [el (.getElementById js/document "file-input")
file (aget (.-files el) 0)
form-data (js/FormData.)
_ (.append form-data "file" file)
_ (.append form-data "foo" "bar") ;; Works, but cannot be replace "bar" with for example (clj->js {:bar "baz"})
submit-params {:body form-data}]
(rf/dispatch [:upload-file submit-params])))
Exception when trying to submit a map
java.lang.Thread.run Thread.java: 833
org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run EnhancedQueueExecutor.java: 1449
org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask EnhancedQueueExecutor.java: 1558
org.jboss.threads.EnhancedQueueExecutor.safeRun EnhancedQueueExecutor.java: 2019
org.jboss.threads.ContextClassLoaderSavingRunnable.run ContextClassLoaderSavingRunnable.java: 35
io.undertow.server.HttpServerExchange$1.run HttpServerExchange.java: 841
io.undertow.server.Connectors.executeRootHandler Connectors.java: 387
io.undertow.server.session.SessionAttachmentHandler.handleRequest SessionAttachmentHandler.java: 68
org.projectodd.wunderboss.web.undertow.async.websocket.UndertowWebsocket$2.handleRequest UndertowWebsocket.java: 109
immutant.web.internal.undertow/create-http-handler/reify/handleRequest undertow.clj: 239
bass4.db-update.core/updates-mw/fn core.clj: 115
bass4.middleware.core/wrap-mw-fn/fn core.clj: 67
...
bass4.middleware.request-logger/wrap-logger request_logger.clj: 80
bass4.middleware.core/wrap-mw-fn/fn core.clj: 67
...
bass4.middleware.errors/catch-internal-error-mw errors.clj: 34
bass4.middleware.errors/catch-request-error errors.clj: 22
bass4.middleware.core/wrap-mw-fn/fn core.clj: 67
...
bass4.embedded.middleware/embedded-iframe middleware.clj: 147
ring.middleware.x-headers/wrap-x-header/fn x_headers.clj: 22 (repeats 3 times)
ring.middleware.not-modified/wrap-not-modified/fn not_modified.clj: 61
ring.middleware.default-charset/wrap-default-charset/fn default_charset.clj: 31
ring.middleware.content-type/wrap-content-type/fn content_type.clj: 34
ring.middleware.resource/wrap-resource-prefer-resources/fn resource.clj: 25
ring.middleware.absolute-redirects/wrap-absolute-redirects/fn absolute_redirects.clj: 47
ring.middleware.cookies/wrap-cookies/fn cookies.clj: 214
ring.middleware.params/wrap-params/fn params.clj: 75
ring.middleware.multipart-params/wrap-multipart-params/fn multipart_params.clj: 171
ring.middleware.nested-params/wrap-nested-params/fn nested_params.clj: 89
ring.middleware.keyword-params/wrap-keyword-params/fn keyword_params.clj: 53
ring.middleware.webjars/wrap-webjars/fn webjars.clj: 41
bass4.middleware.core/wrap-mw-fn/fn core.clj: 67
...
bass4.middleware.core/security-headers-mw core.clj: 80
bass4.middleware.core/wrap-mw-fn/fn core.clj: 67
...
bass4.middleware.lockdown/lockdown-mw lockdown.clj: 36
bass4.middleware.core/no-cache-headers/fn core.clj: 71
bass4.middleware.core/wrap-mw-fn/fn core.clj: 67
...
bass4.db.middleware/inject-db middleware.clj: 43
bass4.middleware.core/wrap-mw-fn/fn core.clj: 67
...
bass4.middleware.file-php/File-php file_php.clj: 56
bass4.middleware.core/wrap-mw-fn/fn core.clj: 67
...
bass4.middleware.lockdown/sms-lockdown-mw lockdown.clj: 55
bass4.middleware.core/wrap-mw-fn/fn core.clj: 67
...
bass4.services.attack-detector/attack-detector-mw attack_detector.clj: 271
bass4.embedded.middleware/wrap-embedded-request/fn middleware.clj: 142
bass4.route-rules/wrap-route-mw/fn route_rules.clj: 112 (repeats 2 times)
bass4.middleware.core/embedded-admin-routes/match-embedded-routes/fn core.clj: 157
bass4.session.storage/inject-session/fn storage.clj: 104
ring.middleware.session/wrap-session/fn session.clj: 108
bass4.env/wrap-prone-debug-exceptions/fn env.clj: 18
bass4.env/wrap-session-modification/fn env.clj: 34
bass4.env/session-modification-wrapper env.clj: 29
selmer.middleware/wrap-error-page/fn middleware.clj: 18
ring.middleware.reload/wrap-reload/fn reload.clj: 39
bass4.session.timeout/inject-participant-timeouts/fn timeout.clj: 224
bass4.session.timeout/inject-participant-timeouts* timeout.clj: 211
bass4.session.timeout/wrap-session-hard-timeout/fn timeout.clj: 202
bass4.session.timeout/wrap-session-hard-timeout* timeout.clj: 197
bass4.session.timeout/no-hard-timeout-response timeout.clj: 184
bass4.api.core/api5-routes-mw/fn core.clj: 124
...
reitit.ring/ring-handler/fn ring.cljc: 329
ring.middleware.params/wrap-params/fn params.clj: 75
muuntaja.middleware/wrap-format-negotiate/fn middleware.clj: 96
muuntaja.middleware/wrap-format-response/fn middleware.clj: 132
muuntaja.middleware/wrap-format-request/fn middleware.clj: 114
reitit.ring.coercion/fn/fn/fn coercion.cljc: 64
reitit.ring.middleware.multipart/compile/fn/fn/fn multipart.clj: 45
reitit.ring.middleware.multipart/coerced-request multipart.clj: 28
reitit.coercion/coerce-request coercion.cljc: 111
clojure.core/reduce-kv core.clj: 6919
clojure.core.protocols/fn/G protocols.clj: 175
clojure.core/fn core.clj: 6908
...
reitit.coercion/coerce-request/fn coercion.cljc: 113
reitit.coercion/request-coercer/fn coercion.cljc: 84
reitit.coercion.malli/-coercer/fn malli.cljc: 60
reitit.coercion.malli/-coercer/->coercer/reify/-decode malli.cljc: 46
malli.core/-comp/fn core.cljc: 177
malli.core/-guard/fn core.cljc: 149
malli.core/-comp/fn core.cljc: 177
malli.core/-map-transformer/child-transformer/fn core.cljc: 518
malli.transform/strip-extra-keys-transformer/fn/fn transform.cljc: 368
clojure.core/reduce-kv core.clj: 6919
clojure.core.protocols/fn/G protocols.clj: 175
clojure.core/fn core.clj: 6888
clojure.core/reduce core.clj: 6886
clojure.core.protocols/fn/G protocols.clj: 13
clojure.core.protocols/fn protocols.clj: 75
clojure.core.protocols/seq-reduce protocols.clj: 31
clojure.core.protocols/fn/G protocols.clj: 19
clojure.core.protocols/fn protocols.clj: 153
clojure.core/fn/fn core.clj: 6899
java.lang.ClassCastException: class java.lang.Character cannot be cast to class java.util.Map$Entry (java.lang.Character and java.util.Map$Entry are in module java.base of loader 'bootstrap')