aarkerio
aarkerio

Reputation: 2364

Lacinia and re-graph incompatible headers

I'm using the last available lacinia version: "0.36.0-alpha-3" with Luminus (Ring+reitit), but this version asks for a specific header:

$ curl 'http://localhost:3000/api/graphql' -X POST --data "{test_by_id(id: 5) { title } }" -H 'Content-Type: application/graphql'

that request works fine, but without "'Content-Type: application/graphql'" the request wouldn't work. So I need to define my re-graph init vector like:

  [::re-graph/init
    {:ws-url                  nil 
     :http-url                "http://localhost:3000/api/graphql"
     :http-parameters         {:with-credentials? false
                               :headers {"Content-Type" "application/graphql"}
                               }
     :ws-reconnect-timeout    nil
     :resume-subscriptions?   false   
     :connection-init-payload {}}]

but putting that header makes re-graph unable to work properly:

{"errors":[{"message":"Failed to parse GraphQL query.","extensions":{"errors":[{"locations":[{"line":1,"column":null}],"message":"mismatched input '\"query\"' expecting {'query', 'mutation', 'subscription', 

it looks like re-graph sends and receives data using "application/json" header, so lacinia asks for some type of header but re-graph can't work with that option.

Upvotes: 1

Views: 312

Answers (2)

Perry
Perry

Reputation: 11

I had the same problem, and I think I got a solution for it. re-frame requests follows the Apollo Specification, as stated by @aarkerio. Here is the code to keep the original endpoint working with the origina specification, and allow it to respond to re-frame requests. This will make the endpoint respond to Graphiql request (from your http://localhost:3000/graphiql route), and re-graph ones. Any comments or corrections are welcomed.

Replace the original function set on the /graphql route on src/clj/mem_learning/routes/services.clj:

["/graphql" {:post graphql-call}

Add the graphql-call function on that same file:

(defn graphql-call [req]
  (let [body (:body-params req)
        content-type (keyword (get-in req [:headers "content-type"]))]
    (case content-type
          :application/json (ok (graphql/execute-request-re-graph body))
          :application/graphql (ok (graphql/execute-request (-> req :body slurp))))))

add the execute-request-re-graph to the src/clj/mem_learning/routes/services/graphql.clj file:

(defn execute-request-re-graph
  "execute request with re-graph/apollo format"
  [{:keys [variables query context]}]
  (lacinia/execute compiled-schema query variables context)))

Upvotes: 1

aarkerio
aarkerio

Reputation: 2364

ANSWER:

It looks that Luminus creates a middleware configuration:

(defn service-routes []
  ["/api"
   {:coercion spec-coercion/coercion
    :muuntaja formats/instance
    :swagger {:id ::api}
    :middleware [;; query-params & form-params
             parameters/parameters-middleware
             ;; content-negotiation
             muuntaja/format-negotiate-middleware
             ;; encoding response body
             muuntaja/format-response-middleware
             ;; exception handling
             exception/exception-middleware
             ;; decoding request body
             muuntaja/format-request-middleware
             ;; coercing response bodys
             coercion/coerce-response-middleware
             ;; coercing request parameters
             coercion/coerce-request-middleware
             ;; multipart
             multipart/multipart-middleware
             ]}

commenting the line "muuntaja/format-negotiate-middleware" makes the "application/json" call possible.

SECOND UPDATE (four hours later)

Ok, that muuntaja middleware thing was not the problem at all, the real problem is that curl send the data with the format:

{ test_by_id(id: 7, archived: false) { title } }

meanwhile re-graph uses:

{"query":"query { test_by_id(id: 7, archived: false) { title } }","variables":null}

this is a normal java string btw not a data structure, so we need to do some changes, first a new function:

(defn graphql-call [req]
  (let [body       (-> req :body slurp)
    full-query (json/read-str body :key-fn keyword)
    _          (log/info (str ">>>  **** full-query >>>>> " full-query))]
(ok (graphql/execute-request full-query))))

we set the function:

["/graphql" {:post graphql-call}]

and in my_app.routes.services.graphql file:

(defn execute-request [{:keys [variables query context]}]
  (json/write-str (lacinia/execute compiled-schema query variables context)))

and now re-graph works!

(also now I can send and use variables in GraphQL)

It's necessary to set:

 :http-parameters         {:with-credentials? false
                           :oauth-token "ah4rdSecr3t"
                           :headers {"Content-Type" "application/graphql"}

btw. Also, maybe it's better:

(lacinia/execute compiled-schema query variables context)

than:

(json/write-str (lacinia/execute compiled-schema query variables context)) 

because it interferes with re-graph importing the data already as a native ClojureScript map.

Upvotes: 0

Related Questions