Giter Site home page Giter Site logo

graphlient's Introduction

Graphlient

Gem Version Build Status

A friendlier Ruby client for consuming GraphQL-based APIs. Built on top of your usual graphql-client, but with better defaults, more consistent error handling, and using the faraday HTTP client.

Table of Contents

Installation

Add the following line to your Gemfile.

gem 'graphlient'

Usage

Create a new instance of Graphlient::Client with a URL and optional headers/http_options.

client = Graphlient::Client.new('https://test-graphql.biz/graphql',
  headers: {
    'Authorization' => 'Bearer 123'
  },
  http_options: {
    read_timeout: 20,
    write_timeout: 30
  }
)
http_options default type
read_timeout nil seconds
write_timeout nil seconds

The schema is available automatically via .schema.

client.schema # GraphQL::Schema

Make queries with query, which takes a String or a block for the query definition.

With a String.

response = client.query <<~GRAPHQL
  query {
    invoice(id: 10) {
      id
      total
      line_items {
        price
        item_type
      }
    }
  }
GRAPHQL

With a block.

response = client.query do
  query do
    invoice(id: 10) do
      id
      total
      line_items do
        price
        item_type
      end
    end
  end
end

This will call the endpoint setup in the configuration with POST, the Authorization header and query as follows.

query {
  invoice(id: 10) {
    id
    total
    line_items {
      price
      item_type
    }
  }
}

A successful response object always contains data which can be iterated upon. The following example returns the first line item's price.

response.data.invoice.line_items.first.price

You can also execute mutations the same way.

response = client.query do
  mutation do
    createInvoice(input: { fee_in_cents: 12_345 }) do
      id
      fee_in_cents
    end
  end
end

The successful response contains data in response.data. The following example returns the newly created invoice's ID.

response.data.create_invoice.first.id

Schema storing and loading on disk

To reduce requests to graphql API you can cache schema:

client = Client.new(url, schema_path: 'config/your_graphql_schema.json')
client.schema.dump! # you only need to call this when graphql schema changes

Preloading Schema Once

Even if caching the schema on disk, instantiating Graphlient::Client often can be both time and memory intensive due to loading the schema for each instance. This is especially true if the schema is a large file. To get around these performance issues, instantiate your schema once and pass it in as a configuration option.

One time in an initializer

schema = Graphlient::Schema.new(
  'https://graphql.foo.com/graphql', 'lib/graphql_schema_foo.json'
)

Pass in each time you initialize a client

client = Graphlient::Client.new(
  'https://graphql.foo.com/graphql',
  schema: schema,
  headers: {
    'Authorization' => 'Bearer 123',
  }
)

Error Handling

Unlike graphql-client, Graphlient will always raise an exception unless the query has succeeded.

All errors inherit from Graphlient::Errors::Error if you need to handle them in bulk.

Executing Parameterized Queries and Mutations

Graphlient can execute parameterized queries and mutations by providing variables as query parameters.

The following query accepts an array of IDs.

With a String.

query = <<-GRAPHQL
  query($ids: [Int]) {
    invoices(ids: $ids) {
      id
      fee_in_cents
    }
  }
GRAPHQL
variables = { ids: [42] }

client.query(query, variables)

With a block.

client.query(ids: [42]) do
  query(ids: [:int]) do
    invoices(ids: :ids) do
      id
      fee_in_cents
    end
  end
end

Graphlient supports following Scalar types for parameterized queries by default:

  • :id maps to ID
  • :boolean maps to Boolean
  • :float maps to Float
  • :int maps to Int
  • :string maps to String

You can use any of the above types with ! to make it required or use them in [] for array parameters.

For any other custom types, graphlient will simply use to_s of the symbol provided for the type, so query(ids: [:InvoiceType!]) will result in query($ids: [InvoiceType!]).

The following mutation accepts a custom type that requires fee_in_cents.

client.query(input: { fee_in_cents: 12_345 }) do
  mutation(input: :createInvoiceInput!) do
    createInvoice(input: :input) do
      id
      fee_in_cents
    end
  end
end

Parse and Execute Queries Separately

You can parse and execute queries separately with optional variables. This is highly recommended as parsing a query and validating a query on every request adds performance overhead. Parsing queries early allows validation errors to be discovered before request time and avoids many potential security issues.

# parse a query, returns a GraphQL::Client::OperationDefinition
query = client.parse do
  query(ids: [:int]) do
    invoices(ids: :ids) do
      id
      fee_in_cents
    end
  end
end

# execute a query, returns a GraphQL::Client::Response
client.execute query, ids: [42]

Or pass in a string instead of a block:

# parse a query, returns a GraphQL::Client::OperationDefinition
query = client.parse <<~GRAPHQL
  query($some_id: Int) {
    invoice(id: $some_id) {
      id
      feeInCents
    }
  }
GRAPHQL

# execute a query, returns a GraphQL::Client::Response
client.execute query, ids: [42]

Dynamic vs. Static Queries

Graphlient uses graphql-client, which recommends building queries as static module members along with dynamic variables during execution. This can be accomplished with graphlient the same way.

Create a new instance of Graphlient::Client with a URL and optional headers.

module SWAPI
  Client = Graphlient::Client.new('https://test-graphql.biz/graphql',
    headers: {
      'Authorization' => 'Bearer 123'
    },
    allow_dynamic_queries: false
  )
end

The schema is available automatically via .schema.

SWAPI::Client.schema # GraphQL::Schema

Define a query.

module SWAPI
  InvoiceQuery = Client.parse do
    query(id: :int) do
      invoice(id: :id) do
        id
        fee_in_cents
      end
    end
  end
end

Execute the query.

response = SWAPI::Client.execute(SWAPI::InvoiceQuery, id: 42)

Note that in the example above the client is created with allow_dynamic_queries: false (only allow static queries), while graphlient defaults to allow_dynamic_queries: true (allow dynamic queries). This option is marked deprecated, but we're proposing to remove it and default it to true in graphql-client#128.

Generate Queries with Graphlient::Query

You can directly use Graphlient::Query to generate raw GraphQL queries.

query = Graphlient::Query.new do
  query do
    invoice(id: 10) do
      line_items
    end
  end
end

query.to_s
# "\nquery {\n  invoice(id: 10){\n    line_items\n    }\n  }\n"

Use of Fragments

Fragments should be referred by constant:

module Fragments
  Invoice = client.parse <<~'GRAPHQL'
    fragment on Invoice {
      id
      feeInCents
    }
  GRAPHQL
end

Graphlient offers the syntax below to refer to the original constant:

  • Triple underscore ___ to refer to the fragment
  • Double underscore __ for namespace separator

In this example, Fragments::Invoice would be referred as follows:

invoice_query = client.parse do
  query do
    invoice(id: 10) do
      id
      ___Fragments__Invoice
    end
  end
end

The wrapped response only allows access to fields that have been explicitly asked for. In this example, while id has been referenced directly in the main query, feeInCents has been spread via fragment and trying to access it in the original wrapped response will throw GraphQL::Client::ImplicitlyFetchedFieldError (to prevent data leaks between components).

response = client.execute(invoice_query)
result = response.data.invoice
result.to_h
# {"id" => 10, "feeInCents"=> 20000}
result.id
# 10
result.fee_in_cents
# raises GraphQL::Client::ImplicitlyFetchedFieldError

feeInCents cannot be fetched directly from the main query, but from the fragment as shown below:

invoice = Fragments::Invoice.new(result)
invoice.id
# 10
invoice.fee_in_cents
# 20000

Create API Client Classes with Graphlient::Extension::Query

You can include Graphlient::Extensions::Query in your class. This will add a new method_missing method to your context which will be used to generate GraphQL queries.

include Graphlient::Extensions::Query

query = query do
  invoice(id: 10) do
    line_items
  end
end

query.to_s
# "\nquery{\n  invoice(id: 10){\n    line_items\n    }\n  }\n"

Swapping the HTTP Stack

You can swap the default Faraday adapter for Net::HTTP.

client = Graphlient::Client.new('https://test-graphql.biz/graphql',
  http: Graphlient::Adapters::HTTP::HTTPAdapter
)

Testing with Graphlient and RSpec

Use Graphlient inside your RSpec tests in a Rails application or with Rack::Test against your actual application.

require 'spec_helper'

describe App do
  include Rack::Test::Methods

  def app
    # ...
  end

  let(:client) do
    Graphlient::Client.new('http://test-graphql.biz/graphql') do |client|
      client.http do |h|
        h.connection do |c|
          c.adapter Faraday::Adapter::Rack, app
        end
      end
    end
  end

  context 'an invoice' do
    let(:result) do
      client.query do
        query do
          invoice(id: 10) do
            id
          end
        end
      end
    end

    it 'can be retrieved' do
      expect(result.data.invoice.id).to eq 10
    end
  end
end

Alternately you can stub_request with Webmock.

describe App do
  let(:url) { 'http://example.com/graphql' }
  let(:client) { Graphlient::Client.new(url) }

  before do
    stub_request(:post, url).to_return(
      status: 200,
      body: DummySchema.execute(GraphQL::Introspection::INTROSPECTION_QUERY).to_json
    )
  end

  it 'retrieves schema' do
    expect(client.schema).to be_a Graphlient::Schema
  end
end

In order to stub the response to actual queries, dump the schema into a JSON file and specify it via schema_path as follows.

describe App do
  let(:url) { 'http://graph.biz/graphql' }
  let(:client) { Graphlient::Client.new(url, schema_path: 'spec/support/fixtures/invoice_api.json') }
  let(:query) do
    <<~GRAPHQL
      query{
        invoice(id: 42) {
          id
          feeInCents
        }
      }
    GRAPHQL
  end
  let(:json_response) do
    {
      'data' => {
        'invoice' => {
          'id' => '42',
          'feeInCents' => 2000
        }
      }
    }.to_json
  end

  before do
    stub_request(:post, url).to_return(
      status: 200,
      body: json_response
    )
  end

  it 'returns invoice fees' do
    response = client.query(query)
    expect(response.data).to be_truthy
    expect(response.data.invoice.id).to eq('42')
    expect(response.data.invoice.fee_in_cents).to eq(2000)
  end
end

License

MIT License, see LICENSE

graphlient's People

Contributors

ashkan18 avatar ateamlunchbox avatar avinoth avatar burgestrand avatar dblock avatar gabrieldzul avatar igor-drozdov avatar jfhinchcliffe avatar jmondo avatar jonallured avatar kbaum avatar kirillkaiumov avatar neroleung avatar orta avatar qqism avatar rellampec avatar sap1enza avatar taylorthurlow avatar yuki24 avatar zavan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

graphlient's Issues

Dynamically change headers

I need to dynamically change request headers, e.g. to switch bearer tokens when i reached the limit. How can i accomplish this?

Unable to pass literal values / constants in Ruby

Description
I'm working on writing a Ruby wrapper for a GraphQL API. Some of the endpoints accept things like this (from the docs):

{
  Get {
    Article(sort: [{
      order: asc          # <-- This is the issue

and

{
  Get {
    Article(where: {
        ...
        operator: Equal,           # <-- This is the issue

Passing "asc", 'asc' or a variable with the name asc throws a GraphQL error. Respectively -- passing 'Equal', "Equal" errors out as well.

I was able to get it working by defining an Equal class, module, constant or OpenStruct and passing it in:

class Equal; end;

but this feels extremely hacky.

Is there a cleaner way to do this? The problem is that I can't hardcode order: Equal because the value is dynamic.

Here's my current code where I parse the query:

    def get_query(params, fields)
      client.graphql.parse """
        query {
          Get {
              Question(#{params}) {
                #{fields.join(' ')}
              }
          }
        }
      """
    end

params = { where: { operator: 'equal', field: 'name' }, sort: { order: 'asc'} }
get_query(params)

Thank you!

Bad encoding of the variables

Based on http://graphql.org/learn/serving-over-http/#post-request, the variables should be sent over as a JSON object, since it is a JSON document after all.

Currently graphlient encodes the json, and sends it over as a string:

{
  "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",
  "variables":"{}"
}

Due to this we had to introduce unnecessary complexity in the server to parse that string into a JSON hash, if it's given as a string, and leave it as is when it's coming from comforming clients.

Docs incorrectly state that Graphlient::Errors::TimeoutError does not inherit from Graphlient::Errors::ServerError

Describe the bug
Graphlient::Errors::TimeoutError does not inherit from Graphlient::Errors::ServerError currently, hence inner_exception, status_code and response can't be called.

To Reproduce
N/A

Expected behavior
As per description, Graphlient::Errors::TimeoutError should inherit from Graphlient::Errors::ServerError.

Screenshots
image
image

According to source code, TimeoutError inherits from base Error currently

image

Possibility of dynamically adding fields to a query?

let's say we want to make two string queries, but both call the same graphql query with a slightly different fields.

  1. calls
<<-GRAPHQL.freeze
        query(
          $id: Int!
        ) {
          queryName(
            id: $id
          ) {
            person {
              name,
              age
            }
          }
        }
      GRAPHQL
  1. calls
<<-GRAPHQL.freeze
        query(
          $id: Int!
        ) {
          queryName(
            id: $id
          ) {
            person {
              name,
              age,
              relationshipStatus,
              networth
            }
          }
        }
      GRAPHQL

Is there a way to build a "base" string query and that both 1 and 2 can reuse? Is using string literals or rewriting the whole query from scratch the only viable ways? E.g
2.

<<-GRAPHQL.freeze
        query(
          $id: Int!
        ) {
          queryName(
            id: $id
          ) {
            #{personFields}
          }
        }
      GRAPHQL

personFields = <<-GRAPHQL.freeze
            person {
              name,
              age,
              relationshipStatus,
              networth
            }
GRAPHQL

How to pass context in query?

How can I pass the current_user context in a query? In the execute method I see: query_params[:context] = @options if @options.

I got this shared_context:

# frozen_string_literal: true

require "active_record"
require "graphlient"

RSpec.shared_context "Graphql Client", shared_context: :metadata do
  include Rack::Test::Methods

  let(:client) do
    Graphlient::Client.new("http://api-example.com/graphql") do |client|
      client.http do |h|
        h.connection do |c|
          c.use Faraday::Adapter::Rack, Rails.application
        end
      end
    end

  end
end

Since I am using query authorization I can't simply query something like:

query = <<-GRAPHQL
        mutation {
          createInvite(
            team_id: 1
            email: "[email protected]"
          )
        }
      GRAPHQL

client.query(query)

Because I need the context[:current_user]

Simplify parameter definition for parameterized queries

See #20 (comment)

client.query(ids: [42]) do
  query(:ids => [:int]) do
   ....
   end
end

instead of

client.query(ids: [42]) do
  query(:$ids => :'[Int]') do
    invoices(ids: :$ids) do
      id
      fee_in_cents
    end
  end
end

This means on our end we need to detect query variables and add $ to them when generating query.

Consider removing Faraday as a dependency and default to Net::HTTP

Followup from #7 and #29. Currently we require Faraday and default to Graphlient::Adapters::HTTP::FaradayAdapter. We could default to Graphlient::Adapters::HTTP::HTTPAdapter and remove Faraday as a required dependency, but we lose the nice RSpec integration by default in which you can use a Faraday middleware

  let(:client) do
    Graphlient::Client.new('http://test-graphql.biz/graphql') do |client|
      client.http do |h|
        h.connection do |c|
          c.use Faraday::Adapter::Rack, app
        end
      end
    end
  end

[FEATURE] Expose underlying HTTP response

Describe the bug
I am looking for rate limiting headers in the graphql response -- but I only get back the response body.

These are the headers: https://anilist.gitbook.io/anilist-apiv2-docs/overview/rate-limiting

Am I missing something?

To Reproduce

response = @client.query @page_query, { page: 1, perPage: 100 }

Expected behavior
I expect the response to have access to the headers from the request.

Screenshots
If applicable, add screenshots to help explain your problem.

Screenshot 2023-12-25 at 4 24 26 PM

cache schema

Currently for each call we download schema to be able to validate the query against it. We should cache this and avoid extra calls.

How about focussing on just the DSL aspect?

GitHub’s GraphQL client does a lot of things well already (e.g. query/schema validation). It seems like graphlient was born out of a want for a Ruby DSL, so how about just focussing on that, but otherwise leverage the excellent work already being done by many maintainers?

Looking at the source of their client, the first thing it does is parse the query, from then on it basically operates on the AST of the query. You could programatically generate that AST based on your DSL and then hand that off to graphql-client to do the rest of the work.

$ gem install graphql
Fetching: graphql-1.7.3.gem (100%)
Successfully installed graphql-1.7.3
1 gem installed

$ irb -r graphql
irb(main):001:0> GraphQL.parse <<-GRAPHQL
irb(main):002:0"   query {
irb(main):003:0"     luke: human(id: "1000") {
irb(main):004:0"       name
irb(main):005:0"     }
irb(main):006:0"     leia: human(id: "1003") {
irb(main):007:0"       name
irb(main):008:0"     }
irb(main):009:0"   }
irb(main):010:0" GRAPHQL
=> #<GraphQL::Language::Nodes::Document:0x007ffd86029a50 @filename=nil, @definitions=[#<GraphQL::Language::Nodes::OperationDefinition:0x007ffd86029bb8 @line=1, @col=3, @filename=nil, @operation_type="query", @name=nil, @variables=[], @directives=[], @selections=[#<GraphQL::Language::Nodes::Field:0x007ffd8602b4b8 @line=2, @col=5, @filename=nil, @name="human", @alias="luke", @arguments=[#<GraphQL::Language::Nodes::Argument:0x007ffd8602bc10 @line=2, @col=17, @filename=nil, @name="id", @value="1000">], @directives=[], @selections=[#<GraphQL::Language::Nodes::Field:0x007ffd8602b738 @line=3, @col=7, @filename=nil, @name="name", @alias=nil, @arguments=[], @directives=[], @selections=[]>]>, #<GraphQL::Language::Nodes::Field:0x007ffd8602a1f8 @line=5, @col=5, @filename=nil, @name="human", @alias="leia", @arguments=[#<GraphQL::Language::Nodes::Argument:0x007ffd8602af68 @line=5, @col=17, @filename=nil, @name="id", @value="1003">], @directives=[], @selections=[#<GraphQL::Language::Nodes::Field:0x007ffd8602a8d8 @line=6, @col=7, @filename=nil, @name="name", @alias=nil, @arguments=[], @directives=[], @selections=[]>]>]>]>

Support Query Variables

So we can do things like

query invoiceStates($ids: [ID!], $partner_id: String!) {
      invoices(ids: $ids, partner_id: $partner_id)
      {
        id
      }
}

Latest version incompatible with faraday 1.0.0

Using graphlient 3.7 with Faraday 1.0.0 results in the following error:

RuntimeError (Adapter should be set using the adaptermethod, notuse)

It is likely this problem: https://stackoverflow.com/questions/58162859/upgrading-faraday-gem-runtimeerror-adapter-should-be-set-using-the-adapter-m that happens with faraday versions higher than 0.15.4

As far as I can tell from the source code of graphlient, the faraday adapter is set using the "use" method (here: https://github.com/ashkan18/graphlient/blob/0b36f9fc30fc290de98e67d9313b5f3d2681288f/lib/graphlient/adapters/http/faraday_adapter.rb). If my undestanding of the problem is correct, this will need to change to be compatible with Faraday 1.0.0 .

cannot stub request anymore

Describe the bug
Since 0.7.0 it is not possible to stub_request in ones specs anymore:
<NoMethodError: undefined method `values_at' for #String:0x00007f9defb82e20

To Reproduce
Steps to reproduce the behavior:

let(:response) do
      {
        'data' => {
          'someQuery' => {
            '__typename' => 'SomeType',
            'id' => 'A123'
          }
        }
      }.to_json
end

before do
  client = Graphlient::Client.new(url, schema_path: x)
  stub_request(:post, url).to_return(status: 200, body: response)
end

it do
  client.execute(some_query)
end

We get <NoMethodError: undefined method `values_at' for #String:0x00007f9defb82e20

I could track it down to graphql-client-0.18.0/lib/graphql/client.rb:358:

358         execute.execute(
359           document: document,
360           operation_name: operation.name,
361           variables: variables,
362           context: context
363        )

On a regular call this will result a hash. During a stubbed call it will return my mock "response" string, which does not have the method values_at which causes the error above.

I am not sure if this is a bug in graphlient or graphql-client. Could you provide further informations, please?

Add schema validation

We know the graphql endpoint and we should be able to validate the query using the cached schema.

Enable the use of FragmentDefinition constants within blocks

Is your feature request related to a problem? Please describe.

While fragments are backed when you use heredoc syntax and parse by storing in a constant, using the constant in a block will fail with a "}" (RCURLY) GraphQL::ParseError.

For example, for the following fragment:

HumanFragment = graphlient.parse <<-'GRAPHQL'
  fragment on Human {
    name
    homePlanet
  }
GRAPHQL

Using it like this will work:

HerosQuery = graphlient.parse <<-'GRAPHQL'
  {
    luke: human(id: "1000") {
      ...HumanFragment
    }
    leia: human(id: "1003") {
      ...HumanFragment
    }
  }
GRAPHQL

graphlient.query(HerosQuery)

However, when using blocks, we clearly cannot use ...HumanFragment. Using the constants themselves will fail with a parsing error.

graphlient.query {
  luke: human(id: "1000") {
    HumanFragment
  }
  leia: human(id: "1003") {
    HumanFragment
  }
}

The error:

        11: from ruby/Ruby26-x64/lib/ruby/gems/2.6.0/gems/graphlient-0.6.0/lib/graphlient/client.rb:37:in `query'
        10: from ruby/Ruby26-x64/lib/ruby/gems/2.6.0/gems/graphlient-0.6.0/lib/graphlient/client.rb:15:in `parse'
         9: from ruby/Ruby26-x64/lib/ruby/gems/2.6.0/gems/graphql-client-0.18.0/lib/graphql/client.rb:186:in `parse'
         8: from ruby/Ruby26-x64/lib/ruby/gems/2.6.0/gems/graphql-2.0.14/lib/graphql.rb:46:in `parse'
         7: from ruby/Ruby26-x64/lib/ruby/gems/2.6.0/gems/graphql-2.0.14/lib/graphql.rb:58:in `parse_with_racc'
         6: from parser.y:480:in `parse'
         5: from parser.y:466:in `parse_document'
         4: from ruby/Ruby26-x64/lib/ruby/gems/2.6.0/gems/graphql-2.0.14/lib/graphql/tracing.rb:90:in `trace'
         3: from parser.y:470:in `block in parse_document'
         2: from ruby/Ruby26-x64/lib/ruby/2.6.0/racc/parser.rb:259:in `do_parse'
         1: from ruby/Ruby26-x64/lib/ruby/2.6.0/racc/parser.rb:259:in `_racc_do_parse_c'
parser.y:538:in `on_error': Parse error on "}" (RCURLY) at [4, 13] (GraphQL::ParseError)

Describe the solution you'd like

I think that to be aligned with the backing gem (graph-client) those constants should be usable within the block structure that graphlient offers.

HumanFragment.class
#GraphQL::Client::FragmentDefinition

^ They belong to a well known class. So perhaps they could be just transformed to ...HumanFragment, so when Graph::Client.parse kicks in, finds them the way it expects?

Describe alternatives you've considered

Besides what is already offered in the back gem graphql-client, there isn't anything in graphlient that allows to use fragments with blocks (unless I completely missed this point). The only reference to fragments is this issue, and the solution was by not using blocks.

Additional context

I think that the above is pretty clear. You may see this is useful or you may see it is not or have another better/easier idea to support fragments. But I do not think the above can be missunderstood.

Custom type name lookup not working

I have a query working with graphql but not with graphlient because of the naming of the type.

I tried to investigate the issue but it went down pretty deep (graphql-client gem) and couldn't figure out why it works with a simple graphql query and why not with graphlient.

Do you have any ideas where this goes wrong?

The error is the following (It tries to lookup Vehicle, but it should lookup VehicleType).

   Failure/Error: response = client.execute(vehicle_query, id: vehicle.id)

     Graphlient::Errors::ClientError:
       Field 'vehicle_alerts' doesn't exist on type 'Vehicle'
     # /Users/silo/Desktop/carforce/gems/graphlient/lib/graphlient/client.rb:32:in `rescue in execute'
     # /Users/silo/Desktop/carforce/gems/graphlient/lib/graphlient/client.rb:20:in `execute'
     # ./spec/graphql/queries/vehicle_query_spec.rb:49:in `block (5 levels) in <top (required)>'
     # ./spec/rails_helper.rb:72:in `block (3 levels) in <top (required)>'
     # /Users/silo/.rvm/gems/ruby-2.5.1/gems/database_cleaner-1.7.0/lib/database_cleaner/generic/base.rb:16:in `cleaning'
     # /Users/silo/.rvm/gems/ruby-2.5.1/gems/database_cleaner-1.7.0/lib/database_cleaner/base.rb:100:in `cleaning'
     # /Users/silo/.rvm/gems/ruby-2.5.1/gems/database_cleaner-1.7.0/lib/database_cleaner/configuration.rb:86:in `block (2 levels) in cleaning'
     # /Users/silo/.rvm/gems/ruby-2.5.1/gems/database_cleaner-1.7.0/lib/database_cleaner/configuration.rb:87:in `cleaning'
     # ./spec/rails_helper.rb:71:in `block (2 levels) in <top (required)>'
     # ------------------
     # --- Caused by: ---
     # GraphQL::Client::ValidationError:
     #   Field 'vehicle_alerts' doesn't exist on type 'Vehicle'
     #   /Users/silo/Desktop/carforce/gems/graphlient/lib/graphlient/client.rb:29

See my classes the and the queries below:

field :vehicle_lookup,
          resolver: Queries::VehicleLookupQuery,
          description: 'Vehicle from lookup service without ID',
          authenticate: true
module Queries
  class VehicleQuery < Queries::BaseQuery
    type Types::VehicleType, null: false
    argument :id, String, 'id', required: true

    def resolve(id:)
      Vehicle.find(id)
    end
  end
end
module Types
  class VehicleType < GraphQL::Schema::Object
    field :id, ID, 'Primary key', null: false
    field :make, String, 'Make', null: false
    field :vehicle_alerts,
          [Types::VehicleAlertType],
          'Vehicle Alerts',
          null: false do
      argument :state, String, 'state', required: false
    end

    def vehicle_alerts(state: 'active')
      HasManyLoader.for(
        VehicleAlert, :vehicle_id, where_options: { state: state }
      ).load(object.id)
    end
  end
end

See graplient and gql queries below:

let(:vehicle_query) do
  <<-GRAPHQL
    query($id: String!) {
      vehicle(id: $id) {
        id
        make
        vehicle_alerts {
          id
        }
      }
    }
  GRAPHQL
end

This works from any FE/graphql interface (graphiql, etc.)

const vehicleQuery = gql`
  query($id: String!) {
    vehicle(id: $id) {
      id
      make
      vehicleAlerts {
        id
      }
    }
  }
`;

Querying against github API produces unexpected errors

token = ...

client = Graphlient::Client.new('https://api.github.com/graphql',
  headers: {
    'Authorization' => "Bearer #{token}"
  }
)

query = <<~GRAPHQL
  query {
    user(login: "orta") {
      name
    }
  }
GRAPHQL

client.query(query) # => Field 'user' doesn't accept argument 'login'

However it works fine with graphql-client.

    HTTP = GraphQL::Client::HTTP.new("https://api.github.com/graphql") do
      def headers(context)
        {
          "Authorization" => "Bearer #{ENV['GITHUB_ACCESS_TOKEN']}"
        }
      end
    end

    Schema = GraphQL::Client.load_schema(HTTP)

    Client = GraphQL::Client.new(schema: Schema, execute: HTTP)

    Query = Client.parse <<-GRAPHQL
      query($login: String!) {
        user(login: $login) {
          name
        }
      }
    GRAPHQL

    Client.query(
        Query,
        variables: { login: 'orta' }
      )

disable schema dump

Hi, is there a way to avoid automatic schema dump/introspection query?

Graphql for ruby (v1.9.7 ++) have the ability to disable those for production and I couldn't find a way of disabling the automatic fetch locally.

This is the message I'm getting and trying to client.schema or any operation (which in the end hits https://github.com/rmosolgo/graphql-ruby/blob/master/lib/graphql/schema/loader.rb#L16 )

(byebug) client.schema
*** KeyError Exception: key not found: "data"

Any thoughts besides mocking the schema?

Camelcasing => ClientError between 0.3.3 & 0.3.7

When updating from graphlient v0.3.3 to v0.3.7, I'm seeing an odd error:

Graphlient::Errors::ClientError: Field 'image_raw' doesn't exist on type 'Resource'

Here's a rough cut of the code resulting in this error:

def query_for(user)
    @client = Graphlient::Client.new(my_cool_service_url)
    @client.query(my_query, { email: user.email })
end

def my_query
  <<~'GRAPHQL'
      query($email: String!) {
        user(email: $email) {
          registrations(first: 10) {
            edges {
              node {
                resource {
                  name
                  slug
                  image_raw
                }
                path {
                  progress
                  points {
                    total
                    earned
                  }
                }
              }
            }
          }
        }
      }
  GRAPHQL
end

Nothing's changed in our API & I can duplicate this error by changing nothing but the gem version.

I poked around a bit in IRB and found that I get back a generic Class from @client.schema in 0.3.7, whereas I see a much richer Graphlient::Schema object in 0.3.3. Has something changed in schema parsing that might cause this error? Am I missing a syntactic change I need to make? I did see the new schema_path option, but that appears to be optional.

Interestingly, if I switch to the image property (which returns a URL to a compressed version of image_raw), everything works fine. Maybe this property name is breaking a new rule? Our implementation includes a handful of other queries with similar failures in 0.3.7 that work on 0.3.3 & when directly posted via Insomnia.

I looked through the diff between these versions, as well as related issues/PRs, and didn't see anything right away that would cause this. Since most of it is delegated, I also browsed through graphql-clients changes with no luck. That said, I'm frustrated so my comprehension may be compromised.

Regardless, thanks for this client!

Mutation

Hi!

I'm trying to set a mutation as a 'string' but I'm getting NameError: wrong constant name createSomething

Here is an example of my code:

@client.parse <<-'GRAPHQL'
  mutation createSomething($name: String!, $filter: createSomethingFilterInput!, $frequency: SomethingFrequency!) {
     createSomething(name: $name, filter: $filter, frequency: $frequency) {
     success
     message
     __typename
     }
}
GRAPHQL

But the same mutation works using graphql playground.

  mutation createSomething($name: String!, $filter: createSomethingFilterInput!, $frequency: SomethingFrequency!) {
     createSomething(name: $name, filter: $filter, frequency: $frequency) {
     success
     message
     __typename
     }
}

I'm trying to avoid the use of Blocks as I want to maintain a standard on my code.

Don't know how to pass custom input types

Hello, I am using your excellent gem in my rails app to call a graphql API in one of myother rails apps. It has been working great so far. Now I'm trying to make a query that takes an input object as a parameter (it's for doing a search). The UserSearch query is defined like this:

userSearch(
    workingGroupId: ID
    therapeuticAreaId: ID
    roles: [String!]
    filters: UserFilters
    page: Int = 1
    perPage: Int = 10
    sort: String = "users.id ASC"
)

UserFilters is defined as an input type (subclass of GraphQL::Schema::InputObject in the server-side code).

On the client side, I'm parsing the query and I reference :UserFilters as the argument type, but when I run the query, how to I pass in the "filters" argument? I tried as a hash but that did not work:

Client.query(                                                                                                                                                                                                
        UserSearchQuery,                                                                                                                                                                                                                
        ta_id: current_ta.id,                                                                                                                                                                                                           
        per_page: self.per_page.to_i,                                                                                                                                                                                                   
        page: self.page.to_i,                                                                                                                                                                                                           
        filters: {name: 'Shirley'}
      )

The error I get is "Variable filters of type UserFilters was provided invalid value"

Any help would be greatly appreciated!

Greg

NoMethodError in FaradayServerError

Hi,
We've encounter a situation where we get
undefined method `[]' for nil:NilClass in
graphlient-0.3.3/lib/graphlient/errors/faraday_server_error.rb:7
This is very frustrating because we don't know what is the error behind it
We think that changing to
@response = inner_exception.response.try(:[],:body)
@status_code = inner_exception.response.try(:[],:status)

will fix it

Allow raising exceptions instead of returning GraphQL errors in a response

Today we can do this:

    it 'returns readable validation errors' do
          result = client.query do
            mutation do
              createRsvp(input: { name: "Matt", email: "matt@ex ample.org", event_id: "420" }) do
                id
              end
            end
          end
          expect(result.data.errors.messages.to_h).to eq(
            'createRsvp' => ['Validation failed: Address is invalid']
          )
   end

Enable raising an error from the standard data.errors output, so:

    it 'returns readable validation errors' do
       expect do
          result = client.query do
            mutation do
              createRsvp(input: { name: "Matt", email: "matt@ex ample.org", event_id: "420" }) do
                id
              end
            end
          end
       end.to raise_error Graphlient::Errors::Server do |e|
          expect(e.messages).to eq(
            'createRsvp' => ['Validation failed: Address is invalid']
          )
      end
   end

Personally I'd want that to be the default because I prefer exceptions for all errors since it doesn't require the caller to worry about checking whether the query succeeded every time, but that's the opposite of what graphql-client does.

Remove global configuration

Feels like Graphlient::Client.configure is the wrong way to go if I have a client that wants to talk to multiple GraphQL endpoints. Maybe create an instance of Graphlient::Client.new(url, options = {}) instead.

client.schema is a Graphlient::Schema

Hi,

Not sure if this is the right place for this. This is my second time using graphlient, and the first I did not have this problem.

Right now my expect(client.schema).to be_a GraphQL::Schema doesn't pass, but expect(client.schema).to be_a Graphlient::Schema does.

This is my client:

let(:client) do
    Graphlient::Client.new('https://www.example.org/graphql') do |client|
      client.http do |h|
        h.connection do |c|
          c.use Faraday::Adapter::Rack, app
        end
      end
    end
  end

Thanks very much.

How to stub with webmock

This is more my ignorance than an issue.

I am currently using graphql-client but graphlient's block syntax and info on testing make me want to switch over, but I am still not sure how to test my code.

I have a deployed GraphQL server. I can run a query in Postman and get a JSON response.
I copied that response into a string.
I want to consume that endpoint in my rspec test but I want to use the canned JSON response I copied.

stub_request(:post, "http://user:[email protected]")
  .to_return(status: 200, body: canned_body, headers: {})

def canned_body
  '{ "data": {"some": "json"} }'
end

This errors:

Schema = GraphQL::Client.load_schema(HTTP)
KeyError: key not found: "data"
from /Users/bgreeff/.rvm/gems/ruby-2.6.1/gems/graphql-1.9.6/lib/graphql/schema/loader.rb:16:in `fetch'

One issue is that my stub runs after Schema = GraphQL::Client.load_schema(HTTP)
This seems like a design flaw, making my test improbable.

I see your example

stub_request(:post, url).to_return(
      status: 200,
      body: DummySchema.execute(GraphQL::Introspection::INTROSPECTION_QUERY).to_json
    )

I am guessing this is because there are actually 2 requests to the server, 1 to load the schema, but I have no idea how this works.

Can you provide a little more info on how I should construct my test please.

How to query without a schema?

Hey!

Thanks for making a gem.

I don't have a schema for my endpoint, but I do have a query + params. Is there a way to query without a schema (file or http)?

Thanks!

Faraday Timeouts no longer coming through as instance of Graphlient::Errors::FaradayServerError

Starting in Faraday 0.17.1, the TimeoutError is no longer an instance of Faraday::ClientError, which is caught here as a Graphlient::Errors::FaradayServerError

See this line and this line as the breaking change.

We have some extra error handling logic based on Graphlient::Errors::FaradayServerErrors, so for now we've had to shoehorn in Timeout catching, whereas before they came through in Graphlient.

I haven't been able to dive in enough to figure out exactly what the right solution is, but I'm happy to be part of the discussion

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.