graphql_connector 0.2.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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