graphql 0.1.0 → 0.2.0

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