diff --git a/lib/graphql/schema_comparator/changes.rb b/lib/graphql/schema_comparator/changes.rb index ad58a52..053554d 100644 --- a/lib/graphql/schema_comparator/changes.rb +++ b/lib/graphql/schema_comparator/changes.rb @@ -201,21 +201,93 @@ def path end end - class SchemaQueryTypeChanged < AbstractChange - attr_reader :old_schema, :new_schema, :criticality + class RootOperationTypeAdded < AbstractChange + attr_reader :new_schema, :operation_type, :criticality - def initialize(old_schema, new_schema) + def initialize(new_schema:, operation_type:) + @new_schema = new_schema + @operation_type = operation_type + @criticality = Changes::Criticality.non_breaking( + reason: "Adding a schema #{operation_type} root is considered non-breaking." + ) + end + + def message + "Schema #{operation_type} root `#{operation_type_name}` was added" + end + + def path + operation_type_name + end + + def operation_type_name + case operation_type + when :query + new_schema.query.graphql_name + when :mutation + new_schema.mutation.graphql_name + when :subscription + new_schema.subscription.graphql_name + end + end + end + + class RootOperationTypeChanged < AbstractChange + attr_reader :old_schema, :new_schema, :operation_type, :criticality + + def initialize(old_schema:, new_schema:, operation_type:) @old_schema = old_schema @new_schema = new_schema + @operation_type = operation_type @criticality = Changes::Criticality.breaking end def message - "Schema query root has changed from `#{old_schema.query.graphql_name}` to `#{new_schema.query.graphql_name}`" + "Schema #{operation_type} root has changed from `#{operation_type_name(old_schema)}` to `#{operation_type_name(new_schema)}`" end def path - # TODO + operation_type_name(old_schema) + end + + def operation_type_name(schema) + case operation_type + when :query + schema.query.graphql_name + when :mutation + schema.mutation.graphql_name + when :subscription + schema.subscription.graphql_name + end + end + end + + class RootOperationTypeRemoved < AbstractChange + attr_reader :old_schema, :operation_type, :criticality + + def initialize(old_schema:, operation_type:) + @old_schema = old_schema + @operation_type = operation_type + @criticality = Changes::Criticality.breaking + end + + def message + "Schema #{operation_type} root `#{operation_type_name}` was removed" + end + + def path + operation_type_name + end + + def operation_type_name + case operation_type + when :query + old_schema.query.graphql_name + when :mutation + old_schema.mutation.graphql_name + when :subscription + old_schema.subscription.graphql_name + end end end @@ -404,42 +476,6 @@ def path end end - class SchemaMutationTypeChanged < AbstractChange - attr_reader :old_schema, :new_schema, :criticality - - def initialize(old_schema, new_schema) - @old_schema = old_schema - @new_schema = new_schema - @criticality = Changes::Criticality.breaking - end - - def message - "Schema mutation root has changed from `#{old_schema.mutation}` to `#{new_schema.mutation}`" - end - - def path - # TODO - end - end - - class SchemaSubscriptionTypeChanged < AbstractChange - attr_reader :old_schema, :new_schema, :criticality - - def initialize(old_schema, new_schema) - @old_schema = old_schema - @new_schema = new_schema - @criticality = Changes::Criticality.breaking - end - - def message - "Schema subscription type has changed from `#{old_schema.subscription}` to `#{new_schema.subscription}`" - end - - def path - # TODO - end - end - # Dangerous Changes class FieldArgumentDefaultChanged < AbstractChange @@ -457,11 +493,11 @@ def initialize(type, field, old_argument, new_argument) end def message - if old_argument.default_value.nil? || old_argument.default_value == :__no_default__ - "Default value `#{new_argument.default_value}` was added to argument `#{new_argument.graphql_name}` on field `#{field.path}`" - else + if old_argument.default_value? "Default value for argument `#{new_argument.graphql_name}` on field `#{field.path}` changed"\ " from `#{old_argument.default_value}` to `#{new_argument.default_value}`" + else + "Default value `#{new_argument.default_value}` was added to argument `#{new_argument.graphql_name}` on field `#{field.path}`" end end @@ -507,8 +543,12 @@ def initialize(directive, old_argument, new_argument) end def message - "Default value for argument `#{new_argument.graphql_name}` on directive `#{directive.graphql_name}` changed"\ - " from `#{old_argument.default_value}` to `#{new_argument.default_value}`" + if old_argument.default_value? + "Default value for argument `#{new_argument.graphql_name}` on directive `#{directive.graphql_name}` changed"\ + " from `#{old_argument.default_value}` to `#{new_argument.default_value}`" + else + "Default value `#{new_argument.default_value}` was added to argument `#{new_argument.graphql_name}` on directive `#{directive.graphql_name}`" + end end def path diff --git a/lib/graphql/schema_comparator/diff/schema.rb b/lib/graphql/schema_comparator/diff/schema.rb index dee531e..774f637 100644 --- a/lib/graphql/schema_comparator/diff/schema.rb +++ b/lib/graphql/schema_comparator/diff/schema.rb @@ -65,15 +65,33 @@ def changes_in_schema changes = [] if old_schema.query&.graphql_name != new_schema.query&.graphql_name - changes << Changes::SchemaQueryTypeChanged.new(old_schema, new_schema) + if old_schema.query.nil? + changes << Changes::RootOperationTypeAdded.new(new_schema: new_schema, operation_type: :query) + elsif new_schema.query.nil? + changes << Changes::RootOperationTypeRemoved.new(old_schema: old_schema, operation_type: :query) + else + changes << Changes::RootOperationTypeChanged.new(old_schema: old_schema, new_schema: new_schema, operation_type: :query) + end end if old_schema.mutation&.graphql_name != new_schema.mutation&.graphql_name - changes << Changes::SchemaMutationTypeChanged.new(old_schema, new_schema) + if old_schema.mutation.nil? + changes << Changes::RootOperationTypeAdded.new(new_schema: new_schema, operation_type: :mutation) + elsif new_schema.mutation.nil? + changes << Changes::RootOperationTypeRemoved.new(old_schema: old_schema, operation_type: :mutation) + else + changes << Changes::RootOperationTypeChanged.new(old_schema: old_schema, new_schema: new_schema, operation_type: :mutation) + end end if old_schema.subscription&.graphql_name != new_schema.subscription&.graphql_name - changes << Changes::SchemaSubscriptionTypeChanged.new(old_schema, new_schema) + if old_schema.subscription.nil? + changes << Changes::RootOperationTypeAdded.new(new_schema: new_schema, operation_type: :subscription) + elsif new_schema.subscription.nil? + changes << Changes::RootOperationTypeRemoved.new(old_schema: old_schema, operation_type: :subscription) + else + changes << Changes::RootOperationTypeChanged.new(old_schema: old_schema, new_schema: new_schema, operation_type: :subscription) + end end changes diff --git a/test/lib/graphql/schema_comparator/diff/schema_test.rb b/test/lib/graphql/schema_comparator/diff/schema_test.rb index e29304d..fc411c2 100644 --- a/test/lib/graphql/schema_comparator/diff/schema_test.rb +++ b/test/lib/graphql/schema_comparator/diff/schema_test.rb @@ -5,6 +5,7 @@ def setup @old_schema = <<~SCHEMA schema { query: Query + mutation: OldMutation } input AInput { # a @@ -67,6 +68,9 @@ def setup type WillBeRemoved { a: String } + type OldMutation { + a: String! + } directive @willBeRemoved on FIELD SCHEMA @@ -74,6 +78,7 @@ def setup @new_schema =<<~SCHEMA schema { query: Query + mutation: Mutation } input AInput { # changed @@ -139,6 +144,10 @@ def setup # Included when true. someArg: String! ) on FIELD + + type Mutation { + a: String! + } SCHEMA @differ = GraphQL::SchemaComparator::Diff::Schema.new( @@ -168,7 +177,7 @@ def test_changes_kitchensink "Deprecation reason on field `CType.a` has changed from `whynot` to `cuz`", "Argument `arg: Int` added to field `CType.a`", "Default value `10` was added to argument `arg` on field `CType.d`", - "Default value for argument `anotherArg` on directive `yolo` changed from `__no_default__` to `Test`", + "Default value `Test` was added to argument `anotherArg` on directive `yolo`", "Union member `BType` was removed from Union type `MyUnion`", "Union member `DType` was added to Union type `MyUnion`", "Field `anotherInterfaceField` was removed from object type `AnotherInterface`", @@ -192,6 +201,9 @@ def test_changes_kitchensink "Argument `willBeRemoved` was removed from directive `yolo`", "Description for argument `someArg` on directive `yolo` changed from `Included when true.` to `someArg does stuff`", "Type for argument `someArg` on directive `yolo` changed from `Boolean!` to `String!`", + "Type `Mutation` was added", + "Type `OldMutation` was removed", + "Schema mutation root has changed from `OldMutation` to `Mutation`", ].sort, @differ.diff.map(&:message).sort assert_equal [ @@ -214,8 +226,11 @@ def test_changes_kitchensink "CType.a.arg", "CType.d.arg", "CType.interfaceField", + "Mutation", "MyUnion", "MyUnion", + "OldMutation", + "OldMutation", "AnotherInterface.anotherInterfaceField", "AnotherInterface.b", "WithInterfaces", @@ -241,6 +256,51 @@ def test_changes_kitchensink ].sort, @differ.diff.map(&:path).sort end + def test_schema_root_changes + old_schema = <<~SCHEMA + schema { + query: OldQuery + mutation: Mutation + } + + type OldQuery { + a: String! + } + + type Mutation { + a: String! + } + SCHEMA + + new_schema = <<~SCHEMA + schema { + query: Query + subscription: Subscription + } + + type Query { + a: String! + } + + type Subscription { + a: String! + } + SCHEMA + + expected_changes = [ + { path: "Mutation", message: "Schema mutation root `Mutation` was removed", level: 3 }, + { path: "Mutation", message: "Type `Mutation` was removed", level: 3 }, + { path: "OldQuery", message: "Schema query root has changed from `OldQuery` to `Query`", level: 3 }, + { path: "OldQuery", message: "Type `OldQuery` was removed", level: 3 }, + { path: "Query", message: "Type `Query` was added", level: 1 }, + { path: "Subscription", message: "Schema subscription root `Subscription` was added", level: 1 }, + { path: "Subscription", message: "Type `Subscription` was added", level: 1 }, + ] + + actual_changes = schema_diff(old_schema, new_schema) + assert_equal(normalize_schema_diff(expected_changes), normalize_schema_diff(actual_changes)) + end + def test_enum_value_changes old_schema = <<~SCHEMA schema {