graphql 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/analyze_query.rb +4 -2
  3. data/lib/graphql/analysis/field_usage.rb +4 -4
  4. data/lib/graphql/analysis/query_complexity.rb +16 -21
  5. data/lib/graphql/argument.rb +13 -6
  6. data/lib/graphql/base_type.rb +2 -1
  7. data/lib/graphql/compatibility/execution_specification.rb +76 -0
  8. data/lib/graphql/compatibility/query_parser_specification.rb +16 -2
  9. data/lib/graphql/compatibility/query_parser_specification/parse_error_specification.rb +0 -5
  10. data/lib/graphql/compatibility/schema_parser_specification.rb +6 -0
  11. data/lib/graphql/define/assign_argument.rb +8 -2
  12. data/lib/graphql/define/instance_definable.rb +12 -15
  13. data/lib/graphql/directive.rb +2 -1
  14. data/lib/graphql/enum_type.rb +5 -7
  15. data/lib/graphql/field.rb +6 -11
  16. data/lib/graphql/field/resolve.rb +1 -0
  17. data/lib/graphql/input_object_type.rb +9 -9
  18. data/lib/graphql/interface_type.rb +2 -1
  19. data/lib/graphql/internal_representation.rb +1 -0
  20. data/lib/graphql/internal_representation/node.rb +31 -9
  21. data/lib/graphql/internal_representation/rewrite.rb +26 -26
  22. data/lib/graphql/internal_representation/selections.rb +41 -0
  23. data/lib/graphql/introspection/input_value_type.rb +6 -2
  24. data/lib/graphql/language/generation.rb +2 -0
  25. data/lib/graphql/language/lexer.rl +4 -0
  26. data/lib/graphql/language/nodes.rb +3 -0
  27. data/lib/graphql/language/parser.rb +525 -509
  28. data/lib/graphql/language/parser.y +2 -0
  29. data/lib/graphql/object_type.rb +2 -2
  30. data/lib/graphql/query.rb +21 -0
  31. data/lib/graphql/query/context.rb +52 -4
  32. data/lib/graphql/query/serial_execution.rb +3 -4
  33. data/lib/graphql/query/serial_execution/field_resolution.rb +35 -36
  34. data/lib/graphql/query/serial_execution/operation_resolution.rb +9 -15
  35. data/lib/graphql/query/serial_execution/selection_resolution.rb +14 -11
  36. data/lib/graphql/query/serial_execution/value_resolution.rb +18 -17
  37. data/lib/graphql/query/variables.rb +1 -1
  38. data/lib/graphql/relay/mutation.rb +5 -8
  39. data/lib/graphql/scalar_type.rb +1 -2
  40. data/lib/graphql/schema.rb +2 -13
  41. data/lib/graphql/schema/build_from_definition.rb +28 -13
  42. data/lib/graphql/schema/loader.rb +4 -1
  43. data/lib/graphql/schema/printer.rb +10 -3
  44. data/lib/graphql/schema/timeout_middleware.rb +18 -2
  45. data/lib/graphql/schema/unique_within_type.rb +6 -3
  46. data/lib/graphql/static_validation/literal_validator.rb +3 -1
  47. data/lib/graphql/union_type.rb +1 -2
  48. data/lib/graphql/version.rb +1 -1
  49. data/readme.md +1 -0
  50. data/spec/graphql/analysis/analyze_query_spec.rb +6 -8
  51. data/spec/graphql/argument_spec.rb +18 -0
  52. data/spec/graphql/define/assign_argument_spec.rb +48 -0
  53. data/spec/graphql/define/instance_definable_spec.rb +4 -2
  54. data/spec/graphql/execution_error_spec.rb +66 -0
  55. data/spec/graphql/input_object_type_spec.rb +81 -0
  56. data/spec/graphql/internal_representation/rewrite_spec.rb +104 -21
  57. data/spec/graphql/introspection/input_value_type_spec.rb +43 -6
  58. data/spec/graphql/introspection/schema_type_spec.rb +1 -0
  59. data/spec/graphql/introspection/type_type_spec.rb +2 -0
  60. data/spec/graphql/language/generation_spec.rb +3 -2
  61. data/spec/graphql/query/arguments_spec.rb +17 -4
  62. data/spec/graphql/query/context_spec.rb +23 -0
  63. data/spec/graphql/query/variables_spec.rb +15 -1
  64. data/spec/graphql/relay/mutation_spec.rb +42 -2
  65. data/spec/graphql/schema/build_from_definition_spec.rb +4 -2
  66. data/spec/graphql/schema/loader_spec.rb +59 -1
  67. data/spec/graphql/schema/printer_spec.rb +2 -0
  68. data/spec/graphql/schema/reduce_types_spec.rb +1 -1
  69. data/spec/graphql/schema/timeout_middleware_spec.rb +2 -2
  70. data/spec/graphql/schema/unique_within_type_spec.rb +9 -0
  71. data/spec/graphql/schema/validation_spec.rb +15 -3
  72. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +122 -0
  73. data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +78 -0
  74. data/spec/support/dairy_app.rb +9 -0
  75. data/spec/support/minimum_input_object.rb +4 -0
  76. data/spec/support/star_wars_schema.rb +1 -1
  77. metadata +5 -5
  78. data/lib/graphql/query/serial_execution/execution_context.rb +0 -37
  79. data/spec/graphql/query/serial_execution/execution_context_spec.rb +0 -54
@@ -22,4 +22,22 @@ describe GraphQL::Argument do
22
22
  argument = GraphQL::Argument.define(name: :favoriteFood, type: -> { GraphQL::STRING_TYPE })
23
23
  assert_equal GraphQL::STRING_TYPE, argument.type
24
24
  end
25
+
26
+ it "accepts a default_value" do
27
+ argument = GraphQL::Argument.define(name: :favoriteFood, type: GraphQL::STRING_TYPE, default_value: 'Default')
28
+ assert_equal 'Default', argument.default_value
29
+ assert argument.default_value?
30
+ end
31
+
32
+ it "accepts a default_value of nil" do
33
+ argument = GraphQL::Argument.define(name: :favoriteFood, type: GraphQL::STRING_TYPE, default_value: nil)
34
+ assert argument.default_value.nil?
35
+ assert argument.default_value?
36
+ end
37
+
38
+ it "default_value is optional" do
39
+ argument = GraphQL::Argument.define(name: :favoriteFood, type: GraphQL::STRING_TYPE)
40
+ assert argument.default_value.nil?
41
+ assert !argument.default_value?
42
+ end
25
43
  end
@@ -0,0 +1,48 @@
1
+ require "spec_helper"
2
+
3
+ describe GraphQL::Define::AssignArgument do
4
+ it "it accepts default_value" do
5
+ arg = define_argument(:a, GraphQL::STRING_TYPE, default_value: 'Default')
6
+
7
+ assert_equal "Default", arg.default_value
8
+ assert arg.default_value?
9
+ end
10
+
11
+ it "default_value is optional" do
12
+ arg = define_argument(:a, GraphQL::STRING_TYPE)
13
+
14
+ assert arg.default_value.nil?
15
+ assert !arg.default_value?
16
+ end
17
+
18
+ it "default_value can be explicitly set to nil" do
19
+ arg = define_argument(:a, GraphQL::STRING_TYPE, default_value: nil)
20
+
21
+ assert arg.default_value.nil?
22
+ assert arg.default_value?
23
+ end
24
+
25
+ it "passing unknown keyword arguments will raise" do
26
+ err = assert_raises ArgumentError do
27
+ define_argument(:a, GraphQL::STRING_TYPE, blah: nil)
28
+ end
29
+
30
+ assert_equal 'unknown keyword: blah', err.message
31
+
32
+ err = assert_raises ArgumentError do
33
+ define_argument(:a, GraphQL::STRING_TYPE, blah: nil, blah2: nil)
34
+ end
35
+
36
+ assert_equal 'unknown keywords: blah, blah2', err.message
37
+ end
38
+
39
+ def define_argument(*args)
40
+ type = GraphQL::ObjectType.define do
41
+ field :a, types.String do
42
+ argument(*args)
43
+ end
44
+ end
45
+
46
+ type.fields['a'].arguments[args.first.to_s]
47
+ end
48
+ end
@@ -11,11 +11,13 @@ module Garden
11
11
 
12
12
  class Vegetable
13
13
  include GraphQL::Define::InstanceDefinable
14
- lazy_defined_attr_accessor :name, :start_planting_on, :end_planting_on
14
+ attr_accessor :name, :start_planting_on, :end_planting_on
15
+ ensure_defined(:name, :start_planting_on, :end_planting_on)
15
16
  accepts_definitions :name, plant_between: DefinePlantBetween, color: GraphQL::Define.assign_metadata_key(:color)
16
17
 
17
18
  # definition added later:
18
- lazy_defined_attr_accessor :height
19
+ attr_accessor :height
20
+ ensure_defined(:height)
19
21
  end
20
22
  end
21
23
 
@@ -44,6 +44,7 @@ describe GraphQL::ExecutionError do
44
44
  }
45
45
  }
46
46
  executionError
47
+ valueWithExecutionError
47
48
  }
48
49
 
49
50
  fragment similarCheeseFields on Cheese {
@@ -90,6 +91,7 @@ describe GraphQL::ExecutionError do
90
91
  ]
91
92
  },
92
93
  "executionError" => nil,
94
+ "valueWithExecutionError" => 0
93
95
  },
94
96
  "errors"=>[
95
97
  {
@@ -127,6 +129,11 @@ describe GraphQL::ExecutionError do
127
129
  "locations"=>[{"line"=>41, "column"=>7}],
128
130
  "path"=>["executionError"]
129
131
  },
132
+ {
133
+ "message"=>"Could not fetch latest value",
134
+ "locations"=>[{"line"=>42, "column"=>7}],
135
+ "path"=>["valueWithExecutionError"]
136
+ },
130
137
  ]
131
138
  }
132
139
  assert_equal(expected_result, result)
@@ -185,4 +192,63 @@ describe GraphQL::ExecutionError do
185
192
  assert_equal(expected_result, result)
186
193
  end
187
194
  end
195
+
196
+ describe "fragment query when returned from a field" do
197
+ let(:query_string) {%|
198
+ query MilkQuery {
199
+ dairy {
200
+ ...Dairy
201
+ }
202
+ }
203
+
204
+ fragment Dairy on Dairy {
205
+ milks {
206
+ source
207
+ executionError
208
+ allDairy {
209
+ __typename
210
+ ...Milk
211
+ }
212
+ }
213
+ }
214
+
215
+ fragment Milk on Milk {
216
+ origin
217
+ executionError
218
+ }
219
+ |}
220
+ it "the error is inserted into the errors key and the rest of the query is fulfilled" do
221
+ expected_result = {
222
+ "data"=>{
223
+ "dairy" => {
224
+ "milks" => [
225
+ {
226
+ "source" => "COW",
227
+ "executionError" => nil,
228
+ "allDairy" => [
229
+ { "__typename" => "Cheese" },
230
+ { "__typename" => "Cheese" },
231
+ { "__typename" => "Cheese" },
232
+ { "__typename" => "Milk", "origin" => "Antiquity", "executionError" => nil }
233
+ ]
234
+ }
235
+ ]
236
+ }
237
+ },
238
+ "errors"=>[
239
+ {
240
+ "message"=>"There was an execution error",
241
+ "locations"=>[{"line"=>11, "column"=>9}],
242
+ "path"=>["dairy", "milks", 0, "executionError"]
243
+ },
244
+ {
245
+ "message"=>"There was an execution error",
246
+ "locations"=>[{"line"=>21, "column"=>7}],
247
+ "path"=>["dairy", "milks", 0, "allDairy", 3, "executionError"]
248
+ }
249
+ ]
250
+ }
251
+ assert_equal(expected_result, result)
252
+ end
253
+ end
188
254
  end
@@ -29,6 +29,32 @@ describe GraphQL::InputObjectType do
29
29
  end
30
30
  end
31
31
 
32
+ describe "validate_input with null" do
33
+ let(:schema) { GraphQL::Schema.from_definition(%|
34
+ type Query {
35
+ a: Int
36
+ }
37
+
38
+ input ExampleInputObject {
39
+ a: String
40
+ b: Int!
41
+ }
42
+ |) }
43
+ let(:input_type) { schema.types['ExampleInputObject'] }
44
+
45
+ it "returns an invalid result when value is null for non-null argument" do
46
+ invalid_input = MinimumInputObject.new({"a" => "Test", "b" => nil})
47
+ result = input_type.validate_input(invalid_input, PermissiveWarden)
48
+ assert(!result.valid?)
49
+ end
50
+
51
+ it "returns valid result when value is null for nullable argument" do
52
+ invalid_input = MinimumInputObject.new({"a" => nil, "b" => 1})
53
+ result = input_type.validate_input(invalid_input, PermissiveWarden)
54
+ assert(result.valid?)
55
+ end
56
+ end
57
+
32
58
  describe "validate_input with enumerable input" do
33
59
  describe "with good input" do
34
60
  let(:input) do
@@ -115,6 +141,61 @@ describe GraphQL::InputObjectType do
115
141
  end
116
142
  end
117
143
 
144
+ describe "coerce_result" do
145
+ it "omits unspecified arguments" do
146
+ result = input_object.coerce_result(fatContent: 0.3)
147
+ assert_equal ["fatContent"], result.keys
148
+ assert_equal 0.3, result["fatContent"]
149
+ end
150
+ end
151
+
152
+ describe "coercion of null inputs" do
153
+ let(:schema) { GraphQL::Schema.from_definition(%|
154
+ type Query {
155
+ a: Int
156
+ }
157
+
158
+ input ExampleInputObject {
159
+ a: String
160
+ b: Int!
161
+ c: String = "Default"
162
+ }
163
+ |) }
164
+ let(:input_type) { schema.types['ExampleInputObject'] }
165
+
166
+ it "null values are returned in coerced input" do
167
+ input = MinimumInputObject.new({"a" => "Test", "b" => nil,"c" => "Test"})
168
+ result = input_type.coerce_input(input)
169
+
170
+ assert_equal 'Test', result['a']
171
+
172
+ assert result.key?('b')
173
+ assert_equal nil, result['b']
174
+
175
+ assert_equal "Test", result['c']
176
+ end
177
+
178
+ it "null values are preserved when argument has a default value" do
179
+ input = MinimumInputObject.new({"a" => "Test", "b" => 1, "c" => nil})
180
+ result = input_type.coerce_input(input)
181
+
182
+ assert_equal 'Test', result['a']
183
+ assert_equal 1, result['b']
184
+
185
+ assert result.key?('c')
186
+ assert_equal nil, result['c']
187
+ end
188
+
189
+ it "omitted arguments are not returned" do
190
+ input = MinimumInputObject.new({"b" => 1, "c" => "Test"})
191
+ result = input_type.coerce_input(input)
192
+
193
+ assert !result.key?('a')
194
+ assert_equal 1, result['b']
195
+ assert_equal 'Test', result['c']
196
+ end
197
+ end
198
+
118
199
  describe "when sent into a query" do
119
200
  let(:variables) { {} }
120
201
  let(:result) { DummySchema.execute(query_string, variables: variables) }
@@ -19,19 +19,21 @@ describe GraphQL::InternalRepresentation::Rewrite do
19
19
  }
20
20
  }
21
21
  |}
22
+
22
23
  it "produces a tree of nodes" do
23
24
  op_node = rewrite_result["getCheeses"]
24
25
 
25
- assert_equal 2, op_node.children.length
26
+ root_children = op_node.typed_children[DairyAppQueryType]
27
+ assert_equal 2, root_children.length
26
28
  assert_equal DairyAppQueryType, op_node.return_type
27
- first_field = op_node.children.values.first
28
- assert_equal 3, first_field.children.length
29
- assert_equal [DairyAppQueryType], first_field.definitions.keys
29
+ first_field = root_children.values.first
30
+ assert_equal 3, first_field.typed_children[CheeseType].length
31
+ assert_equal DairyAppQueryType, first_field.owner_type
30
32
  assert_equal CheeseType, first_field.return_type
31
33
 
32
- second_field = op_node.children.values.last
33
- assert_equal 1, second_field.children.length
34
- assert_equal [DairyAppQueryType], second_field.definitions.keys
34
+ second_field = root_children.values.last
35
+ assert_equal 1, second_field.typed_children[CheeseType].length
36
+ assert_equal DairyAppQueryType.get_field("cheese"), second_field.definition
35
37
  assert_equal CheeseType, second_field.return_type
36
38
  assert second_field.inspect.is_a?(String)
37
39
  end
@@ -47,9 +49,9 @@ describe GraphQL::InternalRepresentation::Rewrite do
47
49
  |}
48
50
 
49
51
  it "gets dynamic field definitions" do
50
- cheese_field = rewrite_result[nil].children["cheese"]
51
- typename_field = cheese_field.children["typename"]
52
- assert_equal "__typename", typename_field.definitions.values.first.name
52
+ cheese_field = rewrite_result[nil].typed_children[DairyAppQueryType]["cheese"]
53
+ typename_field = cheese_field.typed_children[CheeseType]["typename"]
54
+ assert_equal "__typename", typename_field.definition.name
53
55
  assert_equal "__typename", typename_field.definition_name
54
56
  end
55
57
  end
@@ -125,22 +127,103 @@ describe GraphQL::InternalRepresentation::Rewrite do
125
127
  it "puts all fragment members as children" do
126
128
  op_node = rewrite_result[nil]
127
129
 
128
- cheese_field = op_node.children["cheese"]
129
- assert_equal ["id1", "id2", "fatContent", "origin", "similarCow", "flavor"], cheese_field.children.keys
130
+ cheese_field = op_node.typed_children[DairyAppQueryType]["cheese"]
131
+ assert_equal ["id1", "id2", "fatContent", "similarCow", "flavor"], cheese_field.typed_children[CheeseType].keys
132
+ assert_equal ["fatContent", "origin"], cheese_field.typed_children[EdibleInterface].keys
130
133
  # Merge:
131
- similar_cow_field = cheese_field.children["similarCow"]
132
- assert_equal ["similarCowSource", "fatContent", "similarCheese", "id"], similar_cow_field.children.keys
134
+ similar_cow_field = cheese_field.typed_children[CheeseType]["similarCow"]
135
+ assert_equal ["similarCowSource", "fatContent", "similarCheese", "id"], similar_cow_field.typed_children[CheeseType].keys
133
136
  # Deep merge:
134
- similar_sheep_field = similar_cow_field.children["similarCheese"]
135
- assert_equal ["flavor", "source"], similar_sheep_field.children.keys
137
+ similar_sheep_field = similar_cow_field.typed_children[CheeseType]["similarCheese"]
138
+ assert_equal ["flavor", "source"], similar_sheep_field.typed_children[CheeseType].keys
139
+
140
+ edible_origin_node = cheese_field.typed_children[EdibleInterface]["origin"]
141
+ assert_equal EdibleInterface.get_field("origin"), edible_origin_node.definition
142
+ assert_equal EdibleInterface, edible_origin_node.owner_type
143
+
144
+ edible_fat_content_node = cheese_field.typed_children[EdibleInterface]["fatContent"]
145
+ assert_equal EdibleInterface.get_field("fatContent"), edible_fat_content_node.definition
146
+ assert_equal EdibleInterface, edible_fat_content_node.owner_type
147
+
148
+ cheese_fat_content_node = cheese_field.typed_children[CheeseType]["fatContent"]
149
+ assert_equal CheeseType.get_field("fatContent"), cheese_fat_content_node.definition
150
+ assert_equal CheeseType, cheese_fat_content_node.owner_type
151
+
152
+ cheese_flavor_node = cheese_field.typed_children[CheeseType]["flavor"]
153
+ assert_equal CheeseType.get_field("flavor"), cheese_flavor_node.definition
154
+ assert_equal CheeseType, cheese_flavor_node.owner_type
136
155
 
137
- assert_equal [EdibleInterface], cheese_field.children["origin"].definitions.keys
138
- assert_equal [CheeseType, EdibleInterface], cheese_field.children["fatContent"].definitions.keys
139
- assert_equal [CheeseType], cheese_field.children["flavor"].definitions.keys
140
156
 
141
157
  # nested spread inside fragment definition:
142
- cheese_2_field = op_node.children["cheese2"].children["similarCheese"]
143
- assert_equal ["id", "fatContent"], cheese_2_field.children.keys
158
+ cheese_2_field = op_node.typed_children[DairyAppQueryType]["cheese2"].typed_children[CheeseType]["similarCheese"]
159
+ assert_equal ["id", "fatContent"], cheese_2_field.typed_children[CheeseType].keys
160
+ end
161
+ end
162
+
163
+ describe "nested fields on typed fragments" do
164
+ let(:result) { DummySchema.execute(query_string) }
165
+ let(:query_string) {%|
166
+ {
167
+ allDairy {
168
+ __typename
169
+
170
+ ... on Milk {
171
+ selfAsEdible {
172
+ milkInlineOrigin: origin
173
+ }
174
+ }
175
+
176
+ ... on Cheese {
177
+ selfAsEdible {
178
+ cheeseInlineOrigin: origin
179
+ }
180
+ }
181
+
182
+ ... on Edible {
183
+ selfAsEdible {
184
+ edibleInlineOrigin: origin
185
+ }
186
+ }
187
+
188
+ ... {
189
+ ... on Edible {
190
+ selfAsEdible {
191
+ untypedInlineOrigin: origin
192
+ }
193
+ }
194
+ }
195
+ ...milkFields
196
+ ...cheeseFields
197
+ }
198
+ }
199
+
200
+ fragment cheeseFields on Cheese {
201
+ selfAsEdible {
202
+ cheeseFragmentOrigin: origin
203
+ }
204
+ }
205
+ fragment milkFields on Milk {
206
+ selfAsEdible {
207
+ milkFragmentOrigin: origin
208
+ }
209
+ }
210
+ |}
211
+
212
+ it "distinguishes between nested fields with the same name on different typed fragments" do
213
+ all_dairy = result["data"]["allDairy"]
214
+ cheeses = all_dairy.select { |d| d["__typename"] == "Cheese" }
215
+ milks = all_dairy.select { |d| d["__typename"] == "Milk" }
216
+
217
+ # Make sure all the data is there:
218
+ assert_equal 3, cheeses.length
219
+ assert_equal 1, milks.length
220
+
221
+ cheeses.each do |cheese|
222
+ assert_equal ["cheeseInlineOrigin", "cheeseFragmentOrigin", "edibleInlineOrigin", "untypedInlineOrigin"], cheese["selfAsEdible"].keys
223
+ end
224
+ milks.each do |milk|
225
+ assert_equal ["milkInlineOrigin", "milkFragmentOrigin", "edibleInlineOrigin", "untypedInlineOrigin"], milk["selfAsEdible"].keys
226
+ end
144
227
  end
145
228
  end
146
229
  end
@@ -5,19 +5,19 @@ describe GraphQL::Introspection::InputValueType do
5
5
  let(:query_string) {%|
6
6
  {
7
7
  __type(name: "DairyProductInput") {
8
- name,
9
- description,
10
- kind,
8
+ name
9
+ description
10
+ kind
11
11
  inputFields {
12
- name,
13
- type { kind, name },
12
+ name
13
+ type { kind, name }
14
14
  defaultValue
15
15
  description
16
16
  }
17
17
  }
18
18
  }
19
19
  |}
20
- let(:result) { DummySchema.execute(query_string)}
20
+ let(:result) { DummySchema.execute(query_string) }
21
21
 
22
22
  it "exposes metadata about input objects, giving extra quotes for strings" do
23
23
  expected = { "data" => {
@@ -62,4 +62,41 @@ describe GraphQL::Introspection::InputValueType do
62
62
 
63
63
  assert_equal('["COW"]', arg['defaultValue'])
64
64
  end
65
+
66
+ it "supports null default values" do
67
+ schema = GraphQL::Schema.from_definition(%|
68
+ type Query {
69
+ hello(person: Person): String
70
+ }
71
+
72
+ input Person {
73
+ firstName: String!
74
+ lastName: String = null
75
+ }
76
+ |)
77
+
78
+ result = schema.execute(%|
79
+ {
80
+ __type(name: "Person") {
81
+ inputFields {
82
+ name
83
+ defaultValue
84
+ }
85
+ }
86
+ }
87
+ |)
88
+
89
+ expected = {
90
+ "data" => {
91
+ "__type" => {
92
+ "inputFields" => [
93
+ { "name" => "firstName", "defaultValue" => nil},
94
+ { "name" => "lastName", "defaultValue" => "null"}
95
+ ]
96
+ }
97
+ }
98
+ }
99
+
100
+ assert_equal expected, result
101
+ end
65
102
  end