-
# frozen_string_literal: true
-
-
1
require_relative './gqli/dsl'
-
1
require_relative './gqli/client'
-
1
require_relative './gqli/introspection'
-
1
require_relative './gqli/version'
-
1
require_relative './gqli/clients'
-
# frozen_string_literal: true
-
-
1
require_relative './enum_value'
-
-
1
module GQLi
-
# Base class for GraphQL type wrappers
-
1
class Base
-
1
attr_reader :__name, :__depth, :__nodes
-
-
1
def initialize(name = nil, depth = 0, &block)
-
378
@__name = name
-
378
@__depth = depth
-
378
@__nodes = []
-
378
instance_eval(&block) unless block.nil?
-
end
-
-
# Inlines fragment nodes into current node
-
1
def ___(fragment)
-
10
@__nodes += __clone_nodes(fragment)
-
end
-
-
# Adds type match node
-
1
def __on(type_name, &block)
-
3
__node("... on #{type_name}", {}, &block)
-
end
-
-
# Adds children node into current node
-
1
def __node(name, params = {}, &block)
-
158
require_relative './node'
-
158
@__nodes << Node.new(name, params, __depth + 1, &block)
-
end
-
-
# Creates an EnumType value
-
1
def __enum(value)
-
2
EnumValue.new(value)
-
end
-
-
1
protected
-
-
1
def __clone_nodes(node_container)
-
182
require_relative './node'
-
182
__clone(node_container.__nodes).map do |n|
-
172
node = Node.new(n.__name, n.__params, __depth + 1)
-
172
node.instance_variable_set(
-
:@__nodes,
-
node.send(:__clone_nodes, n)
-
)
-
172
node
-
end
-
end
-
-
1
def __clone(obj)
-
182
Marshal.load(Marshal.dump(obj))
-
end
-
-
1
def __params_from_args(args)
-
149
args.empty? ? {} : args[0]
-
end
-
-
1
def method_missing(name, *args, &block)
-
149
__node(name.to_s, __params_from_args(args), &block)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require 'http'
-
1
require 'json'
-
1
require_relative './response'
-
1
require_relative './introspection'
-
1
require_relative './version'
-
-
1
module GQLi
-
# GraphQL HTTP Client
-
1
class Client
-
1
attr_reader :url, :params, :headers, :validate_query, :schema
-
-
1
def initialize(url, params: {}, headers: {}, validate_query: true)
-
27
@url = url
-
27
@params = params
-
27
@headers = headers
-
27
@validate_query = validate_query
-
-
27
@schema = Introspection.new(self) if validate_query
-
end
-
-
# Executes a query
-
# If validations are enabled, will perform validation check before request.
-
1
def execute(query)
-
4
if validate_query
-
3
validation = schema.validate(query)
-
3
fail validation_error_message(validation) unless validation.valid?
-
end
-
-
3
execute!(query)
-
end
-
-
# Executres a query
-
# Ignores validations
-
1
def execute!(query)
-
28
http_response = HTTP.headers(request_headers).post(@url, params: @params, json: { query: query.to_gql })
-
-
28
fail "Error: #{http_response.reason}\nBody: #{http_response.body}" if http_response.status >= 300
-
-
26
data = JSON.parse(http_response.to_s)['data']
-
-
26
Response.new(data, query)
-
end
-
-
# Validates a query against the schema
-
1
def valid?(query)
-
return true unless validate_query
-
-
schema.valid?(query)
-
end
-
-
1
protected
-
-
1
def validation_error_message(validation)
-
1
<<~ERROR
-
Validation Error: query is invalid - HTTP Request not sent.
-
-
Errors:
-
- #{validation.errors.join("\n - ")}
-
ERROR
-
end
-
-
1
def request_headers
-
{
-
accept: 'application/json',
-
user_agent: "gqli.rb/#{VERSION}; http.rb/#{HTTP::VERSION}"
-
28
}.merge(@headers)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require_relative './clients/contentful'
-
1
require_relative './clients/github'
-
# frozen_string_literal: true
-
-
1
module GQLi
-
# Module for creating a Contentful GraphQL client
-
1
module Contentful
-
# Creates a Contentful GraphQL client
-
1
def self.create(space, access_token, environment: nil, validate_query: true)
-
26
api_url = "https://graphql.contentful.com/content/v1/spaces/#{space}"
-
26
api_url += "/environments/#{environment}" unless environment.nil?
-
-
26
GQLi::Client.new(
-
api_url,
-
headers: {
-
'Authorization' => "Bearer #{access_token}"
-
},
-
validate_query: validate_query
-
)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module GQLi
-
# Module for creating a Github GraphQL client
-
1
module Github
-
# Creates a Github GraphQL client
-
1
def self.create(access_token, validate_query: true)
-
1
GQLi::Client.new(
-
'https://api.github.com/graphql',
-
headers: {
-
'Authorization' => "Bearer #{access_token}"
-
},
-
validate_query: validate_query
-
)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require_relative './query'
-
1
require_relative './subscription'
-
1
require_relative './fragment'
-
1
require_relative './enum_value'
-
-
1
module GQLi
-
# GraphQL-like DSL methods
-
1
module DSL
-
# Creates a Query object
-
#
-
# Can be used at a class level
-
1
def self.query(name = nil, &block)
-
34
Query.new(name, &block)
-
end
-
-
# Creates a Subscription object
-
#
-
# Can be used at a class level
-
1
def self.subscription(name = nil, &block)
-
2
Subscription.new(name, &block)
-
end
-
-
# Creates a Fragment object
-
#
-
# Can be used at a class level
-
1
def self.fragment(name, on, &block)
-
3
Fragment.new(name, on, &block)
-
end
-
-
# Creates a EnumValue object
-
#
-
# Can be used at a class level
-
1
def self.enum(value)
-
1
EnumValue.new(value)
-
end
-
-
# Creates a Query object
-
#
-
# Can be used at an instance level
-
1
def query(name = nil, &block)
-
3
Query.new(name, &block)
-
end
-
-
# Creates a Subscription object
-
#
-
# Can be used at a class level
-
1
def subscription(name = nil, &block)
-
2
Subscription.new(name, &block)
-
end
-
-
# Creates a Fragment object
-
#
-
# Can be used at an instance level
-
1
def fragment(name, on, &block)
-
4
Fragment.new(name, on, &block)
-
end
-
-
# Creates a EnumValue object
-
#
-
# Can be used at an instance level
-
1
def enum(value)
-
1
EnumValue.new(value)
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module GQLi
-
# Wrapper for Enum values
-
1
class EnumValue
-
1
attr_reader :value
-
-
1
def initialize(value)
-
4
@value = value
-
end
-
-
# Serializes the enum value to string
-
1
def to_s
-
5
value.to_s
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require_relative './base'
-
1
require_relative './node'
-
-
1
module GQLi
-
# Fragment wrapper
-
1
class Fragment < Base
-
1
attr_reader :__on_type
-
-
1
def initialize(name, on, &block)
-
7
super(name, 0, &block)
-
7
@__on_type = on
-
end
-
-
# Serializes to a GraphQL string
-
1
def to_gql
-
2
<<~GQL
-
fragment #{__name} on #{__on_type} {
-
#{__nodes.map(&:to_gql).join("\n")}
-
}
-
GQL
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require_relative './dsl'
-
1
require_relative './validation'
-
-
1
module GQLi
-
# Introspection schema and validator
-
1
class Introspection
-
1
extend DSL
-
-
# Specific type kind introspection fragment
-
1
TypeRef = fragment('TypeRef', '__Type') {
-
1
kind
-
1
name
-
1
ofType {
-
1
kind
-
1
name
-
1
ofType {
-
1
kind
-
1
name
-
1
ofType {
-
1
kind
-
1
name
-
}
-
}
-
}
-
}
-
-
# Input value introspection fragment
-
1
InputValue = fragment('InputValue', '__InputValue') {
-
1
name
-
1
description
-
2
type { ___ TypeRef }
-
1
defaultValue
-
}
-
-
# Type introspection fragment
-
1
FullType = fragment('FullType', '__Type') {
-
1
kind
-
1
name
-
1
description
-
1
fields(includeDeprecated: true) {
-
1
name
-
1
description
-
2
args { ___ InputValue }
-
2
type { ___ TypeRef }
-
1
isDeprecated
-
1
deprecationReason
-
}
-
2
inputFields { ___ InputValue }
-
2
interfaces { ___ TypeRef }
-
1
enumValues(includeDeprecated: true) {
-
1
name
-
1
description
-
1
isDeprecated
-
1
deprecationReason
-
}
-
2
possibleTypes { ___ TypeRef }
-
}
-
-
# Query for fetching the complete schema
-
1
IntrospectionQuery = query {
-
1
__schema {
-
2
queryType { name }
-
2
mutationType { name }
-
2
subscriptionType { name }
-
2
types { ___ FullType }
-
1
directives {
-
1
name
-
1
description
-
2
args { ___ InputValue }
-
1
locations
-
}
-
}
-
}
-
-
1
attr_reader :schema, :query_type, :mutation_type, :subscription_type, :types
-
-
1
def initialize(client)
-
24
@schema = client.execute!(IntrospectionQuery).data.__schema
-
24
@query_type = schema.queryType
-
24
@mutation_type = schema.mutationType
-
24
@subscription_type = schema.subscriptionType
-
24
@types = schema.types
-
end
-
-
# Returns the evaluated validation for a query
-
1
def validate(query)
-
31
Validation.new(self, query)
-
end
-
-
# Returns if the query is valid
-
1
def valid?(query)
-
10
validate(query).valid?
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require_relative './base'
-
-
1
module GQLi
-
# Node wrapper
-
1
class Node < Base
-
1
attr_reader :__params
-
-
1
def initialize(name, params = {}, depth = 1, &block)
-
330
super(name, depth, &block)
-
330
@__params = params
-
end
-
-
# Serializes to a GraphQL string
-
1
def to_gql
-
2677
result = ' ' * __depth + __name
-
2677
result += __params_to_s(__params, true) unless __params.empty?
-
2677
unless __nodes.empty?
-
863
result += " {\n"
-
863
result += __nodes.map(&:to_gql).join("\n")
-
863
result += "\n#{' ' * __depth}}"
-
end
-
-
2677
result
-
end
-
-
1
private
-
-
1
def __directive?(params)
-
65
params.size == 1 && params.keys.first.to_s.start_with?('@')
-
end
-
-
1
def __directive(params)
-
2
params.first.tap do |directive, directive_params|
-
2
return " #{directive}#{__params_to_s(directive_params, true)}"
-
end
-
end
-
-
1
def __params_to_s(params, initial = false)
-
126
case params
-
when ::Hash
-
65
return __directive(params) if __directive?(params)
-
-
63
result = params.map do |k, v|
-
64
"#{k}: #{__params_to_s(v)}"
-
end.join(', ')
-
-
63
return "(#{result})" if initial
-
-
5
"{#{result}}"
-
when ::Array
-
3
"[#{params.map { |p| __params_to_s(p) }.join(', ')}]"
-
when ::String
-
4
"\"#{params}\""
-
else
-
56
params.to_s
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require_relative './base'
-
-
1
module GQLi
-
# Query node
-
1
class Query < Base
-
# Serializes to a GraphQL string
-
1
def to_gql
-
43
result = <<~GQL
-
query #{__name ? __name + ' ' : ''}{
-
#{__nodes.map(&:to_gql).join("\n")}
-
}
-
GQL
-
-
43
result.lstrip
-
end
-
-
# Delegates itself to the client to be executed
-
1
def __execute(client)
-
1
client.execute(self)
-
end
-
-
# Serializes to a GraphQL string
-
1
def to_s
-
1
to_gql
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require 'hashie/mash'
-
-
1
module GQLi
-
# Response object wrapper
-
1
class Response
-
1
attr_reader :data, :query
-
-
1
def initialize(data, query)
-
26
@data = Hashie::Mash.new(data)
-
26
@query = query
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module GQLi
-
# Query node
-
1
class Subscription < Base
-
# Serializes to a GraphQL string
-
1
def to_gql
-
8
result = <<~GQL
-
subscription #{__name ? __name + ' ' : ''}{
-
#{__nodes.map(&:to_gql).join("\n")}
-
}
-
GQL
-
-
8
result.lstrip
-
end
-
-
# Delegates itself to the client to be executed
-
1
def __execute(client)
-
client.execute(self)
-
end
-
-
# Serializes to a GraphQL string
-
1
def to_s
-
2
to_gql
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module GQLi
-
# Validations
-
1
class Validation
-
1
attr_reader :schema, :query, :errors
-
-
1
def initialize(schema, query)
-
31
@schema = schema
-
31
@query = query
-
31
@errors = []
-
-
31
validate
-
end
-
-
# Returns wether the query is valid or not
-
1
def valid?
-
31
errors.empty?
-
end
-
-
1
protected
-
-
1
def validate
-
31
fail 'Not a Query object' unless query.is_a?(Query)
-
-
62
query_type = types.find { |t| t.name.casecmp('query').zero? }
-
31
query.__nodes.each do |node|
-
32
begin
-
32
validate_node(query_type, node)
-
rescue StandardError => e
-
12
errors << e
-
end
-
end
-
-
31
true
-
rescue StandardError => e
-
errors << e
-
end
-
-
1
private
-
-
1
def remove_alias(name)
-
87
return name unless name.include?(':')
-
-
2
name.split(':')[1].strip
-
end
-
-
1
def types
-
150
schema.types
-
end
-
-
1
def validate_node(parent_type, node)
-
94
validate_directives(node)
-
-
91
return valid_match_node?(parent_type, node) if node.__name.start_with?('... on')
-
-
87
node_name = remove_alias(node.__name)
-
-
477
node_type = parent_type.fetch('fields', []).find { |f| f.name == node_name }
-
87
fail "Node type not found for '#{node_name}'" if node_type.nil?
-
-
84
validate_params(node_type, node)
-
-
84
resolved_node_type = type_for(node_type)
-
84
fail "Node type not found for '#{node_name}'" if resolved_node_type.nil?
-
-
84
validate_nesting_node(resolved_node_type, node)
-
-
142
node.__nodes.each { |n| validate_node(resolved_node_type, n) }
-
end
-
-
1
def valid_match_node?(parent_type, node)
-
14
return if parent_type.fetch('possibleTypes', []).find { |t| t.name == node.__name.gsub('... on ', '') }
-
2
fail "Match type '#{node.__name.gsub('... on ', '')}' invalid"
-
end
-
-
1
def validate_directives(node)
-
94
return unless node.__params.size >= 1
-
21
node.__params.first.tap do |k, v|
-
21
break unless k.to_s.start_with?('@')
-
-
7
fail "Directive unknown '#{k}'" unless %i[@include @skip].include?(k)
-
6
fail "Missing arguments for directive '#{k}'" if v.nil? || !v.is_a?(::Hash) || v.empty?
-
4
v.each do |arg, value|
-
5
begin
-
5
fail "Invalid argument '#{arg}' for directive '#{k}'" if arg.to_s != 'if'
-
3
fail "Invalid value for 'if`, must be a boolean" if value != !!value
-
rescue StandardError => e
-
3
errors << e
-
end
-
end
-
end
-
end
-
-
1
def validate_params(node_type, node)
-
107
node.__params.reject { |p, _| p.to_s.start_with?('@') }.each do |param, value|
-
19
begin
-
105
arg = node_type.fetch('args', []).find { |a| a.name == param.to_s }
-
19
fail "Invalid argument '#{param}'" if arg.nil?
-
-
17
arg_type = type_for(arg)
-
17
fail "Argument type not found for '#{param}'" if arg_type.nil?
-
-
17
validate_value_for_type(arg_type, value, param)
-
rescue StandardError => e
-
8
errors << e
-
end
-
end
-
end
-
-
1
def validate_nesting_node(node_type, node)
-
84
fail "Invalid object for node '#{node.__name}'" unless valid_object_node?(node_type, node)
-
end
-
-
1
def valid_object_node?(node_type, node)
-
84
return false if %w[OBJECT INTERFACE].include?(node_type.kind) && node.__nodes.empty?
-
80
true
-
end
-
-
1
def valid_array_node?(node_type, node)
-
return false if %w[OBJECT INTERFACE].include?(node_type.kind) && node.__nodes.empty?
-
true
-
end
-
-
1
def value_type_error(is_type, should_be, for_arg)
-
6
should_be = should_be.kind == 'ENUM' ? 'Enum' : should_be.name
-
6
additional_message = '. Wrap the value with `__enum`.' if should_be == 'Enum'
-
-
6
fail "Value is '#{is_type}', but should be '#{should_be}' for '#{for_arg}'#{additional_message}"
-
end
-
-
1
def validate_value_for_type(arg_type, value, for_arg)
-
27
case value
-
when EnumValue
-
2
if arg_type.kind == 'ENUM' && !arg_type.enumValues.map(&:name).include?(value.to_s)
-
fail "Invalid value for Enum '#{arg_type.name}' for '#{for_arg}'"
-
end
-
when ::String
-
12
unless arg_type.name == 'String' || arg_type.name == 'ID'
-
4
value_type_error('String or ID', arg_type, for_arg)
-
end
-
when ::Integer
-
5
value_type_error('Integer', arg_type, for_arg) unless arg_type.name == 'Int'
-
when ::Float
-
value_type_error('Float', arg_type, for_arg) unless arg_type.name == 'Float'
-
when ::Hash
-
8
validate_hash_value(arg_type, value, for_arg)
-
when true, false
-
value_type_error('Boolean', arg_type, for_arg) unless arg_type.name == 'Boolean'
-
else
-
value_type_error(value.class.name, arg_type, for_arg)
-
end
-
end
-
-
1
def validate_hash_value(arg_type, value, for_arg)
-
8
value_type_error('Object', arg_type.name, for_arg) unless arg_type.kind == 'INPUT_OBJECT'
-
-
252
type = types.find { |f| f.name == arg_type.name }
-
8
fail "Type not found for '#{arg_type.name}'" if type.nil?
-
-
8
value.each do |k, v|
-
10
begin
-
119
input_field = type.fetch('inputFields', []).find { |f| f.name == k.to_s }
-
10
fail "Input field definition not found for '#{k}'" if input_field.nil?
-
-
10
input_field_type = type_for(input_field)
-
10
fail "Input field type not found for '#{k}'" if input_field_type.nil?
-
-
10
validate_value_for_type(input_field_type, v, k)
-
rescue StandardError => e
-
errors << e
-
end
-
end
-
end
-
-
1
def type_for(field_type)
-
111
type = case field_type.type.kind
-
when 'NON_NULL'
-
24
non_null_type(field_type.type.ofType)
-
when 'LIST'
-
10
field_type.type.ofType
-
when 'OBJECT', 'INTERFACE', 'INPUT_OBJECT'
-
38
field_type.type
-
when 'SCALAR'
-
39
field_type.type
-
end
-
-
1639
types.find { |t| t.name == type.name }
-
end
-
-
1
def non_null_type(non_null)
-
24
case non_null.kind
-
when 'LIST'
-
24
non_null.ofType
-
else
-
non_null
-
end
-
end
-
end
-
end
-
1
require 'spec_helper'
-
-
1
class MockGQLInterface
-
1
include GQLi::DSL
-
end
-
-
1
describe GQLi::DSL do
-
16
subject { described_class }
-
-
1
describe 'module level methods' do
-
1
describe '::query' do
-
1
it 'can create a query without name' do
-
1
query = subject.query {
-
1
someField
-
}
-
-
1
expect(query).to be_a GQLi::Query
-
1
expect(query.to_gql).to eq <<~GRAPHQL
-
query {
-
someField
-
}
-
GRAPHQL
-
1
expect(query.to_gql).to eq query.to_s
-
end
-
-
1
it 'can create a query with a name' do
-
1
query = subject.query('FooBar') {
-
1
someOtherField
-
}
-
-
1
expect(query).to be_a GQLi::Query
-
1
expect(query.to_gql).to eq <<~GRAPHQL
-
query FooBar {
-
someOtherField
-
}
-
GRAPHQL
-
end
-
end
-
-
1
describe '::subscription' do
-
1
it 'can create a subscription without name' do
-
1
subscription = subject.subscription {
-
1
someField
-
}
-
-
1
expect(subscription).to be_a GQLi::Subscription
-
1
expect(subscription.to_gql).to eq <<~GRAPHQL
-
subscription {
-
someField
-
}
-
GRAPHQL
-
1
expect(subscription.to_gql).to eq subscription.to_s
-
end
-
-
1
it 'can create a subscription with a name' do
-
1
subscription = subject.subscription('FooBar') {
-
1
someOtherField
-
}
-
-
1
expect(subscription).to be_a GQLi::Subscription
-
1
expect(subscription.to_gql).to eq <<~GRAPHQL
-
subscription FooBar {
-
someOtherField
-
}
-
GRAPHQL
-
end
-
end
-
-
1
describe '::fragment' do
-
1
it 'can create a fragment' do
-
1
fragment = subject.fragment('FooBar', 'Foo') {
-
1
someFooField
-
}
-
-
1
expect(fragment).to be_a GQLi::Fragment
-
1
expect(fragment.to_gql).to eq <<~GRAPHQL
-
fragment FooBar on Foo {
-
someFooField
-
}
-
GRAPHQL
-
end
-
end
-
-
1
describe '::enum' do
-
1
it 'can create an enum value' do
-
1
enum_value = subject.enum('foo')
-
-
1
expect(enum_value).to be_a GQLi::EnumValue
-
1
expect(enum_value.to_s).to eq 'foo'
-
end
-
end
-
end
-
-
1
describe 'instance level methods' do
-
5
subject { MockGQLInterface.new }
-
-
1
describe '#query does the same as ::query' do
-
1
it 'can create a query without name' do
-
1
query = subject.query {
-
1
someField
-
}
-
-
1
expect(query).to be_a GQLi::Query
-
1
expect(query.to_gql).to eq <<~GRAPHQL
-
query {
-
someField
-
}
-
GRAPHQL
-
end
-
-
1
it 'can create a query with a name' do
-
1
query = subject.query('FooBar') {
-
1
someOtherField
-
}
-
-
1
expect(query).to be_a GQLi::Query
-
1
expect(query.to_gql).to eq <<~GRAPHQL
-
query FooBar {
-
someOtherField
-
}
-
GRAPHQL
-
end
-
end
-
-
-
1
describe '#subscription does the same as ::subscription' do
-
-
3
subject { MockGQLInterface.new }
-
-
1
it 'can create a subscription without name' do
-
1
subscription = subject.subscription {
-
1
someField
-
}
-
-
1
expect(subscription).to be_a GQLi::Subscription
-
1
expect(subscription.to_gql).to eq <<~GRAPHQL
-
subscription {
-
someField
-
}
-
GRAPHQL
-
1
expect(subscription.to_gql).to eq subscription.to_s
-
end
-
-
1
it 'can create a subscription with a name' do
-
1
subscription = subject.subscription('FooBar') {
-
1
someOtherField
-
}
-
-
1
expect(subscription).to be_a GQLi::Subscription
-
1
expect(subscription.to_gql).to eq <<~GRAPHQL
-
subscription FooBar {
-
someOtherField
-
}
-
GRAPHQL
-
end
-
end
-
-
1
describe '#fragment does the same as ::fragment' do
-
1
it 'can create a fragment' do
-
1
fragment = subject.fragment('FooBar', 'Foo') {
-
1
someFooField
-
}
-
-
1
expect(fragment).to be_a GQLi::Fragment
-
1
expect(fragment.to_gql).to eq <<~GRAPHQL
-
fragment FooBar on Foo {
-
someFooField
-
}
-
GRAPHQL
-
end
-
end
-
-
1
describe '#enum does the same as ::enum' do
-
1
it 'can create an enum value' do
-
1
enum_value = subject.enum('foo')
-
-
1
expect(enum_value).to be_a GQLi::EnumValue
-
1
expect(enum_value.to_s).to eq 'foo'
-
end
-
end
-
end
-
-
1
describe 'node DSL' do
-
1
it 'nodes can have arguments' do
-
1
query = subject.query {
-
1
someNode(arg1: 100)
-
1
otherNode(arg2: 'some_string')
-
1
moreNodes(arg3: { nestedArg: [1, 2] })
-
}
-
-
1
expect(query.to_gql).to eq <<~GRAPHQL
-
query {
-
someNode(arg1: 100)
-
otherNode(arg2: "some_string")
-
moreNodes(arg3: {nestedArg: [1, 2]})
-
}
-
GRAPHQL
-
end
-
-
1
it 'nodes can have aliases' do
-
1
query = subject.query {
-
__node('pinned: catCollection', where: {
-
sys: { id_in: 'nyancat' }
-
1
}) {
-
1
items {
-
1
name
-
}
-
}
-
__node('unpinned: catCollection', where: {
-
sys: { id_not_in: 'nyancat' }
-
1
}, limit: 4) {
-
1
items {
-
1
name
-
}
-
}
-
}
-
-
1
expect(query.to_gql).to eq <<~GRAPHQL
-
query {
-
pinned: catCollection(where: {sys: {id_in: "nyancat"}}) {
-
items {
-
name
-
}
-
}
-
unpinned: catCollection(where: {sys: {id_not_in: "nyancat"}}, limit: 4) {
-
items {
-
name
-
}
-
}
-
}
-
GRAPHQL
-
end
-
-
1
it 'nodes can have directives' do
-
1
query = subject.query {
-
1
someNode(:@include => { if: true })
-
1
otherNode(:@skip => { if: false })
-
}
-
-
1
expect(query.to_gql).to eq <<~GRAPHQL
-
query {
-
someNode @include(if: true)
-
otherNode @skip(if: false)
-
}
-
GRAPHQL
-
end
-
-
1
it 'nodes can have arbitrarily nested nodes' do
-
1
query = subject.query {
-
1
aNode {
-
1
withChild {
-
1
moreChildren
-
1
andASibiling(someParameterHere: 'just for fun') {
-
1
withEvenMore
-
}
-
}
-
1
alsoASibiling
-
}
-
1
withoutChild
-
}
-
-
1
expect(query.to_gql).to eq <<~GRAPHQL
-
query {
-
aNode {
-
withChild {
-
moreChildren
-
andASibiling(someParameterHere: "just for fun") {
-
withEvenMore
-
}
-
}
-
alsoASibiling
-
}
-
withoutChild
-
}
-
GRAPHQL
-
end
-
-
1
it 'queries can have inlined fragments at any level' do
-
1
NameFragment = subject.fragment('NameFragment', 'Foo') {
-
1
name
-
}
-
-
1
AgeFragment = subject.fragment('AgeFragment', 'Foo') {
-
1
age
-
}
-
-
1
query = subject.query {
-
1
peopleCollection {
-
1
___ NameFragment
-
1
___ AgeFragment
-
}
-
}
-
-
1
expect(query.to_gql).to eq <<~GRAPHQL
-
query {
-
peopleCollection {
-
name
-
age
-
}
-
}
-
GRAPHQL
-
end
-
-
1
it 'queries can have type matchers' do
-
1
query = subject.query {
-
1
catCollection {
-
1
items {
-
1
name
-
1
bestFriend {
-
1
__on('Cat') {
-
1
name
-
}
-
}
-
}
-
}
-
}
-
-
1
expect(query.to_gql).to eq <<~GRAPHQL
-
query {
-
catCollection {
-
items {
-
name
-
bestFriend {
-
... on Cat {
-
name
-
}
-
}
-
}
-
}
-
}
-
GRAPHQL
-
end
-
-
1
it 'can use __node to define nodes that would collide otherwise' do
-
1
query = subject.query {
-
1
catCollection {
-
1
items {
-
1
sys {
-
1
__node('id')
-
}
-
}
-
}
-
}
-
-
1
expect(query.to_gql).to eq <<~GRAPHQL
-
query {
-
catCollection {
-
items {
-
sys {
-
id
-
}
-
}
-
}
-
}
-
GRAPHQL
-
end
-
-
1
it '__node can receive arguments and children nodes' do
-
1
query = subject.query {
-
1
__node('catCollection', limit: 1) {
-
1
items {
-
1
name
-
}
-
}
-
}
-
-
1
expect(query.to_gql).to eq <<~GRAPHQL
-
query {
-
catCollection(limit: 1) {
-
items {
-
name
-
}
-
}
-
}
-
GRAPHQL
-
end
-
-
1
it '__enum can create EnumType values' do
-
1
query = subject.query {
-
1
catCollection(order: __enum('lives_ASC')) {
-
1
items {
-
1
name
-
}
-
}
-
}
-
-
1
expect(query.to_gql).to eq <<~GRAPHQL
-
query {
-
catCollection(order: lives_ASC) {
-
items {
-
name
-
}
-
}
-
}
-
GRAPHQL
-
end
-
end
-
end
-
1
require 'spec_helper'
-
-
1
describe GQLi::Introspection do
-
19
let(:dsl) { GQLi::DSL }
-
1
let(:client) do
-
19
vcr('client') {
-
19
space_id = 'cfexampleapi'
-
19
token = 'b4c0n73n7fu1'
-
19
GQLi::Contentful.create(space_id, token)
-
}
-
end
-
-
20
subject { client.schema }
-
-
1
describe 'introspection schema' do
-
1
it 'queries the API for the schema' do
-
1
expect(subject.types).not_to be_empty
-
-
1
expect(subject.types.map(&:name)).to include('Cat', 'CatCollection', 'Human')
-
end
-
end
-
-
1
describe 'validations' do
-
1
it 'valid query returns true' do
-
1
query = dsl.query {
-
catCollection(
-
locale:"en-US",
-
limit: 1,
-
where: {
-
name:"Nyan Cat",
-
OR: {
-
name:"Happy Cat"
-
}
-
}
-
1
) {
-
1
items {
-
1
name
-
1
color
-
1
birthday
-
1
lives
-
1
bestFriend {
-
1
__on('Cat') {
-
1
name
-
}
-
}
-
1
image {
-
1
url
-
}
-
}
-
}
-
}
-
-
1
expect(subject.valid?(query)).to be_truthy
-
-
1
validation = subject.validate(query)
-
1
expect(validation.valid?).to be_truthy
-
1
expect(validation.errors).to be_empty
-
end
-
-
1
it 'wrong node returns false' do
-
1
query = dsl.query {
-
1
foo
-
}
-
-
1
expect(subject.valid?(query)).to be_falsey
-
-
1
validation = subject.validate(query)
-
1
expect(validation.valid?).to be_falsey
-
1
expect(validation.errors).not_to be_empty
-
1
expect(validation.errors.map(&:to_s)).to include("Node type not found for 'foo'")
-
end
-
-
1
it 'object node that doesnt have proper values returns false' do
-
1
query = dsl.query {
-
1
catCollection
-
}
-
-
1
expect(subject.valid?(query)).to be_falsey
-
-
1
validation = subject.validate(query)
-
1
expect(validation.valid?).to be_falsey
-
1
expect(validation.errors).not_to be_empty
-
1
expect(validation.errors.map(&:to_s)).to include("Invalid object for node 'catCollection'")
-
end
-
-
1
it 'object list node that doesnt have proper values returns false' do
-
1
query = dsl.query {
-
1
catCollection {
-
1
items
-
}
-
}
-
-
1
expect(subject.valid?(query)).to be_falsey
-
-
1
validation = subject.validate(query)
-
1
expect(validation.valid?).to be_falsey
-
1
expect(validation.errors).not_to be_empty
-
1
expect(validation.errors.map(&:to_s)).to include("Invalid object for node 'items'")
-
end
-
-
1
it 'type matching on invalid type returns false' do
-
1
query = dsl.query {
-
1
catCollection {
-
1
items {
-
1
bestFriend {
-
1
__on('InvalidType') {
-
1
foo
-
}
-
}
-
}
-
}
-
}
-
-
1
expect(subject.valid?(query)).to be_falsey
-
-
1
validation = subject.validate(query)
-
1
expect(validation.valid?).to be_falsey
-
1
expect(validation.errors).not_to be_empty
-
1
expect(validation.errors.map(&:to_s)).to include("Match type 'InvalidType' invalid")
-
end
-
-
1
it 'invalid arguments return false' do
-
1
query = dsl.query {
-
1
catCollection(invalidParam: 1) {
-
1
items {
-
1
name
-
}
-
}
-
}
-
-
1
expect(subject.valid?(query)).to be_falsey
-
-
1
validation = subject.validate(query)
-
1
expect(validation.valid?).to be_falsey
-
1
expect(validation.errors).not_to be_empty
-
1
expect(validation.errors.map(&:to_s)).to include("Invalid argument 'invalidParam'")
-
end
-
-
1
it 'invalid argument type returns false' do
-
1
query = dsl.query {
-
1
catCollection(limit: 'foo') {
-
1
items {
-
1
name
-
}
-
}
-
}
-
-
1
expect(subject.valid?(query)).to be_falsey
-
-
1
validation = subject.validate(query)
-
1
expect(validation.valid?).to be_falsey
-
1
expect(validation.errors).not_to be_empty
-
1
expect(validation.errors.map(&:to_s)).to include("Value is 'String or ID', but should be 'Int' for 'limit'")
-
end
-
-
1
describe 'enum values' do
-
1
it 'can create a query with an enum as a filter and validations should not fail' do
-
1
query = dsl.query {
-
1
catCollection(order: __enum('lives_ASC')) {
-
1
items {
-
1
name
-
}
-
}
-
}
-
-
1
expect(subject.valid?(query)).to be_truthy
-
-
1
validation = subject.validate(query)
-
1
expect(validation.valid?).to be_truthy
-
1
expect(validation.errors).to be_empty
-
end
-
-
1
it 'fails when enum value is not provided properly' do
-
1
query = dsl.query {
-
1
catCollection(order: 'lives_ASC') {
-
1
items {
-
1
name
-
}
-
}
-
}
-
-
1
expect(subject.valid?(query)).to be_falsey
-
-
1
validation = subject.validate(query)
-
1
expect(validation.valid?).to be_falsey
-
1
expect(validation.errors).not_to be_empty
-
1
expect(validation.errors.map(&:to_s)).to include("Value is 'String or ID', but should be 'Enum' for 'order'. Wrap the value with `__enum`.")
-
end
-
-
1
it 'fails when enum value is not provided properly and is not a string' do
-
1
query = dsl.query {
-
1
catCollection(order: 1) {
-
1
items {
-
1
name
-
}
-
}
-
}
-
-
1
expect(subject.valid?(query)).to be_falsey
-
-
1
validation = subject.validate(query)
-
1
expect(validation.valid?).to be_falsey
-
1
expect(validation.errors).not_to be_empty
-
1
expect(validation.errors.map(&:to_s)).to include("Value is 'Integer', but should be 'Enum' for 'order'. Wrap the value with `__enum`.")
-
end
-
end
-
-
1
describe 'aliases' do
-
1
it 'can create a query with an alias' do
-
1
query = dsl.query {
-
__node('pinned: catCollection', where: {
-
sys: { id_in: 'nyancat' }
-
1
}) {
-
1
items {
-
1
name
-
}
-
}
-
__node('unpinned: catCollection', where: {
-
sys: { id_not_in: 'nyancat' }
-
1
}, limit: 4) {
-
1
items {
-
1
name
-
}
-
}
-
}
-
-
1
validation = subject.validate(query)
-
1
expect(validation.valid?).to be_truthy
-
1
expect(validation.errors).to be_empty
-
end
-
end
-
-
1
describe 'directives' do
-
1
it 'can create a query with a directive and validations should not fail' do
-
1
query = dsl.query {
-
1
catCollection {
-
1
items {
-
1
name(:@include => { if: true })
-
}
-
}
-
}
-
-
1
validation = subject.validate(query)
-
1
expect(validation.valid?).to be_truthy
-
end
-
-
1
it 'unknown directives will fail' do
-
1
query = dsl.query {
-
1
catCollection(:@unknownDirective => nil)
-
}
-
-
1
validation = subject.validate(query)
-
1
expect(validation.valid?).to be_falsey
-
1
expect(validation.errors).not_to be_empty
-
1
expect(validation.errors.map(&:to_s)).to include("Directive unknown '@unknownDirective'")
-
end
-
-
1
it 'known directive will fail if no arguments are passed' do
-
1
query = dsl.query {
-
1
catCollection(:@include => nil)
-
}
-
-
1
validation = subject.validate(query)
-
1
expect(validation.valid?).to be_falsey
-
1
expect(validation.errors).not_to be_empty
-
1
expect(validation.errors.map(&:to_s)).to include("Missing arguments for directive '@include'")
-
end
-
-
1
it 'known directive will fail if arguments are empty' do
-
1
query = dsl.query {
-
1
catCollection(:@include => {})
-
}
-
-
1
validation = subject.validate(query)
-
1
expect(validation.valid?).to be_falsey
-
1
expect(validation.errors).not_to be_empty
-
1
expect(validation.errors.map(&:to_s)).to include("Missing arguments for directive '@include'")
-
end
-
-
1
it 'known directive will fail if arguments is not if' do
-
1
query = dsl.query {
-
1
catCollection {
-
1
items {
-
1
name(:@include => { else: true })
-
}
-
}
-
}
-
-
1
validation = subject.validate(query)
-
1
expect(validation.valid?).to be_falsey
-
1
expect(validation.errors).not_to be_empty
-
1
expect(validation.errors.map(&:to_s)).to include("Invalid argument 'else' for directive '@include'")
-
end
-
-
1
it 'known directive will fail when if value is not boolean' do
-
1
query = dsl.query {
-
1
catCollection {
-
1
items {
-
1
name(:@include => { if: 123 })
-
}
-
}
-
}
-
-
1
validation = subject.validate(query)
-
1
expect(validation.valid?).to be_falsey
-
1
expect(validation.errors).not_to be_empty
-
1
expect(validation.errors.map(&:to_s)).to include("Invalid value for 'if`, must be a boolean")
-
end
-
-
1
it 'known directive will fail when multiple arguments are passed' do
-
1
query = dsl.query {
-
1
catCollection {
-
1
items {
-
1
name(:@include => { if: true, else: false })
-
}
-
}
-
}
-
-
1
validation = subject.validate(query)
-
1
expect(validation.valid?).to be_falsey
-
1
expect(validation.errors).not_to be_empty
-
1
expect(validation.errors.map(&:to_s)).to include("Invalid argument 'else' for directive '@include'")
-
end
-
end
-
end
-
end