graphql_client 0.3.3

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 (73) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rubocop-http---shopify-github-io-ruby-style-guide-rubocop-yml +1133 -0
  4. data/.rubocop.yml +24 -0
  5. data/.travis.yml +6 -0
  6. data/CHANGELOG.md +3 -0
  7. data/CODE_OF_CONDUCT.md +45 -0
  8. data/CONTRIBUTING.md +28 -0
  9. data/CONTRIBUTING_DEVELOPER_CERTIFICATE_OF_ORIGIN.txt +37 -0
  10. data/Gemfile +8 -0
  11. data/LICENSE.md +21 -0
  12. data/README.md +96 -0
  13. data/Rakefile +13 -0
  14. data/bin/graphql-client +79 -0
  15. data/bin/rake +17 -0
  16. data/circle.yml +3 -0
  17. data/dev.yml +7 -0
  18. data/graphql_ruby_client.gemspec +26 -0
  19. data/lib/graphql_client.rb +46 -0
  20. data/lib/graphql_client/adapters/http_adapter.rb +72 -0
  21. data/lib/graphql_client/base.rb +53 -0
  22. data/lib/graphql_client/config.rb +42 -0
  23. data/lib/graphql_client/deserialization.rb +36 -0
  24. data/lib/graphql_client/error.rb +22 -0
  25. data/lib/graphql_client/graph_connection.rb +21 -0
  26. data/lib/graphql_client/graph_node.rb +24 -0
  27. data/lib/graphql_client/graph_object.rb +56 -0
  28. data/lib/graphql_client/introspection_query.rb +80 -0
  29. data/lib/graphql_client/query/add_inline_fragment.rb +42 -0
  30. data/lib/graphql_client/query/argument.rb +30 -0
  31. data/lib/graphql_client/query/document.rb +86 -0
  32. data/lib/graphql_client/query/field.rb +91 -0
  33. data/lib/graphql_client/query/fragment.rb +41 -0
  34. data/lib/graphql_client/query/has_selection_set.rb +72 -0
  35. data/lib/graphql_client/query/inline_fragment.rb +35 -0
  36. data/lib/graphql_client/query/mutation_document.rb +20 -0
  37. data/lib/graphql_client/query/operation.rb +46 -0
  38. data/lib/graphql_client/query/operations/mutation_operation.rb +17 -0
  39. data/lib/graphql_client/query/operations/query_operation.rb +17 -0
  40. data/lib/graphql_client/query/query_document.rb +20 -0
  41. data/lib/graphql_client/query/selection_set.rb +53 -0
  42. data/lib/graphql_client/response.rb +21 -0
  43. data/lib/graphql_client/response_connection.rb +18 -0
  44. data/lib/graphql_client/response_object.rb +32 -0
  45. data/lib/graphql_client/schema_patches.rb +17 -0
  46. data/lib/graphql_client/version.rb +7 -0
  47. data/shipit.rubygems.yml +1 -0
  48. data/shipit.yml +4 -0
  49. data/test/graphql_client/adapters/http_adapter_test.rb +111 -0
  50. data/test/graphql_client/config_test.rb +68 -0
  51. data/test/graphql_client/graph_connection_test.rb +8 -0
  52. data/test/graphql_client/graph_node_test.rb +8 -0
  53. data/test/graphql_client/graph_object_query_transforming_test.rb +156 -0
  54. data/test/graphql_client/graph_object_test.rb +142 -0
  55. data/test/graphql_client/graphql_client_test.rb +41 -0
  56. data/test/graphql_client/http_client_test.rb +52 -0
  57. data/test/graphql_client/query/add_inline_fragment_test.rb +57 -0
  58. data/test/graphql_client/query/arguments_test.rb +89 -0
  59. data/test/graphql_client/query/document_test.rb +246 -0
  60. data/test/graphql_client/query/field_test.rb +163 -0
  61. data/test/graphql_client/query/fragment_test.rb +45 -0
  62. data/test/graphql_client/query/inline_fragment_test.rb +37 -0
  63. data/test/graphql_client/query/mutation_document_test.rb +30 -0
  64. data/test/graphql_client/query/mutation_operation_test.rb +89 -0
  65. data/test/graphql_client/query/query_document_test.rb +30 -0
  66. data/test/graphql_client/query/query_operation_test.rb +262 -0
  67. data/test/graphql_client/query/selection_set_test.rb +116 -0
  68. data/test/graphql_client/response_connection_test.rb +50 -0
  69. data/test/graphql_client/response_object_test.rb +109 -0
  70. data/test/graphql_client/response_test.rb +67 -0
  71. data/test/support/fixtures/schema.json +13710 -0
  72. data/test/test_helper.rb +37 -0
  73. metadata +227 -0
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Client
5
+ module Query
6
+ Argument = Struct.new(:value) do
7
+ def to_query
8
+ case value
9
+ when FalseClass, Float, Integer, NilClass, String, TrueClass
10
+ generate_query_value(value)
11
+ when Array
12
+ "[#{value.map { |v| generate_query_value(v) }.join(', ')}]"
13
+ when Hash
14
+ "{ #{value.map { |k, v| "#{k}: #{generate_query_value(v)}" }.join(', ')} }"
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def generate_query_value(value)
21
+ if value.to_s.start_with?('$')
22
+ value
23
+ else
24
+ JSON.generate(value, quirks_mode: true)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Client
5
+ module Query
6
+ class Document
7
+ DEFAULT_OPERATION_NAME = 'default'
8
+
9
+ DUPLICATE_OPERATION_NAME = Class.new(StandardError)
10
+ INVALID_DOCUMENT = Class.new(StandardError)
11
+ INVALID_FRAGMENT_TARGET = Class.new(StandardError)
12
+
13
+ attr_reader :fragments, :operations, :schema
14
+
15
+ def initialize(schema)
16
+ @schema = schema
17
+ @fragments = {}
18
+ @operations = {}
19
+
20
+ yield self if block_given?
21
+ end
22
+
23
+ def add_mutation(name = nil, &block)
24
+ add_operation(MutationOperation, name, &block)
25
+ end
26
+
27
+ def add_query(name = nil, &block)
28
+ add_operation(QueryOperation, name, &block)
29
+ end
30
+
31
+ def define_fragment(name, on:)
32
+ type = schema.type(on)
33
+
34
+ unless type.object? || type.interface? || type.union?
35
+ raise INVALID_FRAGMENT_TARGET, "invalid target type (#{type.kind}) for fragment #{name}"
36
+ end
37
+
38
+ fragment = Fragment.new(name, type, document: self)
39
+ @fragments[name] = fragment
40
+
41
+ if block_given?
42
+ yield fragment
43
+ else
44
+ fragment
45
+ end
46
+ end
47
+
48
+ def fragment_definitions
49
+ fragments.values.map(&:to_definition).join("\n")
50
+ end
51
+
52
+ def to_query
53
+ ''.dup.tap do |query_string|
54
+ query_string << "#{fragment_definitions}\n" unless fragments.empty?
55
+ query_string << operations.values.map(&:to_query).join("\n")
56
+ end
57
+ end
58
+
59
+ alias_method :to_s, :to_query
60
+
61
+ private
62
+
63
+ def add_operation(operation_type, name)
64
+ if operations.key?(DEFAULT_OPERATION_NAME)
65
+ raise INVALID_DOCUMENT, 'a document with multiple operations must have named operations'
66
+ end
67
+
68
+ operation = operation_type.new(self, name: name)
69
+ operation_name = operation.name || DEFAULT_OPERATION_NAME
70
+
71
+ if operations.key?(operation_name)
72
+ raise DUPLICATE_OPERATION_NAME, "an operation named #{operation_name} already exists in the document"
73
+ end
74
+
75
+ @operations[operation_name] = operation
76
+
77
+ if block_given?
78
+ yield operation
79
+ else
80
+ operation
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Client
5
+ module Query
6
+ class Field
7
+ include AddInlineFragment
8
+ include HasSelectionSet
9
+
10
+ INVALID_ARGUMENTS = Class.new(StandardError)
11
+
12
+ attr_reader :arguments, :as, :document, :field_defn
13
+
14
+ def initialize(field_defn, document:, arguments: {}, as: nil)
15
+ @field_defn = field_defn
16
+ @document = document
17
+ @arguments = validate_arguments(arguments)
18
+ @as = as
19
+ @selection_set = SelectionSet.new
20
+ end
21
+
22
+ def add_arguments(**arguments)
23
+ new_arguments = validate_arguments(arguments)
24
+ @arguments.merge!(new_arguments)
25
+ end
26
+
27
+ def aliased?
28
+ name != field_defn.name
29
+ end
30
+
31
+ def arguments=(arguments)
32
+ @arguments = validate_arguments(arguments)
33
+ end
34
+
35
+ def connection?
36
+ resolver_type.name.to_s.end_with?('Connection')
37
+ end
38
+
39
+ def name
40
+ as || field_defn.name
41
+ end
42
+
43
+ def node?
44
+ field_defn.name == 'Node' || (resolver_type.object? && resolver_type.implement?('Node'))
45
+ end
46
+
47
+ def resolver_type
48
+ @resolver_type ||= schema.type(field_defn.type.unwrap.name)
49
+ end
50
+
51
+ def schema
52
+ document.schema
53
+ end
54
+
55
+ def to_query(indent: '')
56
+ indent.dup.tap do |query_string|
57
+ query_string << "#{as}: " if aliased?
58
+ query_string << field_defn.name
59
+ query_string << "(#{arguments_string.join(', ')})" if arguments.any?
60
+
61
+ unless selection_set.empty?
62
+ query_string << " {\n"
63
+ query_string << selection_set.to_query(indent)
64
+ query_string << "\n#{indent}}"
65
+ end
66
+ end
67
+ end
68
+
69
+ alias_method :to_s, :to_query
70
+
71
+ private
72
+
73
+ def arguments_string
74
+ arguments.map do |name, value|
75
+ "#{name}: #{value.to_query}"
76
+ end
77
+ end
78
+
79
+ def validate_arguments(arguments)
80
+ arguments.each_with_object({}) do |(arg_name, value), hash|
81
+ if field_defn.args.any? { |arg| arg.name == arg_name.to_s }
82
+ hash[arg_name] = value.is_a?(Argument) ? value : Argument.new(value)
83
+ else
84
+ raise INVALID_ARGUMENTS, "#{arg_name} is not a valid arg for #{name}"
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Client
5
+ module Query
6
+ class Fragment
7
+ include AddInlineFragment
8
+ include HasSelectionSet
9
+
10
+ attr_reader :document, :name, :type
11
+
12
+ def initialize(name, type, document:)
13
+ @name = name
14
+ @type = type
15
+ @document = document
16
+ @selection_set = SelectionSet.new
17
+
18
+ yield self if block_given?
19
+ end
20
+
21
+ def resolver_type
22
+ type
23
+ end
24
+
25
+ def to_definition(indent: '')
26
+ indent.dup.tap do |query_string|
27
+ query_string << "fragment #{name} on #{type.name} {\n"
28
+ query_string << selection_set.to_query(indent)
29
+ query_string << "\n#{indent}}\n"
30
+ end
31
+ end
32
+
33
+ def to_query(indent: '')
34
+ "#{indent}...#{name}"
35
+ end
36
+
37
+ alias_method :to_s, :to_query
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Client
5
+ module Query
6
+ module HasSelectionSet
7
+ ID_FIELD_NAME = 'id'
8
+ INVALID_FIELD = Class.new(StandardError)
9
+ UNDEFINED_FRAGMENT = Class.new(StandardError)
10
+
11
+ attr_accessor :selection_set
12
+
13
+ def add_connection(connection_name, as: nil, **arguments)
14
+ node_field = nil
15
+
16
+ add_field(connection_name, as: as, **arguments) do |connection|
17
+ connection.add_field('edges') do |edges|
18
+ edges.add_field('cursor')
19
+ edges.add_field('node') do |node|
20
+ node_field = node
21
+ yield node
22
+ end
23
+ end
24
+
25
+ connection.add_field('pageInfo') do |page_info|
26
+ page_info.add_field('hasPreviousPage')
27
+ page_info.add_field('hasNextPage')
28
+ end
29
+ end
30
+
31
+ node_field
32
+ end
33
+
34
+ def add_field(field_name, as: nil, **arguments)
35
+ field_defn = resolve(field_name)
36
+ field = Field.new(field_defn, arguments: arguments, as: as, document: document)
37
+ selection_set.add_field(field)
38
+
39
+ field.add_field(ID_FIELD_NAME) if field.node?
40
+
41
+ if block_given?
42
+ yield field
43
+ else
44
+ field
45
+ end
46
+ end
47
+
48
+ def add_fields(*field_names)
49
+ field_names.each do |field_name|
50
+ add_field(field_name)
51
+ end
52
+ end
53
+
54
+ def add_fragment(fragment_name)
55
+ fragment = document.fragments.fetch(fragment_name) do
56
+ raise UNDEFINED_FRAGMENT, "a fragment named #{fragment_name} has not been defined"
57
+ end
58
+
59
+ selection_set.add_fragment(fragment)
60
+ end
61
+
62
+ private
63
+
64
+ def resolve(field_name)
65
+ resolver_type.field(field_name) do
66
+ raise INVALID_FIELD, "#{field_name} is not a valid field for #{resolver_type}"
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Client
5
+ module Query
6
+ class InlineFragment
7
+ include HasSelectionSet
8
+
9
+ attr_reader :document, :type
10
+
11
+ def initialize(type, document:)
12
+ @type = type
13
+ @document = document
14
+ @selection_set = SelectionSet.new
15
+
16
+ yield self if block_given?
17
+ end
18
+
19
+ def resolver_type
20
+ type
21
+ end
22
+
23
+ def to_query(indent: '')
24
+ indent.dup.tap do |query_string|
25
+ query_string << "... on #{type.name} {\n"
26
+ query_string << selection_set.to_query(indent)
27
+ query_string << "\n#{indent}}"
28
+ end
29
+ end
30
+
31
+ alias_method :to_s, :to_query
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Client
5
+ module Query
6
+ class MutationDocument
7
+ def self.new(schema, name = nil)
8
+ document = Document.new(schema)
9
+ mutation = document.add_mutation(name)
10
+
11
+ if block_given?
12
+ yield mutation
13
+ end
14
+
15
+ mutation
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Client
5
+ module Query
6
+ class Operation
7
+ include HasSelectionSet
8
+
9
+ attr_reader :document, :name, :selection_set, :variables
10
+
11
+ def initialize(document, name: nil, variables: {})
12
+ @document = document
13
+ @name = name
14
+ @selection_set = SelectionSet.new
15
+ @variables = variables
16
+
17
+ yield self if block_given?
18
+ end
19
+
20
+ def schema
21
+ document.schema
22
+ end
23
+
24
+ def to_query
25
+ operation_type.dup.tap do |query_string|
26
+ query_string << " #{name}" if name
27
+ query_string << "(#{variables_string.join(', ')})" if variables.any?
28
+ query_string << " {\n"
29
+ query_string << selection_set.to_query
30
+ query_string << "\n}\n"
31
+ end
32
+ end
33
+
34
+ alias_method :to_s, :to_query
35
+
36
+ private
37
+
38
+ def variables_string
39
+ variables.map do |name, type|
40
+ "$#{name}: #{type}"
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Client
5
+ module Query
6
+ class MutationOperation < Operation
7
+ def operation_type
8
+ 'mutation'
9
+ end
10
+
11
+ def resolver_type
12
+ schema.type(schema.mutation_root_name)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end