-
Notifications
You must be signed in to change notification settings - Fork 280
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
Added integrity checks on operations and selection sets #5502
base: dev
Are you sure you want to change the base?
Conversation
added comments
@duckki, please consider creating a changeset entry in |
CI performance tests
|
I really, really like the listing of invariants and checking them. We should start a doc that lists these things out. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems we don't want to call the is_well_formed
method in production, but it might be something we want to enable based on log level as it would provide never helpful information. This isn't something you need to change in this PR, but I wanted to note it.
// `debug_check`: a debug-only sanity check. | ||
// - Executes an expression `$result` that returns a `Result<(), E>` and returns the error if the | ||
// result is an Err. | ||
macro_rules! debug_check { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason we would want to bubble this error up instead of just panicking while debugging?
|
||
//================================================================================================ | ||
// Well-formedness checks | ||
// - structural invariant checks for operations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to call out here that we're specifically not looking to check GraphQL validations, and instead looking to check that data/fields introduced by our operation data structures are self-consistent.
)?; | ||
} | ||
Ok(()) | ||
// Note: fragment_data.type_condition_position and the parent type do not have to have |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is really a GraphQL validation, so as long as we have the explanation mentioned above about how we're not doing those, I think it's fine to not have this note. (Or rather, if we included this note, we could also e.g. include notes about how we skip argument value validation, or directive application validation. I think it'd be less noisy to declare we're generally skipping GraphQL validation near the top.)
inline_fragment | ||
.selection_set | ||
.is_well_formed(schema, named_fragments, option) | ||
// Note: fragment_data.type_condition_position and the parent type do not have to have |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar feedback for this note as above.
|
||
// FragmentSpreadSelection holds a copy of fragment definition's selection set. | ||
// This option controls whether or not to check that copy or skip it. | ||
// Note that `Operation::optimize()` returns a selection set that may have outdated fragment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that
Operation::optimize()
returns a selection set that may have outdated fragment selection sets.
This strikes me as a bug. For rebasing, the JS codebase ensures that fragment data is updated in FragmentSpreadSelection.rebaseOn()
, specifically here. The pre-requisite is that the passed-in named fragments have been rebased correctly, but this should be handled provided we've rebased the fragments in dependency order (which is what the JS code does here).
} | ||
|
||
impl Operation { | ||
pub fn is_well_formed( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm noticing that:
- The well-formedness check for fragment spread selections recurses into the fragment's selection sets, each time the fragment spread is referenced (assuming they're not skipped).
- We don't check the well-formedness of
Operation.named_fragments
here. I'm guessing because we assume they should be exactly the same as the fragments referenced above (although we're not checking this here).
Looking at the codebase, the FragmentSpreadSelection.selection_set
field, when directly set, comes from existing Fragment
s. That means that it should always match the corresponding Fragment
's selection set in the operation. And not just Eq
here for SelectionSet
, but more specifically SelectionSet.schema
and SelectionSet.type_position
should be Eq
, and SelectionSet.selections
should compare the same using Arc::ptr_eq()
.
This would suggest that we change the above validation to:
- Validate the fragments in
Operation.named_fragments
(recursing into their selection sets, but also e.g. checking thatFragment.type_condition_position
matches the one in the selection set). - When validating a fragment spread selection, we should check that the
FragmentSpreadSelection.selection_set
matches in the way described above.
There's a similar case to be made for FragmentSpreadSelection.spread
, where it always comes from feeding a Fragment
to FragmentSpreadData::from_fragment()
. This means we should similarly check matching there, i.e. we should check for Eq
for FragmentSpreadData.schema
, FragmentSpreadData.fragment_name
, and FragmentSpreadData.type_condition_position
, and check Arc::ptr_eq()
for FragmentSpreadData::fragment_directives()
.
- While looking here, I noticed a bug in
FragmentSpreadSelection::new()
where it re-fetches its fragment selection set fromNamedFragments
, but doesn't update the spread data. Glancing at the code, this looks to be used only forSelectionSet::make_selection()
, specifically inSelection::from_operation_element()
. The latter function appears to be a rename ofselectionOfElement()
from JS, but the JS code converts fragment spreads to inline fragments, while the Rust code appears to be keeping them as fragment spreads. It would be better I think here to copy the JS code's behavior (or error when fragment spreads are present, if we believe they shouldn't occur here).
Changing to draft so we don't get pinged to rereview it every day in slack 😇 |
Operations and selection/selection-sets has internal invariants (before considering its correctness or any other constraints).
I call it "well-formedness" of operation/selection/-set structs. For example:
parent_type_position
must be the type of its parent selection set.I implemented the check and placed it in several places where we build operations. Those checks are only executed in debug builds.
I found a bug in
rebase_on
and fixed it in this PR. Though, that fix didn't change the status of existing tests.Checklist
Complete the checklist (and note appropriate exceptions) before the PR is marked ready-for-review.
Exceptions
Note any exceptions here
Notes
Footnotes
It may be appropriate to bring upcoming changes to the attention of other (impacted) groups. Please endeavour to do this before seeking PR approval. The mechanism for doing this will vary considerably, so use your judgement as to how and when to do this. ↩
Configuration is an important part of many changes. Where applicable please try to document configuration examples. ↩
Tick whichever testing boxes are applicable. If you are adding Manual Tests, please document the manual testing (extensively) in the Exceptions. ↩