graphql 0.1.0 → 0.2.0

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 (108) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graph_ql/directive.rb +9 -5
  3. data/lib/graph_ql/directives/include_directive.rb +2 -2
  4. data/lib/graph_ql/directives/skip_directive.rb +2 -2
  5. data/lib/graph_ql/enum.rb +5 -8
  6. data/lib/graph_ql/field.rb +50 -0
  7. data/lib/graph_ql/interface.rb +4 -0
  8. data/lib/graph_ql/introspection/arguments_field.rb +2 -2
  9. data/lib/graph_ql/introspection/directive_type.rb +10 -10
  10. data/lib/graph_ql/introspection/enum_value_type.rb +11 -8
  11. data/lib/graph_ql/introspection/enum_values_field.rb +5 -5
  12. data/lib/graph_ql/introspection/field_type.rb +14 -10
  13. data/lib/graph_ql/introspection/fields_field.rb +4 -4
  14. data/lib/graph_ql/introspection/input_fields_field.rb +3 -3
  15. data/lib/graph_ql/introspection/input_value_type.rb +8 -8
  16. data/lib/graph_ql/introspection/interfaces_field.rb +5 -0
  17. data/lib/graph_ql/introspection/of_type_field.rb +3 -9
  18. data/lib/graph_ql/introspection/possible_types_field.rb +3 -10
  19. data/lib/graph_ql/introspection/schema_type.rb +8 -14
  20. data/lib/graph_ql/introspection/type_kind_enum.rb +1 -1
  21. data/lib/graph_ql/introspection/type_type.rb +17 -19
  22. data/lib/graph_ql/introspection/typename_field.rb +15 -0
  23. data/lib/graph_ql/parser.rb +9 -0
  24. data/lib/graph_ql/parser/nodes.rb +17 -7
  25. data/lib/graph_ql/parser/parser.rb +7 -7
  26. data/lib/graph_ql/parser/transform.rb +23 -20
  27. data/lib/graph_ql/parser/visitor.rb +29 -13
  28. data/lib/graph_ql/query.rb +39 -16
  29. data/lib/graph_ql/query/field_resolution_strategy.rb +15 -11
  30. data/lib/graph_ql/query/type_resolver.rb +4 -2
  31. data/lib/graph_ql/repl.rb +1 -1
  32. data/lib/graph_ql/schema.rb +19 -7
  33. data/lib/graph_ql/schema/each_item_validator.rb +12 -0
  34. data/lib/graph_ql/schema/field_validator.rb +13 -0
  35. data/lib/graph_ql/schema/implementation_validator.rb +21 -0
  36. data/lib/graph_ql/schema/schema_validator.rb +10 -0
  37. data/lib/graph_ql/schema/type_reducer.rb +6 -6
  38. data/lib/graph_ql/schema/type_validator.rb +47 -0
  39. data/lib/graph_ql/static_validation.rb +18 -0
  40. data/lib/graph_ql/static_validation/argument_literals_are_compatible.rb +13 -0
  41. data/lib/graph_ql/static_validation/arguments_are_defined.rb +10 -0
  42. data/lib/graph_ql/static_validation/arguments_validator.rb +16 -0
  43. data/lib/graph_ql/static_validation/directives_are_defined.rb +18 -0
  44. data/lib/graph_ql/static_validation/fields_are_defined_on_type.rb +29 -0
  45. data/lib/graph_ql/static_validation/fields_have_appropriate_selections.rb +31 -0
  46. data/lib/graph_ql/static_validation/fields_will_merge.rb +93 -0
  47. data/lib/graph_ql/static_validation/fragment_types_exist.rb +24 -0
  48. data/lib/graph_ql/static_validation/fragments_are_used.rb +30 -0
  49. data/lib/graph_ql/static_validation/literal_validator.rb +27 -0
  50. data/lib/graph_ql/static_validation/message.rb +29 -0
  51. data/lib/graph_ql/static_validation/required_arguments_are_present.rb +13 -0
  52. data/lib/graph_ql/static_validation/type_stack.rb +87 -0
  53. data/lib/graph_ql/static_validation/validator.rb +48 -0
  54. data/lib/graph_ql/type_kinds.rb +50 -12
  55. data/lib/graph_ql/types/argument_definer.rb +7 -0
  56. data/lib/graph_ql/types/boolean_type.rb +3 -3
  57. data/lib/graph_ql/types/field_definer.rb +19 -0
  58. data/lib/graph_ql/types/float_type.rb +3 -3
  59. data/lib/graph_ql/types/input_object_type.rb +4 -0
  60. data/lib/graph_ql/types/input_value.rb +1 -1
  61. data/lib/graph_ql/types/int_type.rb +4 -4
  62. data/lib/graph_ql/types/list_type.rb +5 -1
  63. data/lib/graph_ql/types/non_null_type.rb +4 -0
  64. data/lib/graph_ql/types/object_type.rb +9 -20
  65. data/lib/graph_ql/types/scalar_type.rb +4 -0
  66. data/lib/graph_ql/types/string_type.rb +3 -3
  67. data/lib/graph_ql/types/type_definer.rb +5 -9
  68. data/lib/graph_ql/union.rb +6 -17
  69. data/lib/graph_ql/version.rb +1 -1
  70. data/lib/graphql.rb +58 -78
  71. data/readme.md +80 -7
  72. data/spec/graph_ql/interface_spec.rb +15 -1
  73. data/spec/graph_ql/introspection/directive_type_spec.rb +2 -2
  74. data/spec/graph_ql/introspection/schema_type_spec.rb +2 -1
  75. data/spec/graph_ql/introspection/type_type_spec.rb +16 -1
  76. data/spec/graph_ql/parser/parser_spec.rb +3 -1
  77. data/spec/graph_ql/parser/transform_spec.rb +12 -2
  78. data/spec/graph_ql/parser/visitor_spec.rb +13 -5
  79. data/spec/graph_ql/query_spec.rb +25 -13
  80. data/spec/graph_ql/schema/field_validator_spec.rb +21 -0
  81. data/spec/graph_ql/schema/type_reducer_spec.rb +2 -2
  82. data/spec/graph_ql/schema/type_validator_spec.rb +54 -0
  83. data/spec/graph_ql/static_validation/argument_literals_are_compatible_spec.rb +41 -0
  84. data/spec/graph_ql/static_validation/arguments_are_defined_spec.rb +40 -0
  85. data/spec/graph_ql/static_validation/directives_are_defined_spec.rb +33 -0
  86. data/spec/graph_ql/static_validation/fields_are_defined_on_type_spec.rb +59 -0
  87. data/spec/graph_ql/static_validation/fields_have_appropriate_selections_spec.rb +30 -0
  88. data/spec/graph_ql/{validations → static_validation}/fields_will_merge_spec.rb +24 -17
  89. data/spec/graph_ql/static_validation/fragment_types_exist_spec.rb +38 -0
  90. data/spec/graph_ql/static_validation/fragments_are_used_spec.rb +24 -0
  91. data/spec/graph_ql/static_validation/required_arguments_are_present_spec.rb +41 -0
  92. data/spec/graph_ql/static_validation/type_stack_spec.rb +35 -0
  93. data/spec/graph_ql/static_validation/validator_spec.rb +28 -0
  94. data/spec/graph_ql/types/object_type_spec.rb +1 -1
  95. data/spec/graph_ql/union_spec.rb +1 -14
  96. data/spec/support/dummy_app.rb +75 -53
  97. metadata +53 -31
  98. data/lib/graph_ql/fields/abstract_field.rb +0 -37
  99. data/lib/graph_ql/fields/access_field.rb +0 -24
  100. data/lib/graph_ql/fields/field.rb +0 -34
  101. data/lib/graph_ql/types/abstract_type.rb +0 -14
  102. data/lib/graph_ql/validations/fields_are_defined_on_type.rb +0 -44
  103. data/lib/graph_ql/validations/fields_will_merge.rb +0 -80
  104. data/lib/graph_ql/validations/fragments_are_used.rb +0 -24
  105. data/lib/graph_ql/validator.rb +0 -29
  106. data/spec/graph_ql/validations/fields_are_defined_on_type_spec.rb +0 -28
  107. data/spec/graph_ql/validations/fragments_are_used_spec.rb +0 -28
  108. data/spec/graph_ql/validator_spec.rb +0 -24
@@ -1,37 +0,0 @@
1
- # Anything can be a Field as long as it responds to:
2
- # - #name: String the name to access this field in a query
3
- # - #type: Type returned by this field's resolve function
4
- # - #description: String
5
- # - #resolve(object, arguments, context): Object of Type `type`
6
- # - #arguments: ???
7
- # - #deprecation_reason
8
- class GraphQL::AbstractField
9
- def name
10
- raise NotImplementedError, "#{self.class.name}#name should return the name for accessing this field"
11
- end
12
-
13
- def type
14
- raise NotImplementedError, "#{self.class.name}#type should return the type class which this field returns"
15
- end
16
-
17
- def description
18
- raise NotImplementedError, "#{self.class.name}#description should return this field's description"
19
- end
20
-
21
-
22
- def resolve(object, arguments, context)
23
- raise NotImplementedError, "#{self.class.name}#resolve(object, arguments, context) should execute this field for object"
24
- end
25
-
26
- def arguments
27
- {}
28
- end
29
-
30
- def deprecated?
31
- !!deprecation_reason
32
- end
33
-
34
- def deprecation_reason
35
- nil
36
- end
37
- end
@@ -1,24 +0,0 @@
1
- # Implement {AbstractField} by calling the field name on its object
2
- # and returning the result.
3
- class GraphQL::AccessField < GraphQL::AbstractField
4
- attr_accessor :name, :type
5
- attr_reader :description, :arguments, :deprecation_reason
6
- def initialize(type:, arguments:, description:, property: nil, deprecation_reason: nil)
7
- @type = type
8
- @arguments = arguments
9
- @description = description
10
- @property = property
11
- @deprecation_reason = deprecation_reason
12
- end
13
-
14
- def resolve(object, args, context)
15
- @property.nil? ? GraphQL::Query::DEFAULT_RESOLVE : object.send(@property)
16
- end
17
-
18
- def type
19
- if @type.is_a?(Proc)
20
- @type = @type.call
21
- end
22
- @type
23
- end
24
- end
@@ -1,34 +0,0 @@
1
- class GraphQL::Field < GraphQL::AbstractField
2
- extend GraphQL::Definable
3
- REQUIRED_DEFINITIONS = [:name, :description, :type]
4
- attr_definable(:arguments, :deprecation_reason, *REQUIRED_DEFINITIONS)
5
-
6
- def initialize(&block)
7
- @arguments = {}
8
- @resolve_proc = -> (o, a, c) { GraphQL::Query::DEFAULT_RESOLVE }
9
- yield(self) if block_given?
10
- end
11
-
12
- # Used when defining:
13
- # resolve -> (obj, args, ctx) { obj.get_value }
14
- # Also used when executing queries:
15
- # field.resolve(obj, args, ctx)
16
- def resolve(proc_or_object, arguments=nil, ctx=nil)
17
- if arguments.nil? && ctx.nil?
18
- @resolve_proc = proc_or_object
19
- else
20
- @resolve_proc.call(proc_or_object, arguments, ctx)
21
- end
22
- end
23
-
24
- def type(type_or_proc=nil)
25
- if type_or_proc.nil?
26
- if @type.is_a?(Proc)
27
- @type = @type.call
28
- end
29
- @type
30
- else
31
- @type = type_or_proc
32
- end
33
- end
34
- end
@@ -1,14 +0,0 @@
1
- # Any object can be a type as long as it implements:
2
- # - #fields: Hash of { String => Field } pairs
3
- # - #kind: one of GraphQL::TypeKinds
4
- # - #interfaces: Array of Interfaces
5
- # - #name: String
6
- # - #description: String
7
- #
8
- class GraphQL::AbstractType
9
- def fields; raise NotImplementedError; end
10
- def kind; raise NotImplementedError; end
11
- def interfaces; raise NotImplementedError; end
12
- def name; raise NotImplementedError; end
13
- def description; raise NotImplementedError; end
14
- end
@@ -1,44 +0,0 @@
1
- class GraphQL::Validations::FieldsAreDefinedOnType
2
- TYPE_INFERRENCE_ROOTS = [GraphQL::Nodes::OperationDefinition, GraphQL::Nodes::FragmentDefinition]
3
- FIELD_MODIFIERS = [GraphQL::TypeKinds::LIST]
4
-
5
- def validate(context)
6
- visitor = context.visitor
7
- TYPE_INFERRENCE_ROOTS.each do |node_class|
8
- visitor[node_class] << -> (node){ validate_document_part(node, context) }
9
- end
10
- end
11
-
12
- private
13
- def validate_document_part(part, context)
14
- if part.is_a?(GraphQL::Nodes::FragmentDefinition)
15
- type = context.schema.types[part.type]
16
- validate_selections(type, part.selections, context)
17
- elsif part.is_a?(GraphQL::Nodes::OperationDefinition)
18
- type = context.schema.public_send(part.operation_type) # mutation root or query root
19
- validate_selections(type, part.selections, context)
20
- end
21
- end
22
-
23
- def validate_selections(type, selections, context)
24
- selections
25
- .select {|f| f.is_a?(GraphQL::Nodes::Field) } # don't worry about fragments
26
- .each do |ast_field|
27
- field = type.fields[ast_field.name]
28
- if field.nil?
29
- context.errors << "Field '#{ast_field.name}' doesn't exist on type '#{type.name}'"
30
- else
31
- field_type = get_field_type(field)
32
- validate_selections(field_type, ast_field.selections, context)
33
- end
34
- end
35
- end
36
-
37
- def get_field_type(field)
38
- if FIELD_MODIFIERS.include?(field.type.kind)
39
- field.type.of_type
40
- else
41
- field.type
42
- end
43
- end
44
- end
@@ -1,80 +0,0 @@
1
- class GraphQL::Validations::FieldsWillMerge
2
- HAS_SELECTIONS = [GraphQL::Nodes::OperationDefinition, GraphQL::Nodes::InlineFragment]
3
-
4
- def validate(context)
5
- fragments = {}
6
- has_selections = []
7
- visitor = context.visitor
8
- HAS_SELECTIONS.each do |node_class|
9
- visitor[node_class] << -> (node) { has_selections << node }
10
- end
11
- visitor[GraphQL::Nodes::FragmentDefinition] << -> (node) { fragments[node.name] = node }
12
- visitor[GraphQL::Nodes::Document].leave << -> (node) {
13
- has_selections.each { |node| validate_selections(node.selections, {}, fragments, context.errors)}
14
- }
15
- end
16
-
17
- private
18
-
19
- def validate_selections(selections, name_to_field, fragments, errors)
20
- selections.each do |field|
21
- if field.is_a?(GraphQL::Nodes::InlineFragment)
22
- validate_selections(field.selections, name_to_field, fragments, errors)
23
- elsif field.is_a?(GraphQL::Nodes::FragmentSpread)
24
- fragment = fragments[field.name]
25
- validate_selections(fragment.selections, name_to_field, fragments, errors)
26
- else
27
- field_errors = validate_field(field, name_to_field)
28
- errors.push(*field_errors)
29
- validate_selections(field.selections, name_to_field, fragments, errors)
30
- end
31
- end
32
- end
33
-
34
- def validate_field(field, name_to_field)
35
- name_in_selection = field.alias || field.name
36
- name_to_field[name_in_selection] ||= field
37
- first_field_def = name_to_field[name_in_selection]
38
- comparison = FieldDefinitionComparison.new(name_in_selection, first_field_def, field)
39
- comparison.errors
40
- end
41
-
42
- # Compare two field definitions, add errors to the list if there are any
43
- class FieldDefinitionComparison
44
- NAMED_VALUES = [GraphQL::Nodes::Enum, GraphQL::Nodes::VariableIdentifier]
45
- attr_reader :errors
46
- def initialize(name, prev_def, next_def)
47
- errors = []
48
- if prev_def.name != next_def.name
49
- errors << "Field '#{name}' has a field conflict: #{prev_def.name} or #{next_def.name}?"
50
- end
51
- prev_arguments = reduce_list(prev_def.arguments)
52
- next_arguments = reduce_list(next_def.arguments)
53
- if prev_arguments != next_arguments
54
- errors << "Field '#{name}' has an argument conflict: #{JSON.dump(prev_arguments)} or #{JSON.dump(next_arguments)}?"
55
- end
56
- prev_directive_names = prev_def.directives.map(&:name)
57
- next_directive_names = next_def.directives.map(&:name)
58
- if prev_directive_names != next_directive_names
59
- errors << "Field '#{name}' has a directive conflict: [#{prev_directive_names.join(", ")}] or [#{next_directive_names.join(", ")}]?"
60
- end
61
- prev_directive_args = prev_def.directives.map {|d| reduce_list(d.arguments) }
62
- next_directive_args = next_def.directives.map {|d| reduce_list(d.arguments) }
63
- if prev_directive_args != next_directive_args
64
- errors << "Field '#{name}' has a directive argument conflict: #{JSON.dump(prev_directive_args)} or #{JSON.dump(next_directive_args)}?"
65
- end
66
- @errors = errors
67
- end
68
-
69
- private
70
-
71
- # Turn AST tree into a hash
72
- # can't look up args, the names just have to match
73
- def reduce_list(args)
74
- args.reduce({}) do |memo, a|
75
- memo[a.name] = NAMED_VALUES.include?(a.value.class) ? a.value.name : a.value
76
- memo
77
- end
78
- end
79
- end
80
- end
@@ -1,24 +0,0 @@
1
- class GraphQL::Validations::FragmentsAreUsed
2
- def validate(context)
3
- v = context.visitor
4
- used_fragment_names = []
5
- defined_fragment_names = []
6
- v[GraphQL::Nodes::FragmentSpread] << -> (node) { used_fragment_names << node.name }
7
- v[GraphQL::Nodes::FragmentDefinition] << -> (node) { defined_fragment_names << node.name}
8
- v[GraphQL::Nodes::Document].leave << -> (node) { add_errors(context.errors, used_fragment_names, defined_fragment_names) }
9
- end
10
-
11
- private
12
-
13
- def add_errors(errors, used_fragment_names, defined_fragment_names)
14
- undefined_fragment_names = used_fragment_names - defined_fragment_names
15
- if undefined_fragment_names.any?
16
- errors << "Some fragments were used but not defined: #{undefined_fragment_names.join(", ")}"
17
- end
18
-
19
- unused_fragment_names = defined_fragment_names - used_fragment_names
20
- if unused_fragment_names.any?
21
- errors << "Some fragments were defined but not used: #{unused_fragment_names.join(", ")}"
22
- end
23
- end
24
- end
@@ -1,29 +0,0 @@
1
- class GraphQL::Validator
2
- VALIDATORS = [
3
- GraphQL::Validations::FragmentsAreUsed,
4
- ]
5
-
6
- def initialize(schema:, validators: VALIDATORS)
7
- @schema = schema
8
- @validators = validators
9
- end
10
-
11
- def validate(document)
12
- context = Context.new(@schema, document)
13
- @validators.each do |validator|
14
- validator.new.validate(context)
15
- end
16
- context.visitor.visit(document)
17
- context.errors
18
- end
19
-
20
- class Context
21
- attr_reader :schema, :document, :errors, :visitor
22
- def initialize(schema, document)
23
- @schema = schema
24
- @document = document
25
- @visitor = GraphQL::Visitor.new
26
- @errors = []
27
- end
28
- end
29
- end
@@ -1,28 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe GraphQL::Validations::FieldsAreDefinedOnType do
4
- let(:document) { GraphQL.parse("
5
- query getCheese($sourceVar: DairyAnimal!) {
6
- notDefinedField { name }
7
- cheese(id: 1) { nonsenseField, flavor }
8
- fromSource(source: COW) { bogusField }
9
- }
10
-
11
- fragment cheeseFields on Cheese { fatContent, hogwashField }
12
- ")}
13
-
14
- let(:validator) { GraphQL::Validator.new(schema: DummySchema, validators: [GraphQL::Validations::FieldsAreDefinedOnType]) }
15
- let(:errors) { validator.validate(document) }
16
- it "finds fields that are requested on types that don't have that field" do
17
- expected_errors = [
18
- "Field 'notDefinedField' doesn't exist on type 'Query'", # from query root
19
- "Field 'nonsenseField' doesn't exist on type 'Cheese'", # from another field
20
- "Field 'bogusField' doesn't exist on type 'Cheese'", # from a list
21
- "Field 'hogwashField' doesn't exist on type 'Cheese'", # from a fragment
22
- ]
23
- assert_equal(expected_errors, errors)
24
- end
25
-
26
- it 'finds invalid fields on interfaces'
27
- it 'finds invalid fields on unions'
28
- end
@@ -1,28 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe GraphQL::Validations::FragmentsAreUsed do
4
- let(:document) { GraphQL.parse("
5
- query getCheese {
6
- name,
7
- ...cheeseFields,
8
- origin {
9
- ...originFields
10
- ...undefinedFields
11
- }
12
- }
13
- fragment cheeseFields on Cheese { fatContent }
14
- fragment originFields on Country { name, continent { ...continentFields }}
15
- fragment continentFields on Continent { name }
16
- fragment unusedFields on Cheese { is, not, used }
17
- ")}
18
-
19
- let(:validator) { GraphQL::Validator.new(schema: nil, validators: [GraphQL::Validations::FragmentsAreUsed]) }
20
- let(:errors) { validator.validate(document) }
21
-
22
- it 'adds errors for unused fragment definitions' do
23
- assert_includes(errors, 'Some fragments were defined but not used: unusedFields')
24
- end
25
- it 'adds errors for undefined fragment spreads' do
26
- assert_includes(errors, 'Some fragments were used but not defined: undefinedFields')
27
- end
28
- end
@@ -1,24 +0,0 @@
1
- require 'spec_helper'
2
-
3
- class SchemaErrorValidator
4
- def validate(context)
5
- context.errors << "Something is wrong: #{context.schema}"
6
- end
7
- end
8
-
9
- class DocumentErrorValidator
10
- def validate(context)
11
- context.errors << "Something is wrong: #{context.document.name}"
12
- end
13
- end
14
-
15
- describe GraphQL::Validator do
16
- let(:document) { OpenStruct.new(name: "This is not a document", children: []) }
17
- let(:validator) { GraphQL::Validator.new(schema: "This is not a schema", validators: [SchemaErrorValidator, DocumentErrorValidator]) }
18
-
19
- it 'uses validators' do
20
- errors = validator.validate(document)
21
- expected_errors = ["Something is wrong: This is not a schema", "Something is wrong: This is not a document"]
22
- assert_equal(expected_errors, errors)
23
- end
24
- end