graphql-schema_comparator 1.1.1 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0ec082ccd9621ae89f5a9ed29c027cbff4a785a3bac913bd5000b3e1a914f43d
4
- data.tar.gz: f6924765894399193664e370a2464327c20e1ed0c9f35d252699893c75706736
3
+ metadata.gz: 9a21994b860741a5060b7600e7473348c0bf0445ecca50ea68966762448a967f
4
+ data.tar.gz: b4197e9f75dac972f474fd578fab78588b54f77ade6d36b126ad57a3f702b85c
5
5
  SHA512:
6
- metadata.gz: 326440a4c85adbadc3c0fafb262653e88226a55cec0a1d336a750270925a334bdfbcbfeec62e36dfa5f08d4179e8a2304622fc0e34520695de1c67ee20833a0c
7
- data.tar.gz: '069cf716049a9746cb88825734d968112ed512312c12d69da39a6d019f1257d9c74357cae5d7756bdfc5c9f8bea2f2ab8ec0bfbe21664b2e7e1e7c24d046b37e'
6
+ metadata.gz: 0b637ed11e3ba28902bd61f9e082cb0ac20f0be6868450b70e6e04df56b0019cbdba4a869d750e4dcaee4af454da799ae05512d2299300cbab3497d5d09d8392
7
+ data.tar.gz: 276c28e5588937b25361937ae594ff678981f71ac84f8f370ebada3e917e8f22f44a54c8dfb32cb9cfa8bf4e58b467dc42de6eb8ef22f864446b50c23f668a1e
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.0 (October 18 2023)
4
+
5
+ ### Bug Fix
6
+
7
+ - More selective detection of breaking/dangerous enum value changes (#54)
8
+ - Improve schema root operation type changes (#55)
9
+
10
+ ## 1.1.2 (September 15 2022)
11
+
12
+ ### Bug Fix
13
+
14
+ - Fix field argument removed message (#51)
15
+ - Fix safe type change comparison (#53)
16
+
3
17
  ## 1.1.1 (January 7 2022)
4
18
 
5
19
  ### Bug Fix
@@ -4,7 +4,7 @@ module GraphQL
4
4
  module SafeTypeChange
5
5
  def safe_change_for_field?(old_type, new_type)
6
6
  if !old_type.kind.wraps? && !new_type.kind.wraps?
7
- old_type == new_type
7
+ old_type.graphql_name == new_type.graphql_name
8
8
  elsif new_type.kind.non_null?
9
9
  of_type = old_type.kind.non_null? ? old_type.of_type : old_type
10
10
  safe_change_for_field?(of_type, new_type.of_type)
@@ -18,7 +18,7 @@ module GraphQL
18
18
 
19
19
  def safe_change_for_input_value?(old_type, new_type)
20
20
  if !old_type.kind.wraps? && !new_type.kind.wraps?
21
- old_type == new_type
21
+ old_type.graphql_name == new_type.graphql_name
22
22
  elsif old_type.kind.list? && new_type.kind.list?
23
23
  safe_change_for_input_value?(old_type.of_type, new_type.of_type)
24
24
  elsif old_type.kind.non_null?
@@ -99,12 +99,18 @@ module GraphQL
99
99
  class EnumValueRemoved < AbstractChange
100
100
  attr_reader :enum_value, :enum_type, :criticality
101
101
 
102
- def initialize(enum_type, enum_value)
102
+ def initialize(enum_type, enum_value, usage)
103
103
  @enum_value = enum_value
104
104
  @enum_type = enum_type
105
- @criticality = Changes::Criticality.breaking(
106
- reason: "Removing an enum value will cause existing queries that use this enum value to error."
107
- )
105
+ @criticality = if usage.input?
106
+ Changes::Criticality.breaking(
107
+ reason: "Removing an enum value will cause existing queries that use this enum value to error."
108
+ )
109
+ else
110
+ Changes::Criticality.non_breaking(
111
+ reason: "Removing an enum value for enums used only in outputs is non-breaking."
112
+ )
113
+ end
108
114
  end
109
115
 
110
116
  def message
@@ -169,7 +175,7 @@ module GraphQL
169
175
  end
170
176
 
171
177
  def message
172
- "Argument `#{argument.graphql_name}: #{argument.type.graphql_name}` was removed from field `#{object_type.graphql_name}.#{field.graphql_name}`"
178
+ "Argument `#{argument.graphql_name}: #{argument.type.to_type_signature}` was removed from field `#{object_type.graphql_name}.#{field.graphql_name}`"
173
179
  end
174
180
 
175
181
  def path
@@ -195,21 +201,93 @@ module GraphQL
195
201
  end
196
202
  end
197
203
 
198
- class SchemaQueryTypeChanged < AbstractChange
199
- attr_reader :old_schema, :new_schema, :criticality
204
+ class RootOperationTypeAdded < AbstractChange
205
+ attr_reader :new_schema, :operation_type, :criticality
206
+
207
+ def initialize(new_schema:, operation_type:)
208
+ @new_schema = new_schema
209
+ @operation_type = operation_type
210
+ @criticality = Changes::Criticality.non_breaking(
211
+ reason: "Adding a schema #{operation_type} root is considered non-breaking."
212
+ )
213
+ end
214
+
215
+ def message
216
+ "Schema #{operation_type} root `#{operation_type_name}` was added"
217
+ end
218
+
219
+ def path
220
+ operation_type_name
221
+ end
222
+
223
+ def operation_type_name
224
+ case operation_type
225
+ when :query
226
+ new_schema.query.graphql_name
227
+ when :mutation
228
+ new_schema.mutation.graphql_name
229
+ when :subscription
230
+ new_schema.subscription.graphql_name
231
+ end
232
+ end
233
+ end
234
+
235
+ class RootOperationTypeChanged < AbstractChange
236
+ attr_reader :old_schema, :new_schema, :operation_type, :criticality
200
237
 
201
- def initialize(old_schema, new_schema)
238
+ def initialize(old_schema:, new_schema:, operation_type:)
202
239
  @old_schema = old_schema
203
240
  @new_schema = new_schema
241
+ @operation_type = operation_type
204
242
  @criticality = Changes::Criticality.breaking
205
243
  end
206
244
 
207
245
  def message
208
- "Schema query root has changed from `#{old_schema.query.graphql_name}` to `#{new_schema.query.graphql_name}`"
246
+ "Schema #{operation_type} root has changed from `#{operation_type_name(old_schema)}` to `#{operation_type_name(new_schema)}`"
209
247
  end
210
248
 
211
249
  def path
212
- # TODO
250
+ operation_type_name(old_schema)
251
+ end
252
+
253
+ def operation_type_name(schema)
254
+ case operation_type
255
+ when :query
256
+ schema.query.graphql_name
257
+ when :mutation
258
+ schema.mutation.graphql_name
259
+ when :subscription
260
+ schema.subscription.graphql_name
261
+ end
262
+ end
263
+ end
264
+
265
+ class RootOperationTypeRemoved < AbstractChange
266
+ attr_reader :old_schema, :operation_type, :criticality
267
+
268
+ def initialize(old_schema:, operation_type:)
269
+ @old_schema = old_schema
270
+ @operation_type = operation_type
271
+ @criticality = Changes::Criticality.breaking
272
+ end
273
+
274
+ def message
275
+ "Schema #{operation_type} root `#{operation_type_name}` was removed"
276
+ end
277
+
278
+ def path
279
+ operation_type_name
280
+ end
281
+
282
+ def operation_type_name
283
+ case operation_type
284
+ when :query
285
+ old_schema.query.graphql_name
286
+ when :mutation
287
+ old_schema.mutation.graphql_name
288
+ when :subscription
289
+ old_schema.subscription.graphql_name
290
+ end
213
291
  end
214
292
  end
215
293
 
@@ -398,42 +476,6 @@ module GraphQL
398
476
  end
399
477
  end
400
478
 
401
- class SchemaMutationTypeChanged < AbstractChange
402
- attr_reader :old_schema, :new_schema, :criticality
403
-
404
- def initialize(old_schema, new_schema)
405
- @old_schema = old_schema
406
- @new_schema = new_schema
407
- @criticality = Changes::Criticality.breaking
408
- end
409
-
410
- def message
411
- "Schema mutation root has changed from `#{old_schema.mutation}` to `#{new_schema.mutation}`"
412
- end
413
-
414
- def path
415
- # TODO
416
- end
417
- end
418
-
419
- class SchemaSubscriptionTypeChanged < AbstractChange
420
- attr_reader :old_schema, :new_schema, :criticality
421
-
422
- def initialize(old_schema, new_schema)
423
- @old_schema = old_schema
424
- @new_schema = new_schema
425
- @criticality = Changes::Criticality.breaking
426
- end
427
-
428
- def message
429
- "Schema subscription type has changed from `#{old_schema.subscription}` to `#{new_schema.subscription}`"
430
- end
431
-
432
- def path
433
- # TODO
434
- end
435
- end
436
-
437
479
  # Dangerous Changes
438
480
 
439
481
  class FieldArgumentDefaultChanged < AbstractChange
@@ -451,11 +493,11 @@ module GraphQL
451
493
  end
452
494
 
453
495
  def message
454
- if old_argument.default_value.nil? || old_argument.default_value == :__no_default__
455
- "Default value `#{new_argument.default_value}` was added to argument `#{new_argument.graphql_name}` on field `#{field.path}`"
456
- else
496
+ if old_argument.default_value?
457
497
  "Default value for argument `#{new_argument.graphql_name}` on field `#{field.path}` changed"\
458
498
  " from `#{old_argument.default_value}` to `#{new_argument.default_value}`"
499
+ else
500
+ "Default value `#{new_argument.default_value}` was added to argument `#{new_argument.graphql_name}` on field `#{field.path}`"
459
501
  end
460
502
  end
461
503
 
@@ -501,8 +543,12 @@ module GraphQL
501
543
  end
502
544
 
503
545
  def message
504
- "Default value for argument `#{new_argument.graphql_name}` on directive `#{directive.graphql_name}` changed"\
505
- " from `#{old_argument.default_value}` to `#{new_argument.default_value}`"
546
+ if old_argument.default_value?
547
+ "Default value for argument `#{new_argument.graphql_name}` on directive `#{directive.graphql_name}` changed"\
548
+ " from `#{old_argument.default_value}` to `#{new_argument.default_value}`"
549
+ else
550
+ "Default value `#{new_argument.default_value}` was added to argument `#{new_argument.graphql_name}` on directive `#{directive.graphql_name}`"
551
+ end
506
552
  end
507
553
 
508
554
  def path
@@ -513,13 +559,19 @@ module GraphQL
513
559
  class EnumValueAdded < AbstractChange
514
560
  attr_reader :enum_type, :enum_value, :criticality
515
561
 
516
- def initialize(enum_type, enum_value)
562
+ def initialize(enum_type, enum_value, usage)
517
563
  @enum_type = enum_type
518
564
  @enum_value = enum_value
519
- @criticality = Changes::Criticality.dangerous(
520
- reason: "Adding an enum value may break existing clients that were not " \
521
- "programming defensively against an added case when querying an enum."
522
- )
565
+ @criticality = if usage.output?
566
+ Changes::Criticality.dangerous(
567
+ reason: "Adding an enum value may break existing clients that were not " \
568
+ "programmed defensively against an added case when querying an enum."
569
+ )
570
+ else
571
+ Changes::Criticality.non_breaking(
572
+ reason: "Adding an enum value for enums used only in inputs is non-breaking."
573
+ )
574
+ end
523
575
  end
524
576
 
525
577
  def message
@@ -2,19 +2,21 @@ module GraphQL
2
2
  module SchemaComparator
3
3
  module Diff
4
4
  class Enum
5
- def initialize(old_enum, new_enum)
5
+ def initialize(old_enum, new_enum, usage)
6
6
  @old_enum = old_enum
7
7
  @new_enum = new_enum
8
8
 
9
9
  @old_values = old_enum.values
10
10
  @new_values = new_enum.values
11
+
12
+ @usage = usage
11
13
  end
12
14
 
13
15
  def diff
14
16
  changes = []
15
17
 
16
- changes += removed_values.map { |value| Changes::EnumValueRemoved.new(old_enum, value) }
17
- changes += added_values.map { |value| Changes::EnumValueAdded.new(new_enum, value) }
18
+ changes += removed_values.map { |value| Changes::EnumValueRemoved.new(old_enum, value, usage) }
19
+ changes += added_values.map { |value| Changes::EnumValueAdded.new(new_enum, value, usage) }
18
20
 
19
21
  each_common_value do |old_value, new_value|
20
22
  # TODO: Add Directive Stuff
@@ -33,7 +35,7 @@ module GraphQL
33
35
 
34
36
  private
35
37
 
36
- attr_reader :old_enum, :new_enum, :old_values, :new_values
38
+ attr_reader :old_enum, :new_enum, :old_values, :new_values, :usage
37
39
 
38
40
  def each_common_value(&block)
39
41
  intersection = old_values.keys & new_values.keys
@@ -42,7 +42,7 @@ module GraphQL
42
42
  else
43
43
  case old_type.kind.name
44
44
  when "ENUM"
45
- changes += Diff::Enum.new(old_type, new_type).diff
45
+ changes += Diff::Enum.new(old_type, new_type, enum_usage(new_type)).diff
46
46
  when "UNION"
47
47
  changes += Diff::Union.new(old_type, new_type).diff
48
48
  when "INPUT_OBJECT"
@@ -65,15 +65,33 @@ module GraphQL
65
65
  changes = []
66
66
 
67
67
  if old_schema.query&.graphql_name != new_schema.query&.graphql_name
68
- changes << Changes::SchemaQueryTypeChanged.new(old_schema, new_schema)
68
+ if old_schema.query.nil?
69
+ changes << Changes::RootOperationTypeAdded.new(new_schema: new_schema, operation_type: :query)
70
+ elsif new_schema.query.nil?
71
+ changes << Changes::RootOperationTypeRemoved.new(old_schema: old_schema, operation_type: :query)
72
+ else
73
+ changes << Changes::RootOperationTypeChanged.new(old_schema: old_schema, new_schema: new_schema, operation_type: :query)
74
+ end
69
75
  end
70
76
 
71
77
  if old_schema.mutation&.graphql_name != new_schema.mutation&.graphql_name
72
- changes << Changes::SchemaMutationTypeChanged.new(old_schema, new_schema)
78
+ if old_schema.mutation.nil?
79
+ changes << Changes::RootOperationTypeAdded.new(new_schema: new_schema, operation_type: :mutation)
80
+ elsif new_schema.mutation.nil?
81
+ changes << Changes::RootOperationTypeRemoved.new(old_schema: old_schema, operation_type: :mutation)
82
+ else
83
+ changes << Changes::RootOperationTypeChanged.new(old_schema: old_schema, new_schema: new_schema, operation_type: :mutation)
84
+ end
73
85
  end
74
86
 
75
87
  if old_schema.subscription&.graphql_name != new_schema.subscription&.graphql_name
76
- changes << Changes::SchemaSubscriptionTypeChanged.new(old_schema, new_schema)
88
+ if old_schema.subscription.nil?
89
+ changes << Changes::RootOperationTypeAdded.new(new_schema: new_schema, operation_type: :subscription)
90
+ elsif new_schema.subscription.nil?
91
+ changes << Changes::RootOperationTypeRemoved.new(old_schema: old_schema, operation_type: :subscription)
92
+ else
93
+ changes << Changes::RootOperationTypeChanged.new(old_schema: old_schema, new_schema: new_schema, operation_type: :subscription)
94
+ end
77
95
  end
78
96
 
79
97
  changes
@@ -94,6 +112,12 @@ module GraphQL
94
112
 
95
113
  private
96
114
 
115
+ def enum_usage(new_enum)
116
+ input_usage = new_schema.references_to(new_enum).any? { |member| member.is_a?(GraphQL::Schema::Argument) }
117
+ output_usage = new_schema.references_to(new_enum).any? { |member| member.is_a?(GraphQL::Schema::Field) }
118
+ EnumUsage.new(input: input_usage, output: output_usage)
119
+ end
120
+
97
121
  def each_common_type(&block)
98
122
  intersection = old_types.keys & new_types.keys
99
123
  intersection.each do |common_type_name|
@@ -0,0 +1,18 @@
1
+ module GraphQL
2
+ module SchemaComparator
3
+ class EnumUsage
4
+ def initialize(input:, output:)
5
+ @input = input
6
+ @output = output
7
+ end
8
+
9
+ def input?
10
+ @input
11
+ end
12
+
13
+ def output?
14
+ @output
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,5 +1,5 @@
1
1
  module GraphQL
2
2
  module SchemaComparator
3
- VERSION = "1.1.1"
3
+ VERSION = "1.2.0"
4
4
  end
5
5
  end
@@ -4,6 +4,7 @@ require "graphql/schema_comparator/version"
4
4
  require "graphql/schema_comparator/result"
5
5
 
6
6
  require 'graphql/schema_comparator/changes'
7
+ require 'graphql/schema_comparator/enum_usage'
7
8
 
8
9
  require "graphql/schema_comparator/diff/schema"
9
10
  require "graphql/schema_comparator/diff/argument"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql-schema_comparator
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marc-Andre Giroux
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-07 00:00:00.000000000 Z
11
+ date: 2023-10-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: graphql
@@ -147,6 +147,7 @@ files:
147
147
  - lib/graphql/schema_comparator/diff/object_type.rb
148
148
  - lib/graphql/schema_comparator/diff/schema.rb
149
149
  - lib/graphql/schema_comparator/diff/union.rb
150
+ - lib/graphql/schema_comparator/enum_usage.rb
150
151
  - lib/graphql/schema_comparator/result.rb
151
152
  - lib/graphql/schema_comparator/version.rb
152
153
  - schema.graphql
@@ -158,7 +159,7 @@ metadata:
158
159
  changelog_uri: https://github.com/xuorig/graphql-schema_comparator/blob/master/CHANGELOG.md
159
160
  source_code_uri: https://github.com/xuorig/graphql-schema_comparator
160
161
  bug_tracker_uri: https://github.com/xuorig/graphql-schema_comparator/issues
161
- post_install_message:
162
+ post_install_message:
162
163
  rdoc_options: []
163
164
  require_paths:
164
165
  - lib
@@ -173,8 +174,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
173
174
  - !ruby/object:Gem::Version
174
175
  version: '0'
175
176
  requirements: []
176
- rubygems_version: 3.0.3.1
177
- signing_key:
177
+ rubygems_version: 3.4.10
178
+ signing_key:
178
179
  specification_version: 4
179
180
  summary: Compare GraphQL schemas and get the changes that happened.
180
181
  test_files: []