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
22 changes: 12 additions & 10 deletions generated/.tailcallrc.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ directive @grpc(
"""
baseURL: String
"""
The key path in the response which should be used to group multiple requests. For
instance `["news","id"]`. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).
The `batchKey` dictates the path Tailcall will follow to group the returned items
from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).
"""
batchKey: [String!]
"""
Expand Down Expand Up @@ -137,8 +137,8 @@ directive @http(
"""
baseURL: String
"""
The `batchKey` parameter groups multiple data requests into a single call. For more
details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).
The `batchKey` dictates the path Tailcall will follow to group the returned items
from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).
"""
batchKey: [String!]
"""
Expand Down Expand Up @@ -185,7 +185,8 @@ directive @http(
"""
This represents the query parameters of your API call. You can pass it as a static
object or use Mustache template for dynamic parameters. These parameters will be
added to the URL.
added to the URL. When `batchKey` is present Tailcall uses the first query parameter
as the key for the groupBy operator.
"""
query: [KeyValue]
) on FIELD_DEFINITION
Expand Down Expand Up @@ -745,8 +746,8 @@ input Grpc {
"""
baseURL: String
"""
The key path in the response which should be used to group multiple requests. For
instance `["news","id"]`. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).
The `batchKey` dictates the path Tailcall will follow to group the returned items
from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).
"""
batchKey: [String!]
"""
Expand Down Expand Up @@ -782,8 +783,8 @@ input Http {
"""
baseURL: String
"""
The `batchKey` parameter groups multiple data requests into a single call. For more
details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).
The `batchKey` dictates the path Tailcall will follow to group the returned items
from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).
"""
batchKey: [String!]
"""
Expand Down Expand Up @@ -830,7 +831,8 @@ input Http {
"""
This represents the query parameters of your API call. You can pass it as a static
object or use Mustache template for dynamic parameters. These parameters will be
added to the URL.
added to the URL. When `batchKey` is present Tailcall uses the first query parameter
as the key for the groupBy operator.
"""
query: [KeyValue]
}
Expand Down
6 changes: 3 additions & 3 deletions generated/.tailcallrc.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,7 @@
]
},
"batchKey": {
"description": "The key path in the response which should be used to group multiple requests. For instance `[\"news\",\"id\"]`. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).",
"description": "The `batchKey` dictates the path Tailcall will follow to group the returned items from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).",
"type": "array",
"items": {
"type": "string"
Expand Down Expand Up @@ -687,7 +687,7 @@
]
},
"batchKey": {
"description": "The `batchKey` parameter groups multiple data requests into a single call. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).",
"description": "The `batchKey` dictates the path Tailcall will follow to group the returned items from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).",
"type": "array",
"items": {
"type": "string"
Expand Down Expand Up @@ -757,7 +757,7 @@
"type": "string"
},
"query": {
"description": "This represents the query parameters of your API call. You can pass it as a static object or use Mustache template for dynamic parameters. These parameters will be added to the URL.",
"description": "This represents the query parameters of your API call. You can pass it as a static object or use Mustache template for dynamic parameters. These parameters will be added to the URL. When `batchKey` is present Tailcall uses the first query parameter as the key for the groupBy operator.",
"type": "array",
"items": {
"$ref": "#/definitions/KeyValue"
Expand Down
8 changes: 4 additions & 4 deletions src/core/blueprint/operators/grpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,10 +180,10 @@ pub fn compile_grpc(inputs: CompileGrpc) -> Valid<IR, String> {
.and_then(|(operation, url, headers, body)| {
let validation = if validate_with_schema {
let field_schema = json_schema_from_field(config_module, field);
if grpc.group_by.is_empty() {
if grpc.batch_key.is_empty() {
validate_schema(field_schema, &operation, field.name()).unit()
} else {
validate_group_by(&field_schema, &operation, grpc.group_by.clone()).unit()
validate_group_by(&field_schema, &operation, grpc.batch_key.clone()).unit()
}
} else {
Valid::succeed(())
Expand All @@ -198,10 +198,10 @@ pub fn compile_grpc(inputs: CompileGrpc) -> Valid<IR, String> {
body,
operation_type: operation_type.clone(),
};
if !grpc.group_by.is_empty() {
if !grpc.batch_key.is_empty() {
IR::IO(IO::Grpc {
req_template,
group_by: Some(GroupBy::new(grpc.group_by.clone())),
group_by: Some(GroupBy::new(grpc.batch_key.clone(), None)),
dl_id: None,
})
} else {
Expand Down
9 changes: 5 additions & 4 deletions src/core/blueprint/operators/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ pub fn compile_http(
http: &config::Http,
) -> Valid<IR, String> {
Valid::<(), String>::fail("GroupBy is only supported for GET requests".to_string())
.when(|| !http.group_by.is_empty() && http.method != Method::GET)
.when(|| !http.batch_key.is_empty() && http.method != Method::GET)
.and(
Valid::<(), String>::fail(
"GroupBy can only be applied if batching is enabled".to_string(),
)
.when(|| {
(config_module.upstream.get_delay() < 1
|| config_module.upstream.get_max_size() < 1)
&& !http.group_by.is_empty()
&& !http.batch_key.is_empty()
}),
)
.and(Valid::from_option(
Expand Down Expand Up @@ -61,10 +61,11 @@ pub fn compile_http(
.or(config_module.upstream.on_request.clone())
.map(|on_request| HttpFilter { on_request });

if !http.group_by.is_empty() && http.method == Method::GET {
if !http.batch_key.is_empty() && http.method == Method::GET {
let key = http.query.first().map(|query| query.key.clone());
IR::IO(IO::Http {
req_template,
group_by: Some(GroupBy::new(http.group_by.clone())),
group_by: Some(GroupBy::new(http.batch_key.clone(), key)),
dl_id: None,
http_filter,
})
Expand Down
18 changes: 10 additions & 8 deletions src/core/config/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,12 +357,12 @@ impl Field {
pub fn has_batched_resolver(&self) -> bool {
self.http
.as_ref()
.is_some_and(|http| !http.group_by.is_empty())
.is_some_and(|http| !http.batch_key.is_empty())
|| self.graphql.as_ref().is_some_and(|graphql| graphql.batch)
|| self
.grpc
.as_ref()
.is_some_and(|grpc| !grpc.group_by.is_empty())
.is_some_and(|grpc| !grpc.batch_key.is_empty())
}
pub fn into_list(mut self) -> Self {
self.list = true;
Expand Down Expand Up @@ -554,8 +554,8 @@ pub struct Http {
pub encoding: Encoding,

#[serde(rename = "batchKey", default, skip_serializing_if = "is_default")]
/// The `batchKey` parameter groups multiple data requests into a single call. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).
pub group_by: Vec<String>,
/// The `batchKey` dictates the path Tailcall will follow to group the returned items from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).
pub batch_key: Vec<String>,

#[serde(default, skip_serializing_if = "is_default")]
/// The `headers` parameter allows you to customize the headers of the HTTP
Expand Down Expand Up @@ -589,6 +589,8 @@ pub struct Http {
/// This represents the query parameters of your API call. You can pass it
/// as a static object or use Mustache template for dynamic parameters.
/// These parameters will be added to the URL.
/// When `batchKey` is present Tailcall uses the first query parameter as
/// the key for the groupBy operator.
pub query: Vec<KeyValue>,
}

Expand Down Expand Up @@ -667,8 +669,8 @@ pub struct Grpc {
/// parameters will be added in the body in `protobuf` format.
pub body: Option<Value>,
#[serde(rename = "batchKey", default, skip_serializing_if = "is_default")]
/// The key path in the response which should be used to group multiple requests. For instance `["news","id"]`. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).
pub group_by: Vec<String>,
/// The `batchKey` dictates the path Tailcall will follow to group the returned items from the batch request. For more details please refer out [n + 1 guide](https://tailcall.run/docs/guides/n+1#solving-using-batching).
pub batch_key: Vec<String>,
#[serde(default, skip_serializing_if = "is_default")]
/// The `headers` parameter allows you to customize the headers of the HTTP
/// request made by the `@grpc` operator. It is used by specifying a
Expand Down Expand Up @@ -1087,12 +1089,12 @@ mod tests {
let f1 = Field { ..Default::default() };

let f2 = Field {
http: Some(Http { group_by: vec!["id".to_string()], ..Default::default() }),
http: Some(Http { batch_key: vec!["id".to_string()], ..Default::default() }),
..Default::default()
};

let f3 = Field {
http: Some(Http { group_by: vec![], ..Default::default() }),
http: Some(Http { batch_key: vec![], ..Default::default() }),
..Default::default()
};

Expand Down
18 changes: 14 additions & 4 deletions src/core/config/group_by.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ use crate::core::is_default;
pub struct GroupBy {
#[serde(default, skip_serializing_if = "is_default")]
path: Vec<String>,
#[serde(default, skip_serializing_if = "is_default")]
key: Option<String>,
}

impl GroupBy {
pub fn new(path: Vec<String>) -> Self {
Self { path }
pub fn new(path: Vec<String>, key: Option<String>) -> Self {
Self { path, key }
}

pub fn path(&self) -> Vec<String> {
Expand All @@ -22,14 +24,22 @@ impl GroupBy {
}

pub fn key(&self) -> &str {
self.path.last().map(|a| a.as_str()).unwrap_or(ID)
match &self.key {
Some(value) => value,
None => {
if self.path.is_empty() {
return ID;
}
self.path.last().unwrap()
}
}
}
}

const ID: &str = "id";

impl Default for GroupBy {
fn default() -> Self {
Self { path: vec![ID.to_string()] }
Self { path: vec![ID.to_string()], key: None }
}
}
2 changes: 1 addition & 1 deletion src/core/config/n_plus_one.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ mod tests {
Field::default()
.type_of("F2".to_string())
.into_list()
.http(Http { group_by: vec!["id".into()], ..Default::default() }),
.http(Http { batch_key: vec!["id".into()], ..Default::default() }),
)]),
),
(
Expand Down
2 changes: 1 addition & 1 deletion src/core/generator/from_proto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ impl Context {
cfg_field.grpc = Some(Grpc {
base_url: None,
body,
group_by: vec![],
batch_key: vec![],
headers: vec![],
method: field_name.id(),
});
Expand Down
2 changes: 1 addition & 1 deletion src/core/http/data_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ impl Loader<DataLoaderRequest> for HttpDataLoader {
let url = request.url();
let pairs: Vec<_> = url
.query_pairs()
.filter(|(key, _)| group_by.path().contains(&key.to_string()))
.filter(|(key, _)| group_by.key().eq(&key.to_string()))
.collect();
first_url.query_pairs_mut().extend_pairs(pairs);
}
Expand Down
59 changes: 59 additions & 0 deletions tests/core/snapshots/test-http-batchKey.md_0.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
source: tests/core/spec.rs
expression: response
---
{
"status": 200,
"headers": {
"content-type": "application/json"
},
"body": {
"data": {
"foos": {
"foos": [
{
"id": "foo_1",
"fooName": "foo_name_1",
"barId": "bar_1",
"bars": [
{
"id": "bar_1",
"barName": "bar_name_1"
},
{
"id": "bar_1",
"barName": "bar_name_1"
}
]
},
{
"id": "foo_2",
"fooName": "foo_name_2",
"barId": "bar_1",
"bars": [
{
"id": "bar_1",
"barName": "bar_name_1"
},
{
"id": "bar_1",
"barName": "bar_name_1"
}
]
},
{
"id": "foo_3",
"fooName": "foo_name_3",
"barId": "bar_2",
"bars": [
{
"id": "bar_2",
"barName": "bar_name_2"
}
]
}
]
}
}
}
}
61 changes: 61 additions & 0 deletions tests/core/snapshots/test-http-batchKey.md_client.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
source: tests/core/spec.rs
expression: formatted
---
type Bar {
barName: String!
id: ID!
}

scalar Bytes

scalar Date

scalar Email

scalar Empty

type Foo {
barId: String!
bars: [Bar!]!
fooName: String!
id: ID!
}

type FooResponse {
foos: [Foo!]!
}

scalar Int128

scalar Int16

scalar Int32

scalar Int64

scalar Int8

scalar JSON

scalar PhoneNumber

type Query {
foos: FooResponse
}

scalar UInt128

scalar UInt16

scalar UInt32

scalar UInt64

scalar UInt8

scalar Url

schema {
query: Query
}
Loading