graphlient 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +38 -38
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -20
  4. data/.github/workflows/ci.yml +33 -30
  5. data/.github/workflows/danger-comment.yml +10 -0
  6. data/.github/workflows/danger.yml +13 -22
  7. data/.github/workflows/rubocop.yml +19 -19
  8. data/.gitignore +59 -52
  9. data/.rspec +1 -1
  10. data/.rubocop.yml +26 -28
  11. data/.rubocop_todo.yml +72 -48
  12. data/CHANGELOG.md +128 -113
  13. data/CONTRIBUTING.md +125 -125
  14. data/Dangerfile +24 -24
  15. data/Gemfile +35 -27
  16. data/LICENSE +21 -21
  17. data/README.md +800 -533
  18. data/RELEASING.md +63 -63
  19. data/Rakefile +15 -15
  20. data/UPGRADING.md +23 -23
  21. data/graphlient.gemspec +19 -19
  22. data/lib/graphlient/adapters/http/adapter.rb +41 -41
  23. data/lib/graphlient/adapters/http/faraday_adapter.rb +79 -79
  24. data/lib/graphlient/adapters/http/http_adapter.rb +40 -39
  25. data/lib/graphlient/adapters/http.rb +3 -3
  26. data/lib/graphlient/adapters.rb +1 -1
  27. data/lib/graphlient/client.rb +98 -80
  28. data/lib/graphlient/errors/client_error.rb +6 -6
  29. data/lib/graphlient/errors/connection_failed_error.rb +6 -6
  30. data/lib/graphlient/errors/error.rb +13 -12
  31. data/lib/graphlient/errors/execution_error.rb +29 -29
  32. data/lib/graphlient/errors/faraday_server_error.rb +12 -12
  33. data/lib/graphlient/errors/graphql_error.rb +53 -52
  34. data/lib/graphlient/errors/http_options_error.rb +6 -6
  35. data/lib/graphlient/errors/http_server_error.rb +13 -13
  36. data/lib/graphlient/errors/server_error.rb +7 -7
  37. data/lib/graphlient/errors/timeout_error.rb +6 -6
  38. data/lib/graphlient/errors.rb +10 -10
  39. data/lib/graphlient/extensions/query.rb +15 -15
  40. data/lib/graphlient/extensions.rb +1 -1
  41. data/lib/graphlient/query/directive.rb +47 -0
  42. data/lib/graphlient/query/serializer/arguments.rb +67 -0
  43. data/lib/graphlient/query/serializer/directives.rb +17 -0
  44. data/lib/graphlient/query/serializer/evaluator.rb +15 -0
  45. data/lib/graphlient/query/serializer/fragments.rb +28 -0
  46. data/lib/graphlient/query/serializer/scalars.rb +63 -0
  47. data/lib/graphlient/query/serializer.rb +153 -0
  48. data/lib/graphlient/query.rb +29 -128
  49. data/lib/graphlient/schema.rb +27 -26
  50. data/lib/graphlient/version.rb +3 -3
  51. data/lib/graphlient.rb +8 -8
  52. data/spec/graphlient/adapters/http/faraday_adapter_spec.rb +172 -167
  53. data/spec/graphlient/adapters/http/http_adapter_spec.rb +60 -60
  54. data/spec/graphlient/client_dsl_spec.rb +153 -0
  55. data/spec/graphlient/client_query_spec.rb +369 -346
  56. data/spec/graphlient/client_schema_spec.rb +75 -75
  57. data/spec/graphlient/extensions/query_spec.rb +16 -16
  58. data/spec/graphlient/github_query_spec.rb +32 -32
  59. data/spec/graphlient/query_dsl_spec.rb +231 -0
  60. data/spec/graphlient/query_spec.rb +105 -105
  61. data/spec/graphlient/schema_spec.rb +56 -56
  62. data/spec/graphlient/static_client_query_spec.rb +75 -68
  63. data/spec/graphlient/webmock_client_query_spec.rb +41 -41
  64. data/spec/spec_helper.rb +26 -14
  65. data/spec/support/context/dummy_client.rb +33 -26
  66. data/spec/support/context/github_client.rb +18 -18
  67. data/spec/support/dummy_app.rb +21 -19
  68. data/spec/support/dummy_schema.rb +17 -17
  69. data/spec/support/fixtures/github/schema.yml +14282 -14282
  70. data/spec/support/fixtures/github/user.yml +76 -76
  71. data/spec/support/fixtures/github/viewer.yml +76 -76
  72. data/spec/support/fixtures/invoice_api.json +1288 -1288
  73. data/spec/support/mutations/create_invoice.rb +18 -18
  74. data/spec/support/queries/query.rb +48 -47
  75. data/spec/support/schema/github.json +44479 -44479
  76. data/spec/support/types/invoice_type.rb +13 -13
  77. data/spec/support/types/mutation_type.rb +5 -5
  78. data/spec/support/vcr.rb +9 -9
  79. metadata +13 -8
  80. data/Gemfile.danger +0 -5
@@ -1,80 +1,98 @@
1
- module Graphlient
2
- class Client
3
- attr_accessor :uri, :options
4
-
5
- class InvalidConfigurationError < StandardError; end
6
-
7
- def initialize(url, options = {}, &_block)
8
- @url = url
9
- @options = options.dup
10
- raise_error_if_invalid_configuration!
11
- yield self if block_given?
12
- end
13
-
14
- def parse(query_str = nil, &block)
15
- query_str ||= Graphlient::Query.new do
16
- instance_eval(&block)
17
- end
18
- client.parse(query_str.to_s)
19
- rescue GraphQL::Client::Error => e
20
- raise Graphlient::Errors::ClientError, e.message
21
- end
22
-
23
- def execute(query, variables = nil)
24
- query_params = {}
25
- query_params[:context] = @options if @options
26
- query_params[:variables] = variables if variables
27
- query = client.parse(query) if query.is_a?(String)
28
- rc = client.query(query, **query_params)
29
- raise Graphlient::Errors::GraphQLError, rc if rc.errors.any?
30
- # see https://github.com/github/graphql-client/pull/132
31
- # see https://github.com/exAspArk/graphql-errors/issues/2
32
- raise Graphlient::Errors::ExecutionError, rc if errors_in_result?(rc)
33
- rc
34
- rescue GraphQL::Client::Error => e
35
- raise Graphlient::Errors::ClientError, e.message
36
- end
37
-
38
- def query(query_or_variables = nil, variables = nil, &block)
39
- if block_given?
40
- execute(parse(&block), query_or_variables)
41
- else
42
- execute(query_or_variables, variables)
43
- end
44
- end
45
-
46
- def http_adapter_class
47
- options[:http] || Adapters::HTTP::FaradayAdapter
48
- end
49
-
50
- def http(&block)
51
- adapter_options = { headers: @options[:headers], http_options: @options[:http_options] }
52
-
53
- @http ||= http_adapter_class.new(@url, adapter_options, &block)
54
- end
55
-
56
- def schema
57
- @schema ||= options[:schema] || Graphlient::Schema.new(http, schema_path)
58
- end
59
-
60
- private
61
-
62
- def raise_error_if_invalid_configuration!
63
- raise InvalidConfigurationError, 'schema_path and schema cannot both be provided' if options.key?(:schema_path) && options.key?(:schema)
64
- end
65
-
66
- def schema_path
67
- return options[:schema_path].to_s if options[:schema_path]
68
- end
69
-
70
- def client
71
- @client ||= GraphQL::Client.new(schema: schema.graphql_schema, execute: http).tap do |client|
72
- client.allow_dynamic_queries = @options.key?(:allow_dynamic_queries) ? options[:allow_dynamic_queries] : true
73
- end
74
- end
75
-
76
- def errors_in_result?(response)
77
- response.data && response.data.errors && response.data.errors.all.any?
78
- end
79
- end
80
- end
1
+ module Graphlient
2
+ class Client
3
+ attr_accessor :uri, :options
4
+
5
+ class InvalidConfigurationError < StandardError; end
6
+
7
+ def initialize(url, options = {}, &_block)
8
+ @url = url
9
+ @options = options.dup
10
+ raise_error_if_invalid_configuration!
11
+ yield self if block_given?
12
+ end
13
+
14
+ def parse(query_str = nil, &block)
15
+ query_str ||= Graphlient::Query.new do
16
+ instance_eval(&block)
17
+ end
18
+ client.parse(query_str.to_s)
19
+ rescue GraphQL::Client::Error => e
20
+ raise Graphlient::Errors::ClientError, e.message
21
+ end
22
+
23
+ def to_query_string(**_kargs, &block)
24
+ Graphlient::Query.new do
25
+ instance_eval(&block)
26
+ end.to_s
27
+ end
28
+
29
+ def execute(query, variables = nil)
30
+ query_params = {}
31
+ query_params[:context] = @options if @options
32
+ query_params[:variables] = variables if variables
33
+ query = client.parse(query) if query.is_a?(String)
34
+ rc = client.query(query, **query_params)
35
+ raise Graphlient::Errors::GraphQLError, rc if rc.errors.any?
36
+ # see https://github.com/github-community-projects/graphql-client/pull/132
37
+ # see https://github.com/exAspArk/graphql-errors/issues/2
38
+ raise Graphlient::Errors::ExecutionError, rc if errors_in_result?(rc)
39
+
40
+ rc
41
+ rescue GraphQL::Client::Error => e
42
+ raise Graphlient::Errors::ClientError, e.message
43
+ end
44
+
45
+ def query(query_or_variables = nil, variables = nil, &block)
46
+ if block_given?
47
+ execute(parse(&block), query_or_variables)
48
+ else
49
+ execute(query_or_variables, variables)
50
+ end
51
+ end
52
+
53
+ def http_adapter_class
54
+ options[:http] || Adapters::HTTP::FaradayAdapter
55
+ end
56
+
57
+ def http(&block)
58
+ adapter_options = { headers: @options[:headers], http_options: @options[:http_options] }
59
+
60
+ @http ||= http_adapter_class.new(@url, adapter_options, &block)
61
+ end
62
+
63
+ # Register a custom scalar type for use in variable declarations.
64
+ # Call inside the client initialiser block:
65
+ # client = Graphlient::Client.new(url) do |c|
66
+ # c.scalar :date, 'Date'
67
+ # c.scalar :uuid, 'UUID'
68
+ # c.scalar :decimal, 'Decimal'
69
+ # end
70
+ def scalar(sym, graphql_type)
71
+ Query.scalar(sym, graphql_type)
72
+ end
73
+
74
+ def schema
75
+ @schema ||= options[:schema] || Graphlient::Schema.new(http, schema_path)
76
+ end
77
+
78
+ private
79
+
80
+ def raise_error_if_invalid_configuration!
81
+ raise InvalidConfigurationError, 'schema_path and schema cannot both be provided' if options.key?(:schema_path) && options.key?(:schema)
82
+ end
83
+
84
+ def schema_path
85
+ options[:schema_path].to_s if options[:schema_path]
86
+ end
87
+
88
+ def client
89
+ @client ||= GraphQL::Client.new(schema: schema.graphql_schema, execute: http).tap do |client|
90
+ client.allow_dynamic_queries = @options.key?(:allow_dynamic_queries) ? options[:allow_dynamic_queries] : true
91
+ end
92
+ end
93
+
94
+ def errors_in_result?(response)
95
+ response.data && response.data.errors && response.data.errors.all.any?
96
+ end
97
+ end
98
+ end
@@ -1,6 +1,6 @@
1
- module Graphlient
2
- module Errors
3
- class ClientError < Error
4
- end
5
- end
6
- end
1
+ module Graphlient
2
+ module Errors
3
+ class ClientError < Error
4
+ end
5
+ end
6
+ end
@@ -1,6 +1,6 @@
1
- module Graphlient
2
- module Errors
3
- class ConnectionFailedError < ServerError
4
- end
5
- end
6
- end
1
+ module Graphlient
2
+ module Errors
3
+ class ConnectionFailedError < ServerError
4
+ end
5
+ end
6
+ end
@@ -1,12 +1,13 @@
1
- module Graphlient
2
- module Errors
3
- class Error < StandardError
4
- attr_reader :inner_exception
5
- def initialize(message, inner_exception = nil)
6
- super(message)
7
-
8
- @inner_exception = inner_exception
9
- end
10
- end
11
- end
12
- end
1
+ module Graphlient
2
+ module Errors
3
+ class Error < StandardError
4
+ attr_reader :inner_exception
5
+
6
+ def initialize(message, inner_exception = nil)
7
+ super(message)
8
+
9
+ @inner_exception = inner_exception
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,29 +1,29 @@
1
- module Graphlient
2
- module Errors
3
- class ExecutionError < Error
4
- attr_reader :response
5
-
6
- def initialize(response)
7
- super('the server responded with a GraphQL execution error')
8
- @response = response
9
- end
10
-
11
- def errors
12
- response.data.errors
13
- end
14
-
15
- def to_s
16
- errors.details.map do |key, details|
17
- details = create_details(details).join("\n")
18
- [key, details].compact.join(': ')
19
- end.join("\n")
20
- end
21
-
22
- private
23
-
24
- def create_details(details)
25
- details.map { |detail| detail['message'] }
26
- end
27
- end
28
- end
29
- end
1
+ module Graphlient
2
+ module Errors
3
+ class ExecutionError < Error
4
+ attr_reader :response
5
+
6
+ def initialize(response)
7
+ super('the server responded with a GraphQL execution error')
8
+ @response = response
9
+ end
10
+
11
+ def errors
12
+ response.data.errors
13
+ end
14
+
15
+ def to_s
16
+ errors.details.map do |key, details|
17
+ details = create_details(details).join("\n")
18
+ [key, details].compact.join(': ')
19
+ end.join("\n")
20
+ end
21
+
22
+ private
23
+
24
+ def create_details(details)
25
+ details.map { |detail| detail['message'] }
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,12 +1,12 @@
1
- module Graphlient
2
- module Errors
3
- class FaradayServerError < ServerError
4
- def initialize(inner_exception)
5
- super(inner_exception.message, inner_exception)
6
- @inner_exception = inner_exception
7
- @response = inner_exception.response[:body]
8
- @status_code = inner_exception.response[:status]
9
- end
10
- end
11
- end
12
- end
1
+ module Graphlient
2
+ module Errors
3
+ class FaradayServerError < ServerError
4
+ def initialize(inner_exception)
5
+ super(inner_exception.message, inner_exception)
6
+ @inner_exception = inner_exception
7
+ @response = inner_exception.response[:body]
8
+ @status_code = inner_exception.response[:status]
9
+ end
10
+ end
11
+ end
12
+ end
@@ -1,52 +1,53 @@
1
- module Graphlient
2
- module Errors
3
- class GraphQLError < Error
4
- attr_reader :response
5
- def initialize(response)
6
- super('the server responded with a GraphQL error')
7
- @response = response
8
- end
9
-
10
- def errors
11
- @response.errors
12
- end
13
-
14
- def to_s
15
- errors.details.map do |key, details|
16
- details = create_details(details).join("\n")
17
- [key == 'data' ? nil : key, details].compact.join(': ')
18
- end.join("\n")
19
- end
20
-
21
- private
22
-
23
- def create_details(details)
24
- details.map { |detail| create_detail(detail) }
25
- end
26
-
27
- ##
28
- # Generates human readable error explanation from a GraphQL error message
29
- # It first tries `problem` attribute of the error response
30
- # then checks for error root level `path` and tries to generate error from that
31
- # and if none exist, it fallbacks to just return error message
32
- def create_detail(detail)
33
- if detail.key?('problems')
34
- [detail['message'], create_problems(detail['problems']).compact.join("\n ")].join("\n ")
35
- elsif detail.key?('path')
36
- [detail['path'].compact.join(' '), detail['message']].join(': ')
37
- else
38
- detail['message']
39
- end
40
- end
41
-
42
- def create_problems(problems)
43
- problems.map { |problem| create_problem(problem) }
44
- end
45
-
46
- def create_problem(problem)
47
- paths = problem.key?('path') && !problem['path'].empty? ? "#{problem['path'].join(', ')}: " : ''
48
- [paths, problem['explanation']].compact.join
49
- end
50
- end
51
- end
52
- end
1
+ module Graphlient
2
+ module Errors
3
+ class GraphQLError < Error
4
+ attr_reader :response
5
+
6
+ def initialize(response)
7
+ super('the server responded with a GraphQL error')
8
+ @response = response
9
+ end
10
+
11
+ def errors
12
+ @response.errors
13
+ end
14
+
15
+ def to_s
16
+ errors.details.map do |key, details|
17
+ details = create_details(details).join("\n")
18
+ [key == 'data' ? nil : key, details].compact.join(': ')
19
+ end.join("\n")
20
+ end
21
+
22
+ private
23
+
24
+ def create_details(details)
25
+ details.map { |detail| create_detail(detail) }
26
+ end
27
+
28
+ ##
29
+ # Generates human readable error explanation from a GraphQL error message
30
+ # It first tries `problem` attribute of the error response
31
+ # then checks for error root level `path` and tries to generate error from that
32
+ # and if none exist, it fallbacks to just return error message
33
+ def create_detail(detail)
34
+ if detail.key?('problems')
35
+ [detail['message'], create_problems(detail['problems']).compact.join("\n ")].join("\n ")
36
+ elsif detail.key?('path')
37
+ [detail['path'].compact.join(' '), detail['message']].join(': ')
38
+ else
39
+ detail['message']
40
+ end
41
+ end
42
+
43
+ def create_problems(problems)
44
+ problems.map { |problem| create_problem(problem) }
45
+ end
46
+
47
+ def create_problem(problem)
48
+ paths = problem.key?('path') && !problem['path'].empty? ? "#{problem['path'].join(', ')}: " : ''
49
+ [paths, problem['explanation']].compact.join
50
+ end
51
+ end
52
+ end
53
+ end
@@ -1,6 +1,6 @@
1
- module Graphlient
2
- module Errors
3
- class HttpOptionsError < Error
4
- end
5
- end
6
- end
1
+ module Graphlient
2
+ module Errors
3
+ class HttpOptionsError < Error
4
+ end
5
+ end
6
+ end
@@ -1,13 +1,13 @@
1
- module Graphlient
2
- module Errors
3
- class HttpServerError < Error
4
- attr_reader :status_code, :response
5
-
6
- def initialize(message, response)
7
- super(message, response)
8
- @status_code = response.code
9
- @response = response.body
10
- end
11
- end
12
- end
13
- end
1
+ module Graphlient
2
+ module Errors
3
+ class HttpServerError < Error
4
+ attr_reader :status_code, :response
5
+
6
+ def initialize(message, response)
7
+ super(message, response)
8
+ @status_code = response.code
9
+ @response = response.body
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,7 +1,7 @@
1
- module Graphlient
2
- module Errors
3
- class ServerError < Error
4
- attr_reader :status_code, :response
5
- end
6
- end
7
- end
1
+ module Graphlient
2
+ module Errors
3
+ class ServerError < Error
4
+ attr_reader :status_code, :response
5
+ end
6
+ end
7
+ end
@@ -1,6 +1,6 @@
1
- module Graphlient
2
- module Errors
3
- class TimeoutError < Error
4
- end
5
- end
6
- end
1
+ module Graphlient
2
+ module Errors
3
+ class TimeoutError < Error
4
+ end
5
+ end
6
+ end
@@ -1,10 +1,10 @@
1
- require_relative 'errors/error'
2
- require_relative 'errors/client_error'
3
- require_relative 'errors/server_error'
4
- require_relative 'errors/graphql_error'
5
- require_relative 'errors/execution_error'
6
- require_relative 'errors/faraday_server_error'
7
- require_relative 'errors/http_options_error'
8
- require_relative 'errors/http_server_error'
9
- require_relative 'errors/connection_failed_error'
10
- require_relative 'errors/timeout_error'
1
+ require_relative 'errors/error'
2
+ require_relative 'errors/client_error'
3
+ require_relative 'errors/server_error'
4
+ require_relative 'errors/graphql_error'
5
+ require_relative 'errors/execution_error'
6
+ require_relative 'errors/faraday_server_error'
7
+ require_relative 'errors/http_options_error'
8
+ require_relative 'errors/http_server_error'
9
+ require_relative 'errors/connection_failed_error'
10
+ require_relative 'errors/timeout_error'
@@ -1,15 +1,15 @@
1
- module Graphlient
2
- module Extensions
3
- module Query
4
- def method_missing(method_name, *args, &block)
5
- Graphlient::Query.new do
6
- send(method_name, *args, &block)
7
- end
8
- end
9
-
10
- def respond_to_missing?(method_name, include_private = false)
11
- super
12
- end
13
- end
14
- end
15
- end
1
+ module Graphlient
2
+ module Extensions
3
+ module Query
4
+ def method_missing(method_name, *args, &block)
5
+ Graphlient::Query.new do
6
+ send(method_name, *args, &block)
7
+ end
8
+ end
9
+
10
+ def respond_to_missing?(method_name, include_private = false)
11
+ super
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1 +1 @@
1
- require_relative 'extensions/query'
1
+ require_relative 'extensions/query'
@@ -0,0 +1,47 @@
1
+ module Graphlient
2
+ class Query
3
+ # Value object returned by `_directive_name(args)` in the DSL.
4
+ #
5
+ # Rather than writing directly to the query string (which would produce
6
+ # the wrong position -- before the field name), _skip / _include etc.
7
+ # return a Directive instance. append_node / spread / on detect Directive
8
+ # objects in their argument list and place them AFTER the field name,
9
+ # producing correct GraphQL output:
10
+ #
11
+ # feeInCents _skip(if: :skip_fee)
12
+ # -> feeInCents @skip(if: $skip_fee)
13
+ #
14
+ # spread :InvoiceFields, _skip(if: :x)
15
+ # -> ...InvoiceFields @skip(if: $x)
16
+ #
17
+ # spread(_skip(if: :x), on: :DraftInvoice) { draft_id }
18
+ # -> ... on DraftInvoice @skip(if: $x) { draftId }
19
+ class Directive
20
+ attr_reader :name, :args
21
+
22
+ def initialize(name, args = {})
23
+ @name = name.to_s.delete_prefix('_').freeze
24
+ @args = args
25
+ end
26
+
27
+ def to_s
28
+ return "@#{name}" if args.empty?
29
+
30
+ formatted = args.map { |k, v| "#{k}: #{format_value(v)}" }.join(', ')
31
+ "@#{name}(#{formatted})"
32
+ end
33
+
34
+ private
35
+
36
+ # Symbols become variable references ($name); everything else is literal.
37
+ def format_value(value)
38
+ case value
39
+ when Symbol then "$#{value}"
40
+ when String then "\"#{value}\""
41
+ when Numeric, TrueClass, FalseClass then value.to_s
42
+ else value.to_s
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end