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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yaml +37 -0
- data/.rspec +1 -0
- data/.rubocop_todo.yml +3 -0
- data/CHANGELOG.md +38 -9
- data/Gemfile.lock +30 -30
- data/README.md +155 -13
- data/examples/departments_service_class_examples.rb +51 -0
- data/examples/mutation_examples.rb +17 -0
- data/examples/query_examples.rb +21 -0
- data/examples/raw_query_examples.rb +24 -0
- data/graphql_connector.gemspec +7 -7
- data/lib/graphql_connector.rb +10 -21
- data/lib/graphql_connector/base_server_type.rb +62 -0
- data/lib/graphql_connector/configuration.rb +14 -3
- data/lib/graphql_connector/formatters/base_format.rb +81 -0
- data/lib/graphql_connector/formatters/mutation_format.rb +14 -0
- data/lib/graphql_connector/formatters/query_format.rb +14 -0
- data/lib/graphql_connector/http_client.rb +49 -0
- data/lib/graphql_connector/service_classable/class_method_validator.rb +31 -0
- data/lib/graphql_connector/service_classable/params_validator.rb +24 -0
- data/lib/graphql_connector/service_classable/queryable.rb +108 -0
- data/lib/graphql_connector/service_classable/return_fields_validator.rb +43 -0
- data/lib/graphql_connector/version.rb +1 -1
- metadata +27 -27
- data/.travis.yml +0 -9
- data/lib/graphql_connector/query_builder.rb +0 -59
@@ -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
|
+
)
|
data/graphql_connector.gemspec
CHANGED
@@ -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 = [
|
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 = '
|
14
|
-
spec.description = '
|
15
|
-
'
|
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.
|
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
|
data/lib/graphql_connector.rb
CHANGED
@@ -1,8 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'graphql_connector/version'
|
4
|
-
require 'graphql_connector/
|
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
|
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
|
-
|
6
|
+
attr_reader :base_server_types
|
7
7
|
|
8
8
|
def initialize
|
9
|
-
@
|
10
|
-
|
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,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
|