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

feat: add enableFederation flag to server #2919

Merged
merged 4 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
5 changes: 5 additions & 0 deletions generated/.tailcallrc.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,11 @@ directive @server(
introducing latency and complicating debugging. Use judiciously. @default `false`.
"""
batchRequests: Boolean
"""
`enableFederation` enables functionality to Tailcall server to act as a federation
subgraph.
"""
enableFederation: Boolean
enableJIT: Boolean
"""
`globalResponseTimeout` sets the maximum query duration before termination, acting
Expand Down
17 changes: 17 additions & 0 deletions generated/.tailcallrc.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1046,6 +1046,23 @@
"null"
]
},
<<<<<<< HEAD
"dedupe": {
"description": "Enables deduplication of IO operations to enhance performance.\n\nThis flag prevents duplicate IO requests from being executed concurrently, reducing resource load. Caution: May lead to issues with APIs that expect unique results for identical inputs, such as nonce-based APIs.",
"type": [
"boolean",
"null"
]
},
"enableFederation": {
"description": "`enableFederation` enables functionality to Tailcall server to act as a federation subgraph.",
"type": [
"boolean",
"null"
]
},
=======
>>>>>>> main
"enableJIT": {
"type": [
"boolean",
Expand Down
5 changes: 5 additions & 0 deletions src/core/config/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ pub struct Server {
/// and operations. @default `true`.
pub introspection: Option<bool>,

/// `enableFederation` enables functionality to Tailcall server to act
/// as a federation subgraph.
#[serde(default, skip_serializing_if = "is_default")]
pub enable_federation: Option<bool>,

#[serde(default, skip_serializing_if = "is_default")]
/// `pipelineFlush` allows to control flushing behavior of the server
/// pipeline.
Expand Down
97 changes: 55 additions & 42 deletions src/core/config/transformer/subgraph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ impl Transform for Subgraph {
type Error = String;

fn transform(&self, mut config: Self::Value) -> Valid<Self::Value, Self::Error> {
let enable_federation = config.server.enable_federation;
tusharmath marked this conversation as resolved.
Show resolved Hide resolved

if matches!(enable_federation, Some(false)) {
// if federation is disabled explicitly don't process the config
return Valid::succeed(config);
}

let mut resolver_by_type = BTreeMap::new();

let valid = Valid::from_iter(config.types.iter_mut(), |(type_name, ty)| {
Expand All @@ -55,26 +62,12 @@ impl Transform for Subgraph {
return valid.map_to(config);
}

if resolver_by_type.is_empty() {
if resolver_by_type.is_empty() && enable_federation.is_none() {
// if federation is not explicitly enabled and there is no entity
// resolvers return config as is
return Valid::succeed(config);
}

let entity_union = Union {
types: resolver_by_type.keys().cloned().collect(),
..Default::default()
};

let entity_resolver = config::EntityResolver { resolver_by_type };

// union that wraps any possible types for entities
config
.unions
.insert(UNION_ENTITIES_NAME.to_owned(), entity_union);
// any scalar for argument `representations`
config
.types
.insert(ENTITIES_TYPE_NAME.to_owned(), config::Type::default());

let service_field = Field { type_of: "String".to_string().into(), ..Default::default() };

let service_type = config::Type {
Expand All @@ -87,38 +80,15 @@ impl Transform for Subgraph {
.types
.insert(SERVICE_TYPE_NAME.to_owned(), service_type);

let query_type = match config.schema.query.as_ref() {
let query_type_name = match config.schema.query.as_ref() {
Some(name) => name,
None => {
config.schema.query = Some("Query".to_string());
"Query"
}
};

let query_type = config.types.entry(query_type.to_owned()).or_default();

let arg = Arg {
type_of: Type::from(ENTITIES_TYPE_NAME.to_string())
.into_required()
.into_list()
.into_required(),
..Default::default()
};

query_type.fields.insert(
ENTITIES_FIELD_NAME.to_string(),
Field {
type_of: Type::from(UNION_ENTITIES_NAME.to_owned())
.into_list()
.into_required(),
args: [(ENTITIES_ARG_NAME.to_owned(), arg)].into_iter().collect(),
doc: Some("Apollo federation Query._entities resolver".to_string()),
resolver: Some(Resolver::ApolloFederation(
ApolloFederation::EntityResolver(entity_resolver),
)),
..Default::default()
},
);
let query_type = config.types.entry(query_type_name.to_owned()).or_default();

query_type.fields.insert(
SERVICE_FIELD_NAME.to_string(),
Expand All @@ -130,6 +100,49 @@ impl Transform for Subgraph {
},
);

if !resolver_by_type.is_empty() {
let entity_union = Union {
types: resolver_by_type.keys().cloned().collect(),
..Default::default()
};

let entity_resolver = config::EntityResolver { resolver_by_type };

// union that wraps any possible types for entities
config
.unions
.insert(UNION_ENTITIES_NAME.to_owned(), entity_union);
// any scalar for argument `representations`
config
.types
.insert(ENTITIES_TYPE_NAME.to_owned(), config::Type::default());

let query_type = config.types.entry(query_type_name.to_owned()).or_default();

let arg = Arg {
type_of: Type::from(ENTITIES_TYPE_NAME.to_string())
.into_required()
.into_list()
.into_required(),
..Default::default()
};

query_type.fields.insert(
ENTITIES_FIELD_NAME.to_string(),
Field {
type_of: Type::from(UNION_ENTITIES_NAME.to_owned())
.into_list()
.into_required(),
args: [(ENTITIES_ARG_NAME.to_owned(), arg)].into_iter().collect(),
doc: Some("Apollo federation Query._entities resolver".to_string()),
resolver: Some(Resolver::ApolloFederation(
ApolloFederation::EntityResolver(entity_resolver),
)),
..Default::default()
},
);
}

Valid::succeed(config)
}
}
Expand Down

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion tests/core/snapshots/apollo-federation-entities.md_1.snap

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions tests/core/snapshots/federation-subgraph-force-disabled.md_0.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
source: tests/core/spec.rs
expression: response
---
{
"status": 200,
"headers": {
"content-type": "application/json"
},
"body": {
"data": {}
}
}
13 changes: 13 additions & 0 deletions tests/core/snapshots/federation-subgraph-force-disabled.md_1.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
source: tests/core/spec.rs
expression: response
---
{
"status": 200,
"headers": {
"content-type": "application/json"
},
"body": {
"data": {}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
source: tests/core/spec.rs
expression: formatted
---
scalar Bytes

scalar Date

scalar DateTime

scalar Email

scalar Empty

scalar Int128

scalar Int16

scalar Int32

scalar Int64

scalar Int8

scalar JSON

scalar PhoneNumber

type Query {
user(id: Int!): User
}

scalar UInt128

scalar UInt16

scalar UInt32

scalar UInt64

scalar UInt8

scalar Url

type User {
id: Int!
name: String!
}

schema {
query: Query
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
source: tests/core/spec.rs
expression: formatter
---
schema
@server(enableFederation: false, port: 8000)
@upstream(baseURL: "http://jsonplaceholder.typicode.com", batch: {delay: 100, headers: []}, httpCache: 42) {
query: Query
}

type Post @expr(body: {id: "{{.value.id}}", title: "post-title-{{.value.id}}"}) {
id: Int!
title: String!
}

type Query {
user(id: Int!): User @http(path: "/users/{{.args.id}}")
}

type User @call(steps: [{query: "user", args: {id: "{{.value.id}}"}}]) {
id: Int!
name: String!
}
13 changes: 13 additions & 0 deletions tests/core/snapshots/federation-subgraph-force-enabled.md_0.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
source: tests/core/spec.rs
expression: response
---
{
"status": 200,
"headers": {
"content-type": "application/json"
},
"body": {
"data": {}
}
}
17 changes: 17 additions & 0 deletions tests/core/snapshots/federation-subgraph-force-enabled.md_1.snap

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
source: tests/core/spec.rs
expression: formatted
---
scalar Bytes

scalar Date

scalar DateTime

scalar Email

scalar Empty

scalar Int128

scalar Int16

scalar Int32

scalar Int64

scalar Int8

scalar JSON

scalar PhoneNumber

type Query {
"""
Apollo federation Query._service resolver
"""
_service: _Service!
user(id: Int!): User
}

scalar UInt128

scalar UInt16

scalar UInt32

scalar UInt64

scalar UInt8

scalar Url

type User {
id: Int!
name: String!
}

type _Service {
sdl: String
}

schema {
query: Query
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
source: tests/core/spec.rs
expression: formatter
---
schema
@server(enableFederation: true, port: 8000)
@upstream(baseURL: "http://jsonplaceholder.typicode.com", batch: {delay: 100, headers: []}, httpCache: 42) {
query: Query
}

type Post {
id: Int!
title: String!
}

type Query {
"""
Apollo federation Query._service resolver
"""
_service: _Service!
user(id: Int!): User @http(path: "/users/{{.args.id}}")
}

type User {
id: Int!
name: String!
}

type _Service {
sdl: String
}
Loading
Loading