Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect new interface implementation for new types #40

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

jturkel
Copy link
Contributor

@jturkel jturkel commented Aug 3, 2021

This PR fixes a bug where dangerous new interface implementations were not detected for new types. While I was in here, I changed the kitchen sink test to compare strings rather than arrays of strings so test failures would print a nice diff.

if type.graphql_definition.is_a?(GraphQL::ObjectType)
type.interfaces.each do |interface|
if old_types[interface.graphql_name]
changes << Changes::ObjectTypeInterfaceAdded.new(interface, type)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess ideally this would only be dangerous for existing types right? It's good to capture this change for new types, but it's not actually dangerous at this point.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly. It's only dangerous when implementing an existing interface.

@swalkinshaw
Copy link
Collaborator

This is actually a subset of #33. Ideally we'd capture every change for a new type?

@jturkel
Copy link
Contributor Author

jturkel commented Aug 4, 2021

🤔 You're right there's definitely overlap between this issue and #33 but do you have any insight into the use cases where reporting nested changes would be generally useful?

My main concern is applying a recursive strategy more broadly could result in a large number of changes being reported making it harder for developers to understand the root cause of lots of warnings. For example when a type is removed, the recursive strategy would report a change for the type's removal, a change for each of its fields being removed, and a change for each its fields' arguments being removed. Perhaps we could return a tree of changes (e.g. the type removed change node would have child change nodes for each of the removed type's fields) and leave it up to clients how they want to process that tree (e.g. whether or not to stop the recursion when they hit the first breaking change node)? I'm just not sure it buys a client much over walking the graphql-ruby types though.

On a somewhat related note, I'm beginning to wonder if removing a type is actually a breaking change. It seems like the real breakage is from removing fields on other types that could break selection sets or removing an interface implementation that could break fragments. Admittedly this is probably biased by my use case of a GitHub Action that generates annotations with links to details on impacted clients for any breaking/dangerous changes though.

@swalkinshaw
Copy link
Collaborator

Our use case is a GitHub bot which ran linting on PRs. We defined hooks based on each type of change (ie: on_field_added) which a lint rule would implement (similar to how Rubocop works). Without this nested/recursive behaviour, we'd have to explicitly implement both on_field_added and a more verbose on_type_added to handle new types with fields. If the gem always generated Change::FieldAdded for fields on new types (for example), this would happen automatically.

Your proposal is basically what we did internally in our bot app so yeah clients should be able to decide what they care about or not.

On a somewhat related note, I'm beginning to wonder if removing a type is actually a breaking change. It seems like the real breakage is from removing fields on other types that could break selection sets or removing an interface implementation that could break fragments.

This sort of mirrors how deprecations work since you don't actually deprecate a type, just the fields that return them. I guess there's some nuance since removing a type can mean:

  • a union's possible types change
  • types an interface implement change
  • fragments (as you mentioned)

So imo it's still a breaking change to remove a type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants