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

Implement ApplyToInternal::compute_output_shape and JSONSelection::shape returning shape::Shape #6458

Merged
merged 4 commits into from
Jan 14, 2025

Conversation

benjamn
Copy link
Member

@benjamn benjamn commented Dec 13, 2024

This draft PR uses our newly published shape crate to implement the ApplyToInternal::compute_output_shape trait method, which returns a shape::Shape, which is an immutable and reference-counted Rust data structure that describes a set of constraints on JSON data. The "shape" terminology is a close synonym for "type" but I'm using it to emphasize the immutable, reference-free, value-typed nature of the JSON domain.

As an important part of this ::compute_output_shape system, all -> methods now define a method_shape function that allows them to reject unexpected input shapes and compute their own output shape in terms of a given input shape and variable shapes. In other words, the type signatures of -> methods are expressed using Rust code which ultimately returns a shape::Shape, which then gets further processed by the compute_output_shape logic, allowing -> methods to blend seamlessly into the shape processing system.

This new ability to compute a static output Shape for a given JSONSelection string allows us to relax the constraint that inline PathSelection items must have a static {...} subselection at the end. The static subselection was previously our only means of statically determining the output fields of the PathSelection, but now we can statically/programmatically examine the output Shape, which doesn't distinguish between static subselections and -> method results (as long as they have the same computed object shape). We are also now requiring a ... token before inline PathSelection items without static subselections, and encouraging the ... token for any inline PathSelection, even if it has a subselection (though enforcing it in that case would be a breaking change).

Finally, this PR proposes a way of setting the __typename in situations where abstract GraphQL types (interfaces and unions) are involved: by writing a { <Type> ... } annotation at the beginning of any subselection, developers can indicate to both the Shape system and the runtime system that the __typename of this output object should have the literal shape "Type", and the object should include __typename: "Type" at runtime. The underlying Shape representation actually does use a __typename field to model this information, but that's a choice made by the JSONSelection code, not something embedded in the Shape library.

I wish I could have broken this PR up more, though I was able to split out #6455, #6456, and #6457.

I don't believe this PR should introduce any logical runtime differences of behavior in the connectors system, since it's basically adding the compute_output_shape method and then only using it in tests. Once I address a few TODOs, the output shapes will play a role in checking the validity of ...-inlined PathSelection items, but the ... syntax is new with this PR, so changing it is still easy.

@benjamn benjamn self-assigned this Dec 13, 2024
@svc-apollo-docs
Copy link
Collaborator

svc-apollo-docs commented Dec 13, 2024

✅ Docs preview has no changes

The preview was not built because there were no changes.

Build ID: 61dbe18c073365a3763b2e50

@router-perf
Copy link

router-perf bot commented Dec 13, 2024

CI performance tests

  • connectors-const - Connectors stress test that runs with a constant number of users
  • const - Basic stress test that runs with a constant number of users
  • demand-control-instrumented - A copy of the step test, but with demand control monitoring and metrics enabled
  • demand-control-uninstrumented - A copy of the step test, but with demand control monitoring enabled
  • enhanced-signature - Enhanced signature enabled
  • events - Stress test for events with a lot of users and deduplication ENABLED
  • events_big_cap_high_rate - Stress test for events with a lot of users, deduplication enabled and high rate event with a big queue capacity
  • events_big_cap_high_rate_callback - Stress test for events with a lot of users, deduplication enabled and high rate event with a big queue capacity using callback mode
  • events_callback - Stress test for events with a lot of users and deduplication ENABLED in callback mode
  • events_without_dedup - Stress test for events with a lot of users and deduplication DISABLED
  • events_without_dedup_callback - Stress test for events with a lot of users and deduplication DISABLED using callback mode
  • extended-reference-mode - Extended reference mode enabled
  • large-request - Stress test with a 1 MB request payload
  • no-tracing - Basic stress test, no tracing
  • reload - Reload test over a long period of time at a constant rate of users
  • step-jemalloc-tuning - Clone of the basic stress test for jemalloc tuning
  • step-local-metrics - Field stats that are generated from the router rather than FTV1
  • step-with-prometheus - A copy of the step test with the Prometheus metrics exporter enabled
  • step - Basic stress test that steps up the number of users over time
  • xlarge-request - Stress test with 10 MB request payload
  • xxlarge-request - Stress test with 100 MB request payload

@benjamn benjamn changed the base branch from benjamn/JSONSelection-withError-arrow-method to benjamn/JSONSelection-variadic-ExprPath December 16, 2024 16:56
NamedSelection ::= NamedPathSelection | PathWithSubSelection | NamedFieldSelection | NamedGroupSelection
NamedPathSelection ::= Alias PathSelection
NamedPathSelection ::= (Alias | "...") PathSelection
Copy link
Contributor

Choose a reason for hiding this comment

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

does this allow for future keywords like if or match directly after the ...?

Copy link
Member Author

@benjamn benjamn Dec 16, 2024

Choose a reason for hiding this comment

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

If the PathSelection started with an if token, that would be a potential concern for making if a keyword in the future, but if we stick with the the idea of a parenthesized test (i.e. ... if (test) {}), the ( character should prevent parsing as a PathSelection, causing the parser to backtrack and consider other ways of parsing ... if (test) {} (which we can define).

Same goes for ... match (value), as long as we use parentheses, or as long as we're willing to make the match field illegal immediately after ... (a breaking change but not unthinkable).

Copy link
Member Author

Choose a reason for hiding this comment

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

Postponing this syntax for a later PR/release.

@@ -69,6 +73,44 @@ impl JSONSelection {

(value, errors.into_iter().collect())
}

pub fn shape(&self) -> Shape {
Copy link
Contributor

Choose a reason for hiding this comment

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

what's the reason for having shape code in this file? it feels different enough to warrant a separate file and maybe a separate trait, even if it's structured basically the same

Copy link
Member Author

@benjamn benjamn Dec 16, 2024

Choose a reason for hiding this comment

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

It made the code a little easier to write to have apply_to_path nearby, but there's probably no harm in moving it to a new file now.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll make this move in a follow-up PR.


for pair in args {
if let LitExpr::Array(pair) = pair.as_ref() {
if pair.len() == 2 {
Copy link
Contributor

Choose a reason for hiding this comment

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

should we return an error if the length isn't exactly 2?

Copy link
Contributor

Choose a reason for hiding this comment

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

do you have an example of error handling? since this doesn't return a Result, what should a caller do with a Shape::error_with_range?

Copy link
Member Author

Choose a reason for hiding this comment

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

I imagine we'll add a way to collect/traverse/visit any ShapeCase::Error variants within a given Shape, to handle them programmatically or surface them in the debugger, but for now the main point of errors is to fail validation against expected GraphQL schema types.

Since you can embed ShapeCase::Error within a larger shape, shape errors are similar to the GraphQL errors-as-data pattern, where you're able to get back some partial shape information along with contextual errors.

@benjamn benjamn force-pushed the benjamn/JSONSelection-variadic-ExprPath branch from 909e897 to e5eedde Compare December 16, 2024 18:24
@benjamn benjamn force-pushed the benjamn/JSONSelection-compute_output_shape branch from 15b6b8c to 1fde6d8 Compare December 16, 2024 18:24
@benjamn benjamn force-pushed the benjamn/JSONSelection-variadic-ExprPath branch from e5eedde to 16d0306 Compare December 16, 2024 18:48
@benjamn benjamn force-pushed the benjamn/JSONSelection-compute_output_shape branch from 1fde6d8 to ebb3e95 Compare December 16, 2024 19:03
@benjamn benjamn force-pushed the benjamn/JSONSelection-variadic-ExprPath branch from 16d0306 to dfeb558 Compare December 16, 2024 19:12
@benjamn benjamn force-pushed the benjamn/JSONSelection-compute_output_shape branch from ebb3e95 to 70486c1 Compare December 16, 2024 20:01
@benjamn benjamn force-pushed the benjamn/JSONSelection-compute_output_shape branch from 70486c1 to 2658a33 Compare January 7, 2025 18:30
@benjamn benjamn changed the base branch from benjamn/JSONSelection-variadic-ExprPath to next January 7, 2025 18:30
Comment on lines +2537 to +2533
// This output shape is wrong if $root.friend_ids turns out to be an
// array, and it's tricky to see how to transform the shape to what
// it would have been if we knew that, where friends: List<{ id:
// $root.friend_ids.* }> (note the * meaning any array index),
// because who's to say it's not the id field that should become the
// List, rather than the friends field?
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not following the impact of this comment. If the result is potentially incorrect, is this an area that needs future work? Or is the potentially incorrect result not a problem for some reason?

Is there something needed in the syntax to clarify this case? Similarly to how the user can specify <Type>, do we need a way to indicate that there is an array type expected here, like [friend_ids]?

Or should the result be a union of all the possible combinations of things that could be an array (which seems like a potentially large number of possibilities)?

@benjamn benjamn force-pushed the benjamn/JSONSelection-compute_output_shape branch 2 times, most recently from af100bc to cb6b5ef Compare January 10, 2025 19:39
@benjamn benjamn changed the base branch from next to dylan/pr-6513-prelude January 10, 2025 19:39
Base automatically changed from dylan/pr-6513-prelude to next January 13, 2025 18:20
@benjamn benjamn force-pushed the benjamn/JSONSelection-compute_output_shape branch from cb6b5ef to fef9431 Compare January 13, 2025 19:06
benjamn added a commit that referenced this pull request Jan 13, 2025
@benjamn benjamn marked this pull request as ready for review January 13, 2025 22:09
@benjamn
Copy link
Member Author

benjamn commented Jan 13, 2025

The TODOs I mentioned above are important, but I'm going to attempt them after merging PR #6513 on top of this PR, rather than addressing them in this PR.

dylan-apollo added a commit that referenced this pull request Jan 14, 2025
This PR implements full expressions in URIs and header values (e.g., arrow methods, not just variables) via a few pieces:

1. The new apollo-federation/src/sources/connect/string_template.rs contains shared logic for both URIs and headers, so they can be more consistent behavior (both parsing and interpolation). This comes with support for full JSONSelection rather than just the variable references
2. As a result of 1, headers now work much like URIs in that arrays and objects won't be automatically serialized to JSON, instead it is an error for an expression to result in one of those. This is a breaking change. Users should work around this with ->jsonStringify from ->jsonStringify method #6519
3. Much of the former variable reference code has been removed in favor of this new parsing approach, which leans on the JSONSelection parser instead
4. To validate these more complex expressions, the new shape library is being used. As a result this PR relies on Implement `ApplyToInternal::compute_output_shape` and `JSONSelection::shape` returning `shape::Shape` #6458

There are some expected gaps in the validations where we need to build more infrastructure for the type checker. For example, ->map is completely ignored for now.
@benjamn benjamn force-pushed the benjamn/JSONSelection-compute_output_shape branch 2 times, most recently from 9873eb3 to c6ace9a Compare January 14, 2025 18:49
benjamn and others added 4 commits January 14, 2025 13:51
* Remove unused generic parameter from `Ranged` trait.
* Implement `Hash` for `WithRange<T>` only when `T: Hash`.
* Implement/use `PrettyPrintable` for `Alias` and `Key`.
* Remove redundant `NamedSelection::apply_to_path` array base case.
This PR implements full expressions in URIs and header values (e.g., arrow methods, not just variables) via a few pieces:

1. The new apollo-federation/src/sources/connect/string_template.rs contains shared logic for both URIs and headers, so they can be more consistent behavior (both parsing and interpolation). This comes with support for full JSONSelection rather than just the variable references
2. As a result of 1, headers now work much like URIs in that arrays and objects won't be automatically serialized to JSON, instead it is an error for an expression to result in one of those. This is a breaking change. Users should work around this with ->jsonStringify from ->jsonStringify method #6519
3. Much of the former variable reference code has been removed in favor of this new parsing approach, which leans on the JSONSelection parser instead
4. To validate these more complex expressions, the new shape library is being used. As a result this PR relies on Implement `ApplyToInternal::compute_output_shape` and `JSONSelection::shape` returning `shape::Shape` #6458

There are some expected gaps in the validations where we need to build more infrastructure for the type checker. For example, ->map is completely ignored for now.
@benjamn benjamn force-pushed the benjamn/JSONSelection-compute_output_shape branch from c6ace9a to 32dafac Compare January 14, 2025 19:08
@benjamn benjamn enabled auto-merge January 14, 2025 19:28
@benjamn benjamn merged commit 1263404 into next Jan 14, 2025
12 checks passed
@benjamn benjamn deleted the benjamn/JSONSelection-compute_output_shape branch January 14, 2025 19:39
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.

5 participants