Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 49 additions & 3 deletions apollo-federation/src/sources/connect/expand/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ mod helpers {
use apollo_compiler::ast::FieldDefinition;
use apollo_compiler::ast::InputValueDefinition;
use apollo_compiler::ast::Value;
use apollo_compiler::collections::HashSet;
use apollo_compiler::name;
use apollo_compiler::schema::Component;
use apollo_compiler::schema::ComponentName;
Expand Down Expand Up @@ -206,6 +207,8 @@ mod helpers {
use crate::schema::position::TypeDefinitionPosition;
use crate::schema::FederationSchema;
use crate::schema::ValidFederationSchema;
use crate::sources::connect::json_selection::ExtractParameters;
use crate::sources::connect::json_selection::StaticParameter;
use crate::sources::connect::url_template::Parameter;
use crate::sources::connect::ConnectSpecDefinition;
use crate::sources::connect::Connector;
Expand Down Expand Up @@ -390,6 +393,10 @@ mod helpers {
connector: &Connector,
arguments: &[Node<InputValueDefinition>],
) -> Result<(), FederationError> {
// The body of the request might include references to input arguments / sibling fields
// that will need to be handled, so we extract any referenced variables now
let body_parameters = extract_params_from_body(connector)?;

let parameters = connector
.transport
.connect_template
Expand All @@ -399,15 +406,15 @@ mod helpers {
"could not extract path template parameters: {e}"
))
})?;
for parameter in parameters {
for parameter in Iterator::chain(body_parameters.iter(), &parameters) {
match parameter {
// Ensure that input arguments are carried over to the new schema, including
// any types associated with them.
Parameter::Argument { argument, .. } => {
// Get the argument type
let arg = arguments
.iter()
.find(|a| a.name.as_str() == argument)
.find(|a| a.name.as_str() == *argument)
.ok_or(FederationError::internal(format!(
"could not find argument: {argument}"
)))?;
Expand Down Expand Up @@ -460,6 +467,10 @@ mod helpers {
let parent_type = self.original_schema.get_type(parent_type_name)?;
let output_type = to_schema.get_type(output_type_name)?;

// The body of the request might include references to input arguments / sibling fields
// that will need to be handled, so we extract any referenced variables now
let body_parameters = extract_params_from_body(connector)?;

let parameters = connector
.transport
.connect_template
Expand All @@ -472,7 +483,7 @@ mod helpers {
// We'll need to collect all synthesized keys for the output type, adding a federation
// `@key` directive once completed.
let mut keys = Vec::new();
for parameter in parameters {
for parameter in Iterator::chain(body_parameters.iter(), &parameters) {
match parameter {
// Arguments should be added to the synthesized key, since they are mandatory
// to resolving the output type. The synthesized key should only include the portions
Expand Down Expand Up @@ -784,6 +795,41 @@ mod helpers {
Ok(())
}
}

fn extract_params_from_body(
connector: &Connector,
) -> Result<HashSet<Parameter>, FederationError> {
let body_parameters = connector
.transport
.body
.as_ref()
.and_then(JSONSelection::extract_parameters)
.unwrap_or_default();

body_parameters
.iter()
.map(|StaticParameter { name, paths }| {
let mut parts = paths.iter();
let field = parts.next().ok_or(FederationError::internal(
"expected parameter in JSONSelection to contain a field",
))?;

match *name {
"$args" => Ok(Parameter::Argument {
argument: field,
paths: parts.copied().collect(),
}),
"$this" => Ok(Parameter::Sibling {
field,
paths: parts.copied().collect(),
}),
other => Err(FederationError::internal(format!(
"got unsupported parameter: {other}"
))),
}
})
.collect::<Result<HashSet<_>, _>>()
}
}

#[cfg(test)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ enum link__Purpose {
type Query
@join__type(graph: CONNECTORS)
{
filterUsersByEmailDomain(email: EmailAddress!): [User] @join__directive(graphs: [CONNECTORS], name: "connect", args: {source: "example", http: {GET: "/filter/users", body: "emailDomain: $args.email"}, selection: "id\nname"})
usersByCompany(company: CompanyInput!): [User] @join__directive(graphs: [CONNECTORS], name: "connect", args: {source: "example", http: {GET: "/by-company/{$args.company.name}"}, selection: "id\nname\ncompany {\n name\n catchPhrase\n bs\n}"})
user(id: ID!): User @join__directive(graphs: [CONNECTORS], name: "connect", args: {source: "example", http: {GET: "/{$args.id}"}, selection: "id\nname\nusername\nemail\naddress {\n street\n suite\n city\n zipcode\n geo {\n lat\n lng\n }\n}\nphone\nwebsite\ncompany {\n name\n catchPhrase\n bs\n email\n}", entity: true})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ subgraphs:
@source(name: "example", http: { baseURL: "http://example" })

type Query {
filterUsersByEmailDomain(email: EmailAddress!): [User]
@connect(source: "example", http: { GET: "/filter/users", body: "emailDomain: $$args.email" }, selection: """
id
name
""")

usersByCompany(company: CompanyInput!): [User]
@connect(source: "example", http: { GET: "/by-company/{$$args.company.name}" }, selection: """
id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,5 @@ type User
a: String @join__field(graph: CONNECTORS)
b: String @join__field(graph: CONNECTORS)
c: String @join__field(graph: CONNECTORS, external: true) @join__field(graph: GRAPHQL)
d: String @join__field(graph: CONNECTORS, requires: "c") @join__directive(graphs: [CONNECTORS], name: "connect", args: {source: "example", http: {GET: "/{$this.c}/d"}, selection: "$"})
d: String @join__field(graph: CONNECTORS, requires: "c") @join__directive(graphs: [CONNECTORS], name: "connect", args: {source: "example", http: {GET: "/{$this.c}/d", body: "with_b: $this.b"}, selection: "$"})
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ subgraphs:
c: String @external
d: String
@requires(fields: "c")
@connect(source: "example", http: { GET: "/{$$this.c}/d" }, selection: "$")
@connect(source: "example", http: { GET: "/{$$this.c}/d", body: "with_b: $$this.b" }, selection: "$")
}

graphql:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,103 @@ source: apollo-federation/src/sources/connect/expand/tests/mod.rs
expression: connectors.by_service_name
---
{
"connectors_Query_filterUsersByEmailDomain_0": Connector {
id: ConnectId {
label: "connectors.example http: GET /filter/users",
subgraph_name: "connectors",
source_name: Some(
"example",
),
directive: ObjectOrInterfaceFieldDirectivePosition {
field: Object(Query.filterUsersByEmailDomain),
directive_name: "connect",
directive_index: 0,
},
},
transport: HttpJsonTransport {
source_url: Some(
Url {
scheme: "http",
cannot_be_a_base: false,
username: "",
password: None,
host: Some(
Domain(
"example",
),
),
port: None,
path: "/",
query: None,
fragment: None,
},
),
connect_template: URLTemplate {
base: None,
path: [
ParameterValue {
parts: [
Text(
"filter",
),
],
},
ParameterValue {
parts: [
Text(
"users",
),
],
},
],
query: {},
},
method: Get,
headers: {},
body: Some(
Named(
SubSelection {
selections: [
Path(
Alias {
name: "emailDomain",
},
Var(
"$args",
Key(
Field(
"email",
),
Empty,
),
),
),
],
star: None,
},
),
),
},
selection: Named(
SubSelection {
selections: [
Field(
None,
"id",
None,
),
Field(
None,
"name",
None,
),
],
star: None,
},
),
config: None,
entity_resolver: None,
},
"connectors_Query_usersByCompany_0": Connector {
id: ConnectId {
label: "connectors.example http: GET /by-company/{$args.company.name!}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,30 @@ scalar join__FieldSet
scalar join__DirectiveArguments

enum join__Graph {
CONNECTORS_QUERY_FILTERUSERSBYEMAILDOMAIN_0 @join__graph(name: "connectors_Query_filterUsersByEmailDomain_0", url: "none")
CONNECTORS_QUERY_USER_0 @join__graph(name: "connectors_Query_user_0", url: "none")
CONNECTORS_QUERY_USERSBYCOMPANY_0 @join__graph(name: "connectors_Query_usersByCompany_0", url: "none")
}

scalar EmailAddress @join__type(graph: CONNECTORS_QUERY_FILTERUSERSBYEMAILDOMAIN_0) @join__type(graph: CONNECTORS_QUERY_USER_0)

type User @join__type(graph: CONNECTORS_QUERY_FILTERUSERSBYEMAILDOMAIN_0) @join__type(graph: CONNECTORS_QUERY_USER_0, key: "id") @join__type(graph: CONNECTORS_QUERY_USERSBYCOMPANY_0) {
id: ID! @join__field(graph: CONNECTORS_QUERY_FILTERUSERSBYEMAILDOMAIN_0) @join__field(graph: CONNECTORS_QUERY_USER_0) @join__field(graph: CONNECTORS_QUERY_USERSBYCOMPANY_0)
name: String @join__field(graph: CONNECTORS_QUERY_FILTERUSERSBYEMAILDOMAIN_0) @join__field(graph: CONNECTORS_QUERY_USER_0) @join__field(graph: CONNECTORS_QUERY_USERSBYCOMPANY_0)
address: Address @join__field(graph: CONNECTORS_QUERY_USER_0)
company: CompanyInfo @join__field(graph: CONNECTORS_QUERY_USER_0) @join__field(graph: CONNECTORS_QUERY_USERSBYCOMPANY_0)
email: EmailAddress @join__field(graph: CONNECTORS_QUERY_USER_0)
phone: String @join__field(graph: CONNECTORS_QUERY_USER_0)
username: String @join__field(graph: CONNECTORS_QUERY_USER_0)
website: String @join__field(graph: CONNECTORS_QUERY_USER_0)
}

type Query @join__type(graph: CONNECTORS_QUERY_FILTERUSERSBYEMAILDOMAIN_0) @join__type(graph: CONNECTORS_QUERY_USER_0) @join__type(graph: CONNECTORS_QUERY_USERSBYCOMPANY_0) {
filterUsersByEmailDomain(email: EmailAddress!): [User] @join__field(graph: CONNECTORS_QUERY_FILTERUSERSBYEMAILDOMAIN_0)
user(id: ID!): User @join__field(graph: CONNECTORS_QUERY_USER_0)
usersByCompany(company: CompanyInput!): [User] @join__field(graph: CONNECTORS_QUERY_USERSBYCOMPANY_0)
}

type AddressGeo @join__type(graph: CONNECTORS_QUERY_USER_0) {
lat: Float @join__field(graph: CONNECTORS_QUERY_USER_0)
lng: Float @join__field(graph: CONNECTORS_QUERY_USER_0)
Expand All @@ -55,31 +75,13 @@ type Address @join__type(graph: CONNECTORS_QUERY_USER_0) {
zipcode: String @join__field(graph: CONNECTORS_QUERY_USER_0)
}

scalar EmailAddress @join__type(graph: CONNECTORS_QUERY_USER_0)

type CompanyInfo @join__type(graph: CONNECTORS_QUERY_USER_0) @join__type(graph: CONNECTORS_QUERY_USERSBYCOMPANY_0) {
bs: String @join__field(graph: CONNECTORS_QUERY_USER_0) @join__field(graph: CONNECTORS_QUERY_USERSBYCOMPANY_0)
catchPhrase: String @join__field(graph: CONNECTORS_QUERY_USER_0) @join__field(graph: CONNECTORS_QUERY_USERSBYCOMPANY_0)
email: EmailAddress @join__field(graph: CONNECTORS_QUERY_USER_0)
name: String @join__field(graph: CONNECTORS_QUERY_USER_0) @join__field(graph: CONNECTORS_QUERY_USERSBYCOMPANY_0)
}

type User @join__type(graph: CONNECTORS_QUERY_USER_0, key: "id") @join__type(graph: CONNECTORS_QUERY_USERSBYCOMPANY_0) {
address: Address @join__field(graph: CONNECTORS_QUERY_USER_0)
company: CompanyInfo @join__field(graph: CONNECTORS_QUERY_USER_0) @join__field(graph: CONNECTORS_QUERY_USERSBYCOMPANY_0)
email: EmailAddress @join__field(graph: CONNECTORS_QUERY_USER_0)
id: ID! @join__field(graph: CONNECTORS_QUERY_USER_0) @join__field(graph: CONNECTORS_QUERY_USERSBYCOMPANY_0)
name: String @join__field(graph: CONNECTORS_QUERY_USER_0) @join__field(graph: CONNECTORS_QUERY_USERSBYCOMPANY_0)
phone: String @join__field(graph: CONNECTORS_QUERY_USER_0)
username: String @join__field(graph: CONNECTORS_QUERY_USER_0)
website: String @join__field(graph: CONNECTORS_QUERY_USER_0)
}

type Query @join__type(graph: CONNECTORS_QUERY_USER_0) @join__type(graph: CONNECTORS_QUERY_USERSBYCOMPANY_0) {
user(id: ID!): User @join__field(graph: CONNECTORS_QUERY_USER_0)
usersByCompany(company: CompanyInput!): [User] @join__field(graph: CONNECTORS_QUERY_USERSBYCOMPANY_0)
}

input CompanyInput @join__type(graph: CONNECTORS_QUERY_USERSBYCOMPANY_0) {
name: String!
catchPhrase: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ input CompanyInput {
scalar EmailAddress

type Query {
filterUsersByEmailDomain(email: EmailAddress!): [User]
usersByCompany(company: CompanyInput!): [User]
user(id: ID!): User
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,29 @@ expression: connectors.by_service_name
},
method: Get,
headers: {},
body: None,
body: Some(
Named(
SubSelection {
selections: [
Path(
Alias {
name: "with_b",
},
Var(
"$this",
Key(
Field(
"b",
),
Empty,
),
),
),
],
star: None,
},
),
),
},
selection: Path(
Var(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ enum join__Graph {
GRAPHQL @join__graph(name: "graphql", url: "https://graphql")
}

type User @join__type(graph: CONNECTORS_QUERY_USER_0, key: "id") @join__type(graph: CONNECTORS_QUERY_USERS_0) @join__type(graph: CONNECTORS_USER_D_1, key: "c") @join__type(graph: GRAPHQL, key: "id") {
type User @join__type(graph: CONNECTORS_QUERY_USER_0, key: "id") @join__type(graph: CONNECTORS_QUERY_USERS_0) @join__type(graph: CONNECTORS_USER_D_1, key: "b c") @join__type(graph: GRAPHQL, key: "id") {
a: String @join__field(graph: CONNECTORS_QUERY_USER_0) @join__field(graph: CONNECTORS_QUERY_USERS_0)
b: String @join__field(graph: CONNECTORS_QUERY_USER_0)
b: String @join__field(graph: CONNECTORS_QUERY_USER_0) @join__field(graph: CONNECTORS_USER_D_1)
id: ID! @join__field(graph: CONNECTORS_QUERY_USER_0) @join__field(graph: CONNECTORS_QUERY_USERS_0) @join__field(graph: GRAPHQL)
d: String @join__field(graph: CONNECTORS_USER_D_1)
c: String @join__field(graph: CONNECTORS_USER_D_1) @join__field(graph: GRAPHQL)
Expand Down
2 changes: 2 additions & 0 deletions apollo-federation/src/sources/connect/json_selection/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
mod apply_to;
mod graphql;
mod helpers;
mod parameter_extraction;
mod parser;
mod pretty;
mod selection_set;
mod visitor;

pub use apply_to::*;
pub use parameter_extraction::*;
pub use parser::*;
// Pretty code is currently only used in tests, so this cfg is to suppress the
// unused lint warning. If pretty code is needed in not test code, feel free to
Expand Down
Loading