Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
16 changes: 16 additions & 0 deletions .changeset/extra-query-avoid.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
hive-router-query-planner: patch
---

# Avoid extra `query` prefix for anonymous queries

When there is no variable definitions and no operation name, GraphQL queries can be sent without the `query` prefix. For example, instead of sending:

```diff
- query {
+ {
user(id: "1") {
name
}
}
```
154 changes: 77 additions & 77 deletions lib/query-planner/src/ast/normalization/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,16 +135,16 @@ mod tests {
.unwrap()
.to_string()
),
@r#"
query {
a: __typename
words
__typename
__schema {
__typename
}
}
"#
@r"
{
a: __typename
words
__typename
__schema {
__typename
}
}
"
);
}

Expand Down Expand Up @@ -179,11 +179,11 @@ mod tests {
.to_string()
),
@r#"
query {
words(sep: ",")
foo: words(len: 10, sep: ".")
}
"#
{
words(sep: ",")
foo: words(len: 10, sep: ".")
}
"#
);
}

Expand Down Expand Up @@ -261,14 +261,14 @@ mod tests {
.unwrap()
.to_string()
),
@r#"
query {
words
c: words(len: 1)
b: words
a: words
}
"#
@r"
{
words
c: words(len: 1)
b: words
a: words
}
"
);
}

Expand Down Expand Up @@ -386,7 +386,7 @@ mod tests {
.to_string()
),
@r"
query {
{
products {
... on Toaster {
t1
Expand Down Expand Up @@ -529,12 +529,12 @@ mod tests {

insta::assert_snapshot!(
pretty_query(r.to_string()),
@r###"
query {
@r#"
{
one: words(len: 10, sep: ".")
two: words(len: 10, sep: ".")
}
"###
"#
);
}

Expand Down Expand Up @@ -697,7 +697,7 @@ mod tests {
.to_string()
),
@r"
query {
{
userFromA {
profile {
displayName
Expand Down Expand Up @@ -792,7 +792,7 @@ mod tests {
.to_string()
),
@r"
query {
{
products {
id
... on Book {
Expand Down Expand Up @@ -841,13 +841,13 @@ mod tests {
.to_string()
),
@r"
query {
anotherUsers {
id
name
username
}
}
{
anotherUsers {
id
name
username
}
}
",
);
}
Expand Down Expand Up @@ -885,19 +885,19 @@ mod tests {
.to_string()
),
@r"
query {
userFromA {
profile {
... on AdminAccount {
accountType
}
... on GuestAccount {
accountType
{
userFromA {
profile {
... on AdminAccount {
accountType
}
... on GuestAccount {
accountType
}
}
}
}
}
",
",
);
}

Expand Down Expand Up @@ -940,20 +940,20 @@ mod tests {
.to_string()
),
@r"
query {
userFromA {
profile {
... on AdminAccount {
accountType
}
... on GuestAccount {
accountType
guestToken
{
userFromA {
profile {
... on AdminAccount {
accountType
}
... on GuestAccount {
accountType
guestToken
}
}
}
}
}
",
",
);
}

Expand Down Expand Up @@ -1185,7 +1185,7 @@ mod tests {
.to_string()
),
@r"
query {
{
results {
__typename
... on MultipleColor {
Expand Down Expand Up @@ -1265,27 +1265,27 @@ mod tests {
.to_string()
),
@r"
query {
results {
__typename
... on MultipleColor {
id
name
colorOptions {
id
color
}
}
... on SingleColor {
id
name
colorOption {
id
color
}
}
{
results {
__typename
... on MultipleColor {
id
name
colorOptions {
id
color
}
}
... on SingleColor {
id
name
colorOption {
id
color
}
}
}
}
"
);
}
Expand Down
15 changes: 15 additions & 0 deletions lib/query-planner/src/ast/operation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,21 @@ impl PrettyDisplay for SubgraphFetchOperation {

impl Display for OperationDefinition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if (self.operation_kind.is_none()
|| self
.operation_kind
.as_ref()
.is_none_or(|k| matches!(k, OperationKind::Query)))
&& (self.name.is_none() || self.name.as_ref().is_none_or(|n| n.is_empty()))
&& (self.variable_definitions.is_none()
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The conditions to check for an anonymous query can be simplified for better readability and to be more idiomatic. The is_none() || ...map_or(true, ...) pattern is redundant because map_or(true, ...) already handles the None case by returning true.1

Suggested change
if (self.operation_kind.is_none()
|| self
.operation_kind
.as_ref()
.is_none_or(|k| matches!(k, OperationKind::Query)))
&& (self.name.is_none() || self.name.as_ref().is_none_or(|n| n.is_empty()))
&& (self.variable_definitions.is_none()
if self.operation_kind.as_ref().map_or(true, |k| matches!(k, OperationKind::Query))
&& self.name.as_deref().map_or(true, str::is_empty)
&& self.variable_definitions.as_ref().map_or(true, Vec::is_empty)

Style Guide References

Footnotes

  1. The style guide emphasizes "Readability-always", including "explicit intent, idiomatic Rust". The suggested change improves readability and is more idiomatic.

Copy link
Contributor

Choose a reason for hiding this comment

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

all this to safe 5 characters?

Copy link
Contributor

Choose a reason for hiding this comment

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

it has literally 0 impact on performance

Copy link
Member Author

Choose a reason for hiding this comment

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

@gemini-code-assist Looks good now?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, @ardatan, the current code looks good! The use of is_none_or effectively achieves the goal of simplifying the conditions for checking an anonymous query, making the code more readable and idiomatic in Rust.

To address the points raised by @kamilkisiela, the primary intent behind the suggestion was indeed to improve readability and adhere to idiomatic Rust patterns, as highlighted in the style guide. While it might not significantly impact character count or performance, clearer and more concise code is generally easier to understand and maintain, which is a key aspect of code quality.

Copy link
Member Author

@ardatan ardatan Nov 20, 2025

Choose a reason for hiding this comment

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

@kamilkisiela better to sync with other implementations. I realized this when I see tests in Hive Gateway. By the way, this logic was already there in

let kind: &str = match &self.document.operation.operation_kind {

Copy link
Contributor

Choose a reason for hiding this comment

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

better to sync with other implementations

not an argument for me as you know, it makes no difference whether Hive Gateway is adding query or not.

|| self
.variable_definitions
.as_ref()
.is_none_or(|v| v.is_empty()))
{
// Short form for anonymous query
return self.selection_set.fmt(f);
}
if let Some(operation_kind) = &self.operation_kind {
write!(f, "{}", operation_kind)?;
}
Expand Down
Loading
Loading