Dynamically set headers when calling `Client#parse`
This is related to https://github.com/github/graphql-client/issues/192 but I think it warrants it's own issue.
Our api has the schema behind authorization as well. So this example for setting up a query in the readme would also require context as an argument.
HeroNameQuery = SWAPI::Client.parse <<-'GRAPHQL'
query {
hero {
name
}
}
GRAPHQL
But if we look at the source code for the parse method we can see that there is no such option.
https://github.com/github/graphql-client/blob/b9cbc7229798e2323dde2c732172a9cd5b691b83/lib/graphql/client.rb#L105
Is there any way to pass headers when defining queries? It seems to me that either every method implemented by lib/graphql/client.rb needs to have the context argument, or there needs to be a way to set it dynamically when the class gets initialized.
If there isn't currently a way to pass headers through the parse method, I'm going to have to write a wrapper that functions a bit differently from the one in the readme.
You can set up your client to accept headers like this
HTTP = GraphQL::Client::HTTP.new(url) do
def headers(context)
token = context[:access_token]
{
"Authorization": "token #{token}",
"Accept-Encoding": "gzip"
}
end
end
SCHEMA = GraphQL::Client.load_schema(HTTP)
CLIENT = GraphQL::Client.new(schema: SCHEMA, execute: HTTP)
We do this for all our authenticated APIs and it works great. Let me know if that doesn't answer your question though, I might be misunderstanding.
Of course, I just saw an identical answer on #192 which you referenced 😂 sorry about that. I don't think I'm understanding the issue
Yeah this is definitely a weird one. I think it's related to how the ruby object model loads classes/modules, but I'm not sure about that.
I'll throw up a server with steps to reproduce. It seems like a simple config memoization will do the trick.
Thanks for getting back to me on this one.
@ericyd So maybe "throwing up a server" was a little ambitious on my part. So I'll just copy the errors I'm getting.
[1] pry(main)> require "graphql/client"
=> false
[2] pry(main)> require "graphql/client/http"
=> true
[3] pry(main)> module GROUNDFLOOR_GRAPHQL
[3] pry(main)* API_URL = "#{ENV['GF_API_URL']}graphql_api/v1"
[3] pry(main)*
[3] pry(main)* HTTP = GraphQL::Client::HTTP.new(API_URL) do
[3] pry(main)* def headers(context)
[3] pry(main)* jwt = context[:jwt]
[3] pry(main)* {
[3] pry(main)* "Authorization": "Bearer #{jwt}",
[3] pry(main)* 'Content-Type': 'application/vnd.api+json',
[3] pry(main)* 'Accept': 'application/vnd.api+json'
[3] pry(main)* }
[3] pry(main)* end
[3] pry(main)* end
[3] pry(main)* SCHEMA = GraphQL::Client.load_schema(HTTP)
[3] pry(main)* CLIENT = GraphQL::Client.new(schema: SCHEMA, execute: HTTP)
[3] pry(main)* end
D, [2019-09-19T10:20:00.176360 #14035] DEBUG -- : [httplog] Connecting: localhost:5000
D, [2019-09-19T10:20:00.183468 #14035] DEBUG -- : [httplog] Sending: POST http://localhost:5000/graphql_api/v1
D, [2019-09-19T10:20:00.183544 #14035] DEBUG -- : [httplog] Header: accept-encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
D, [2019-09-19T10:20:00.183597 #14035] DEBUG -- : [httplog] Header: accept: application/json
D, [2019-09-19T10:20:00.183651 #14035] DEBUG -- : [httplog] Header: user-agent: Ruby
D, [2019-09-19T10:20:00.183707 #14035] DEBUG -- : [httplog] Header: content-type: application/json
D, [2019-09-19T10:20:00.183764 #14035] DEBUG -- : [httplog] Header: authorization: Bearer
D, [2019-09-19T10:20:00.183820 #14035] DEBUG -- : [httplog] Header: content-type: application/vnd.api+json
D, [2019-09-19T10:20:00.183913 #14035] DEBUG -- : [httplog] Header: accept: application/vnd.api+json
D, [2019-09-19T10:20:00.183968 #14035] DEBUG -- : [httplog] Header: connection: close
D, [2019-09-19T10:20:00.184021 #14035] DEBUG -- : [httplog] Header: host: localhost:5000
D, [2019-09-19T10:20:00.184071 #14035] DEBUG -- : [httplog] Header: content-length: 1451
D, [2019-09-19T10:20:00.184155 #14035] DEBUG -- : [httplog] Data: {"query":"query IntrospectionQuery {\n __schema {\n queryType {\n name\n }\n mutationType {\n name\n }\n subscriptionType {\n name\n }\n types {\n ...FullType\n }\n directives {\n name\n description\n locations\n args {\n ...InputValue\n }\n }\n }\n}\n\nfragment FullType on __Type {\n kind\n name\n description\n fields(includeDeprecated: true) {\n name\n description\n args {\n ...InputValue\n }\n type {\n ...TypeRef\n }\n isDeprecated\n deprecationReason\n }\n inputFields {\n ...InputValue\n }\n interfaces {\n ...TypeRef\n }\n enumValues(includeDeprecated: true) {\n name\n description\n isDeprecated\n deprecationReason\n }\n possibleTypes {\n ...TypeRef\n }\n}\n\nfragment InputValue on __InputValue {\n name\n description\n type {\n ...TypeRef\n }\n defaultValue\n}\n\nfragment TypeRef on __Type {\n kind\n name\n
ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n
name\n ofType {\n kind\n name\n }\n }\n }\n }\n }\n }\n }\n}","operationName":"IntrospectionQuery"}
D, [2019-09-19T10:20:00.184232 #14035] DEBUG -- : [httplog] Status: 401
D, [2019-09-19T10:20:00.184311 #14035] DEBUG -- : [httplog] Benchmark: 0.00584 seconds
D, [2019-09-19T10:20:00.184370 #14035] DEBUG -- : [httplog] Header: content-type: application/vnd.api+json
D, [2019-09-19T10:20:00.184424 #14035] DEBUG -- : [httplog] Header: access-control-allow-origin: http://localhost:3000
D, [2019-09-19T10:20:00.184477 #14035] DEBUG -- : [httplog] Header: connection: close
D, [2019-09-19T10:20:00.184529 #14035] DEBUG -- : [httplog] Header: content-length: 69
D, [2019-09-19T10:20:00.184616 #14035] DEBUG -- : [httplog] Response:
{"errors":[{"status":"401","detail":"Invalid authorization header"}]}
KeyError: key not found: "data"
from /home/andy/Repos/webdev/groundfloor_rails/vendor/bundle/ruby/2.4.0/gems/graphql-1.9.12/lib/graphql/schema/loader.rb:16:in `fetch'
In order to retrieve the schema, I need to be able to pass a JWT at the initialization of the client. Also when defining queries using the client. The initialization query can't be preformed unless a jwt is passed.
Is there any way to fix this, or should I perhaps look at another gem?
Thanks for the STR @wandy-dev , that makes the issue more clear.
I'll be honest, I'm not really a contributor to this repo, the author solved a problem for me so I was trying to address some low hanging fruit in the issues as a thank you.
My gut instinct on how to solve this is to load a static schema rather than populating it with an introspection query, which is what the load_schema method will do if its passed an instance of GraphQL::Client::HTTP.
An example of how to do this is shown in the README under Configuration: https://github.com/github/graphql-client#configuration In short, you would change
SCHEMA = GraphQL::Client.load_schema(HTTP)
to
SCHEMA = GraphQL::Client.load_schema("path/to/schema.json")
I think your post demonstrates an interesting issue though and bears further investigation. I imagine there is a way to solve this but have not dived deep enough into the code to figure it out right now.
Try:
GraphQL::Client.load_schema(GraphQL::Client.dump_schema(HTTP, nil, context: { access_token: token }))
So obviously this is no longer a priority for me. I'm no longer at the organization that I needed to raise this issue for.
If you would like to close it that's fine by me.
I recently ran into this issue too in a project. It would definitely be useful to be able to supply the context in the parse function. Ultimately, I solved this by using a generic all-purpose environment variable for retrieving the schema, and supply a different API key in the context for the Client.query but this isn't ideal.
GITHUB_ACCESS_TOKEN = ENV['GITHUB_API_TOKEN']
HTTP = GraphQL::Client::HTTP.new('https://api.github.com/graphql') do
def headers(context)
unless token = context[:api_key] || GITHUB_ACCESS_TOKEN
fail "Missing GitHub access token"
end
{
"Authorization" => "Bearer #{token}",
"User-Agent" => 'Ruby'
}
end
end
Schema = GraphQL::Client.load_schema(HTTP)
Client = GraphQL::Client.new(schema: Schema, execute: HTTP)