graphql_connector 0.2.0 → 1.2.1

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.
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
4
+ # ! The following examples are used together with
5
+ # ! https://github.com/sushie1984/rails-graphql-server
6
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
7
+
8
+ uri = 'http://rails-graphql-server.herokuapp.com/api/graphql'
9
+ GraphqlConnector.configure do |config|
10
+ config.add_server(name: 'RailsGraphqlServer', uri: uri, headers: {})
11
+ end
12
+
13
+ department_input = { attributes: { name: 'One', location: 'Berlin' } }
14
+ return_fields = ['department': ['id', 'name', 'employees' => ['yearlySalary']]]
15
+ GraphqlConnector::RailsGraphqlServer.mutation('createDepartment',
16
+ { input: department_input },
17
+ return_fields)
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
4
+ # ! The following examples are used together with
5
+ # ! https://github.com/sushie1984/rails-graphql-server
6
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
7
+
8
+ uri = 'http://rails-graphql-server.herokuapp.com/api/graphql'
9
+ GraphqlConnector.configure do |config|
10
+ config.add_server(name: 'RailsGraphqlServer', uri: uri, headers: {})
11
+ end
12
+
13
+ GraphqlConnector::RailsGraphqlServer.query('departments',
14
+ {},
15
+ ['id', 'name',
16
+ 'employees' => ['yearlySalary']])
17
+
18
+ GraphqlConnector::RailsGraphqlServer.query('departments',
19
+ { id: %w[1 2] },
20
+ ['id', 'name',
21
+ 'employees' => ['yearlySalary']])
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
4
+ # ! The following examples are used together with
5
+ # ! https://github.com/sushie1984/rails-graphql-server
6
+ # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
7
+
8
+ GraphqlConnector.configure do |config|
9
+ config.add_server(name: 'RailsGraphqlServer',
10
+ uri: 'http://rails-graphql-server.herokuapp.com/api/graphql',
11
+ headers: {})
12
+ end
13
+
14
+ GraphqlConnector::RailsGraphqlServer.raw_query(
15
+ 'query { departments { id name employees { yearlySalary } } }'
16
+ )
17
+
18
+ GraphqlConnector::RailsGraphqlServer.raw_query(
19
+ 'query departments($id: [ID!]) {
20
+ departments(id: $id) { name employees { name }
21
+ }
22
+ }',
23
+ variables: { id: %w[1 2] }
24
+ )
@@ -7,12 +7,13 @@ require 'graphql_connector/version'
7
7
  Gem::Specification.new do |spec|
8
8
  spec.name = 'graphql_connector'
9
9
  spec.version = GraphqlConnector::VERSION
10
- spec.authors = ['Garllon']
11
- spec.email = ['palluthe.bennet@gmail.com']
10
+ spec.authors = %w[Garllon sushie1984]
11
+ spec.email = ['palluthe.bennet@gmail.com', 'sascha_burku@yahoo.de']
12
12
 
13
- spec.summary = 'Simple GraphQL client'
14
- spec.description = 'Simple grahql client to query with your own raw string'\
15
- 'or with the samll helper method query.'
13
+ spec.summary = 'GraphQL client'
14
+ spec.description = 'Grahql client to query with your own raw string, '\
15
+ 'with the small helper method query or with service '\
16
+ 'class inclusion.'
16
17
  spec.homepage = 'https://github.com/Garllon/graphql_connector/blob/master/README.md'
17
18
  spec.license = 'MIT'
18
19
 
@@ -30,11 +31,10 @@ Gem::Specification.new do |spec|
30
31
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
32
  spec.require_paths = ['lib']
32
33
 
33
- spec.add_dependency 'httparty', '~> 0.17'
34
+ spec.add_dependency 'httparty', '~> 0.16'
34
35
 
35
36
  spec.add_development_dependency 'bundler', '~> 2.0'
36
37
  spec.add_development_dependency 'pry', '~> 0.10'
37
- spec.add_development_dependency 'rake', '~> 10.0'
38
38
  spec.add_development_dependency 'rspec', '~> 3.8'
39
39
  spec.add_development_dependency 'rubocop', '~> 0.75'
40
40
  end
@@ -1,8 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'graphql_connector/version'
4
- require 'graphql_connector/query_builder'
4
+ require 'graphql_connector/formatters/base_format'
5
+ require 'graphql_connector/formatters/query_format'
6
+ require 'graphql_connector/formatters/mutation_format'
5
7
  require 'graphql_connector/configuration'
8
+ require 'graphql_connector/http_client'
9
+ require 'graphql_connector/base_server_type'
10
+ require 'graphql_connector/service_classable/class_method_validator'
11
+ require 'graphql_connector/service_classable/params_validator'
12
+ require 'graphql_connector/service_classable/return_fields_validator'
13
+ require 'graphql_connector/service_classable/queryable'
6
14
  require 'graphql_connector/custom_attribute_error'
7
15
  require 'httparty'
8
16
 
@@ -19,29 +27,10 @@ module GraphqlConnector
19
27
  end
20
28
 
21
29
  def self.reset
22
- @configuration = Configuration.new
30
+ @configuration.reset!
23
31
  end
24
32
 
25
33
  def self.configure
26
34
  yield(configuration)
27
35
  end
28
-
29
- def self.query(model, conditions, selected_fields)
30
- query_string = QueryBuilder.new(model, conditions, selected_fields).create
31
- parsed_body = raw_query(query_string)
32
- OpenStruct.new(parsed_body['data'][model])
33
- end
34
-
35
- def self.raw_query(query_string)
36
- response = HTTParty.post(GraphqlConnector.configuration.host,
37
- headers: GraphqlConnector.configuration.headers,
38
- body: { query: query_string })
39
- parsed_body = JSON.parse(response.body)
40
-
41
- if parsed_body.key? 'errors'
42
- raise CustomAttributeError, parsed_body['errors']
43
- end
44
-
45
- parsed_body
46
- end
47
36
  end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlConnector
4
+ class BaseServerTypeAlreadyExistsError < StandardError; end
5
+ # Class to wrap http_client calls under a specific namespaced class
6
+ class BaseServerType
7
+ class << self
8
+ def build(name, uri, headers)
9
+ verify_new_client_type_for!(name)
10
+ base_class = class_with(uri, headers)
11
+ base_object = GraphqlConnector.const_set(name, base_class)
12
+ inject_http_client_delegations(base_object)
13
+ create_service_class_module(base_object)
14
+
15
+ base_object
16
+ end
17
+
18
+ private
19
+
20
+ def verify_new_client_type_for!(name)
21
+ return unless GraphqlConnector.const_defined?(name)
22
+
23
+ raise BaseServerTypeAlreadyExistsError,
24
+ "The name: #{name} is already in use. Check your "\
25
+ 'configuration!'
26
+ end
27
+
28
+ def create_service_class_module(base_object)
29
+ base_object.class_eval <<-METHOD, __FILE__, __LINE__ + 1
30
+ module Query
31
+ def self.extended(base)
32
+ base.extend(GraphqlConnector::ServiceClassable::Queryable)
33
+ end
34
+
35
+ def http_client
36
+ #{base_object}.http_client
37
+ end
38
+ end
39
+ METHOD
40
+ end
41
+
42
+ def class_with(uri, headers)
43
+ Class.new do
44
+ attr_accessor :uri, :headers
45
+ @uri = uri
46
+ @headers = headers
47
+ end
48
+ end
49
+
50
+ def inject_http_client_delegations(base_object)
51
+ base_object.instance_eval do
52
+ extend SingleForwardable
53
+ def_delegators :http_client, :query, :raw_query, :mutation
54
+
55
+ def http_client
56
+ @http_client ||= GraphqlConnector::HttpClient.new(@uri, @headers)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -3,11 +3,22 @@
3
3
  module GraphqlConnector
4
4
  # The configuration template file for the gem.
5
5
  class Configuration
6
- attr_accessor :host, :headers
6
+ attr_reader :base_server_types
7
7
 
8
8
  def initialize
9
- @host = nil
10
- @headers = nil
9
+ @base_server_types = {}
10
+ end
11
+
12
+ def add_server(name:, uri:, headers:)
13
+ @base_server_types[name] = BaseServerType.build(name, uri, headers)
14
+ end
15
+
16
+ def reset!
17
+ @base_server_types.keys.each do |name|
18
+ GraphqlConnector.const_get(name).send :remove_const, 'Query'
19
+ GraphqlConnector.send :remove_const, name
20
+ end
21
+ @base_server_types = {}
11
22
  end
12
23
  end
13
24
  end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlConnector
4
+ module Formatters
5
+ # Class that returns in query or mutation string format
6
+ class BaseFormat
7
+ def initialize(model, conditions, selected_fields)
8
+ @model = model
9
+ @conditions = conditions
10
+ @selected_fields = selected_fields
11
+ end
12
+
13
+ def create
14
+ <<-STRING
15
+ #{query_type} {
16
+ #{@model}#{arguments} {
17
+ #{parse_fields(@selected_fields)}
18
+ }
19
+ }
20
+ STRING
21
+ end
22
+
23
+ private
24
+
25
+ def arguments
26
+ conditions = @conditions.each_with_object([]) do |(key, value), array|
27
+ array << "#{key}: #{value_as_parameter(value)}"
28
+ end
29
+
30
+ return '' if conditions.empty?
31
+
32
+ "(#{conditions.join(', ')})"
33
+ end
34
+
35
+ def value_as_parameter(value)
36
+ case value
37
+ when Array
38
+ casted_values = value.map { |v| value_as_parameter(v) }
39
+ "[#{casted_values.join(',')}]"
40
+ when Hash
41
+ casted_values = value.map { |k, v| "#{k}: #{value_as_parameter(v)}" }
42
+ "{#{casted_values.join(',')}}"
43
+ else
44
+ scalar_types(value)
45
+ end
46
+ end
47
+
48
+ def scalar_types(value)
49
+ case value
50
+ when TrueClass, FalseClass, Integer, Float
51
+ value
52
+ else # fallback to string
53
+ '"' + value.to_s + '"'
54
+ end
55
+ end
56
+
57
+ def parse_fields(selected_fields)
58
+ results = selected_fields.map do |field|
59
+ case field
60
+ when Hash
61
+ handle_association(field)
62
+ else
63
+ field
64
+ end
65
+ end
66
+
67
+ results.join(' ')
68
+ end
69
+
70
+ def handle_association(hash)
71
+ hash.map do |key, fields|
72
+ "#{key} { #{parse_fields(fields)} }"
73
+ end
74
+ end
75
+
76
+ def query_type
77
+ raise 'query_type undefined'
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlConnector
4
+ module Formatters
5
+ # Class that returns in mutation string format
6
+ class MutationFormat < BaseFormat
7
+ private
8
+
9
+ def query_type
10
+ 'mutation'
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlConnector
4
+ module Formatters
5
+ # Class that returns in query string format
6
+ class QueryFormat < BaseFormat
7
+ private
8
+
9
+ def query_type
10
+ 'query'
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlConnector
4
+ # Wrapper class for HTTParty post query
5
+ class HttpClient
6
+ def initialize(uri, headers)
7
+ @uri = uri
8
+ @headers = headers
9
+ end
10
+
11
+ def query(model, conditions, selected_fields)
12
+ query_string =
13
+ Formatters::QueryFormat.new(model, conditions, selected_fields).create
14
+ parsed_body = raw_query(query_string)
15
+ format_body(parsed_body['data'][model.to_s])
16
+ end
17
+
18
+ def mutation(model, inputs, selected_fields)
19
+ query_string =
20
+ Formatters::MutationFormat.new(model, inputs, selected_fields).create
21
+ parsed_body = raw_query(query_string)
22
+ format_body(parsed_body['data'][model.to_s])
23
+ end
24
+
25
+ def raw_query(query_string, variables: {})
26
+ response = HTTParty.post(@uri,
27
+ headers: @headers,
28
+ body: { query: query_string,
29
+ variables: variables })
30
+ parsed_body = JSON.parse(response.body)
31
+ verify_response!(parsed_body)
32
+ parsed_body
33
+ end
34
+
35
+ private
36
+
37
+ def format_body(response_body)
38
+ return OpenStruct.new(response_body) unless response_body.is_a? Array
39
+
40
+ response_body.map { |entry| OpenStruct.new(entry) }
41
+ end
42
+
43
+ def verify_response!(parsed_body)
44
+ return unless parsed_body.key? 'errors'
45
+
46
+ raise CustomAttributeError, parsed_body['errors']
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlConnector
4
+ module ServiceClassable
5
+ class ClassMethodAlreadyImplementedError < StandardError; end
6
+ class InvalidClassTypeError < StandardError; end
7
+ # Checks whether a class method for a specific graphql query is in an
8
+ # expected format
9
+ class ClassMethodValidator
10
+ class << self
11
+ def validate_class_method(class_method_name, invoked_class)
12
+ return unless invoked_class.singleton_methods
13
+ .map(&:to_s)
14
+ .include?(class_method_name.to_s)
15
+
16
+ error_msg = "The method '#{class_method_name}: ... ' is "\
17
+ 'already implemented within the context of '\
18
+ "#{invoked_class} and therefore cannot be used again!"
19
+ raise ClassMethodAlreadyImplementedError, error_msg
20
+ end
21
+
22
+ def validate_element_class_type(element, class_types)
23
+ return if element.class == class_types
24
+
25
+ raise InvalidClassTypeError, "Please ensure that #{element} is a"\
26
+ "#{class_types}!"
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphqlConnector
4
+ module ServiceClassable
5
+ class InvalidParamsError < StandardError; end
6
+ # Checks whether params for a specifc graphql query are in an expected
7
+ # format
8
+ class ParamsValidator
9
+ class << self
10
+ def validate(query_params)
11
+ params = [query_params].flatten
12
+ return if params.empty? ||
13
+ params.map(&:class).uniq == [Symbol] ||
14
+ params.map(&:class).uniq == [String]
15
+
16
+ raise InvalidParamsError,
17
+ "Please ensure that #{query_params} are either "\
18
+ 'Symbols/Strings as described in the README '\
19
+ '(e.g.: params: [:id, :name])'
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end