Repository: apollographql/apollo-rs
Pull request #909 (2024-09-09 16:16:09)

An adopted schema extension disables implicit root operations + Prep releases
`adopt_orphan_extensions` is a non-standard mode so we don’t have a spec to precisely define its behavior, but I think this behavior is more consistent. See new test for an example.

Also prepare releases for:
* apollo-parser@0.8.2
* apollo-compiler@1.0.0-beta.22
* apollo-smitch@0.12.0 (only needed to update the pinned compiler dependency)


Patch:
diff --git a/crates/apollo-compiler/CHANGELOG.md b/crates/apollo-compiler/CHANGELOG.md
index bc84d78..a79bb55 100644
--- a/crates/apollo-compiler/CHANGELOG.md
+++ b/crates/apollo-compiler/CHANGELOG.md
@@ -17,7 +17,17 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
 ## Maintenance
 ## Documentation-->
 
-# [1.0.0-beta.21](https://crates.io/crates/apollo-compiler/1.0.0-beta.21) - 2024-08-03
+# [1.0.0-beta.22](https://crates.io/crates/apollo-compiler/1.0.0-beta.21) - 2024-09-09
+
+## Fixes
+- **Allow adopted orphan schema extensions to define root operations - [trevor-scheer], [pull/907].**
+  This is only relevant in the non-standard `adopt_orphan_extensions` mode.
+
+[trevor-scheer]: https://github.com/trevor-scheer
+[pull/907]: https://github.com/apollographql/apollo-rs/pull/907
+
+
+# [1.0.0-beta.21](https://crates.io/crates/apollo-compiler/1.0.0-beta.21) - 2024-09-03
 
 ## BREAKING
 
diff --git a/crates/apollo-compiler/Cargo.toml b/crates/apollo-compiler/Cargo.toml
index a428637..8fe14d4 100644
--- a/crates/apollo-compiler/Cargo.toml
+++ b/crates/apollo-compiler/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "apollo-compiler"
-version = "1.0.0-beta.21" # When bumping, also update README.md
+version = "1.0.0-beta.22" # When bumping, also update README.md
 authors = ["Irina Shestak <shestak.irina@gmail.com>"]
 license = "MIT OR Apache-2.0"
 repository = "https://github.com/apollographql/apollo-rs"
diff --git a/crates/apollo-compiler/README.md b/crates/apollo-compiler/README.md
index 1041808..8e6a125 100644
--- a/crates/apollo-compiler/README.md
+++ b/crates/apollo-compiler/README.md
@@ -40,7 +40,7 @@ Or add this to your `Cargo.toml` for a manual installation:
 # Just an example, change to the necessary package version.
 # Using an exact dependency is recommended for beta versions
 [dependencies]
-apollo-compiler = "=1.0.0-beta.21"
+apollo-compiler = "=1.0.0-beta.22"
 ```
 
 ## Rust versions
diff --git a/crates/apollo-compiler/src/schema/from_ast.rs b/crates/apollo-compiler/src/schema/from_ast.rs
index 30c64eb..dbe821a 100644
--- a/crates/apollo-compiler/src/schema/from_ast.rs
+++ b/crates/apollo-compiler/src/schema/from_ast.rs
@@ -273,53 +273,35 @@ impl SchemaBuilder {
             SchemaDefinitionStatus::NoneSoFar { orphan_extensions } => {
                 // This a macro rather than a closure to generate separate `static`s
                 let schema_def = schema.schema_definition.make_mut();
-                let mut has_implicit_root_operation = false;
-                'root_operation_loop: for (operation_type, root_operation) in [
-                    (OperationType::Query, &mut schema_def.query),
-                    (OperationType::Mutation, &mut schema_def.mutation),
-                    (OperationType::Subscription, &mut schema_def.subscription),
-                ] {
-                    let name = operation_type.default_type_name();
-                    // If `adopt_orphan_extensions` is enabled, we should scan
-                    // each orphan schema extension for root operations. If we
-                    // see one, we should skip adding that particular implicit
-                    // root operation since the extensions will be applied
-                    // further down.
-                    if adopt_orphan_extensions {
-                        for ext in &orphan_extensions {
-                            for node in &ext.root_operations {
-                                let current_operation_type = node.as_ref().0;
-                                if current_operation_type == operation_type {
-                                    continue 'root_operation_loop;
-                                }
-                            }
-                        }
-                    }
-
-                    if schema.types.get(&name).is_some_and(|def| def.is_object()) {
-                        *root_operation = Some(name.into());
-                        has_implicit_root_operation = true
-                    }
-                }
-
-                let apply_schema_extensions =
-                    // https://github.com/apollographql/apollo-rs/issues/682
-                    // If we have no explict `schema` definition but do have object type(s)
-                    // with a default type name for root operations,
-                    // an implicit schema definition is generated with those root operations.
-                    // That implict definition can be extended:
-                    has_implicit_root_operation ||
+                if adopt_orphan_extensions {
                     // https://github.com/apollographql/apollo-rs/pull/678
                     // In this opt-in mode we unconditionally assume
                     // an implicit schema definition to extend
-                    adopt_orphan_extensions;
-                if apply_schema_extensions {
                     for ext in &orphan_extensions {
                         schema_def.extend_ast(&mut errors, ext)
                     }
+                    if schema_def.query.is_none()
+                        && schema_def.mutation.is_none()
+                        && schema_def.subscription.is_none()
+                    {
+                        add_implicit_root_types(schema_def, &schema.types);
+                    }
                 } else {
-                    for ext in &orphan_extensions {
-                        errors.push(ext.location(), BuildError::OrphanSchemaExtension)
+                    let has_implicit_root_operation =
+                        add_implicit_root_types(schema_def, &schema.types);
+                    if has_implicit_root_operation {
+                        // https://github.com/apollographql/apollo-rs/issues/682
+                        // If we have no explict `schema` definition but do have object type(s)
+                        // with a default type name for root operations,
+                        // an implicit schema definition is generated with those root operations.
+                        // That implict definition can be extended:
+                        for ext in &orphan_extensions {
+                            schema_def.extend_ast(&mut errors, ext)
+                        }
+                    } else {
+                        for ext in &orphan_extensions {
+                            errors.push(ext.location(), BuildError::OrphanSchemaExtension)
+                        }
                     }
                 }
             }
@@ -343,6 +325,25 @@ impl SchemaBuilder {
     }
 }
 
+fn add_implicit_root_types(
+    schema_def: &mut SchemaDefinition,
+    types: &IndexMap<Name, ExtendedType>,
+) -> bool {
+    let mut has_implicit_root_operation = false;
+    for (operation_type, root_operation) in [
+        (OperationType::Query, &mut schema_def.query),
+        (OperationType::Mutation, &mut schema_def.mutation),
+        (OperationType::Subscription, &mut schema_def.subscription),
+    ] {
+        let name = operation_type.default_type_name();
+        if types.get(&name).is_some_and(|def| def.is_object()) {
+            *root_operation = Some(name.into());
+            has_implicit_root_operation = true
+        }
+    }
+    has_implicit_root_operation
+}
+
 fn adopt_type_extensions(
     errors: &mut DiagnosticList,
     type_name: &Name,
diff --git a/crates/apollo-parser/CHANGELOG.md b/crates/apollo-parser/CHANGELOG.md
index 070ad5d..674d563 100644
--- a/crates/apollo-parser/CHANGELOG.md
+++ b/crates/apollo-parser/CHANGELOG.md
@@ -16,6 +16,19 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
 ## Maintenance
 
 ## Documentation -->
+
+# [0.8.2](https://crates.io/crates/apollo-parser/0.8.2) - 2024-09-09
+
+## Fixes
+- **Allow object type definition syntax without fields - [trevor-scheer], [pull/901].**
+  It is syntactically valid for an object type to have no fields
+  when the `{}` curly braces are omitted.
+  During validation, this is also valid as long as an extension for the type
+  also exists which does include field definitions.
+
+[trevor-scheer]: https://github.com/trevor-scheer
+[pull/901]: https://github.com/apollographql/apollo-rs/pull/901
+
 # [0.8.1](https://crates.io/crates/apollo-parser/0.8.1) - 2024-08-20
 
 ## Features
diff --git a/crates/apollo-parser/Cargo.toml b/crates/apollo-parser/Cargo.toml
index 34c4b1c..677ff6b 100644
--- a/crates/apollo-parser/Cargo.toml
+++ b/crates/apollo-parser/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "apollo-parser"
-version = "0.8.1" # When bumping, also update README.md
+version = "0.8.2" # When bumping, also update README.md
 authors = ["Irina Shestak <shestak.irina@gmail.com>"]
 license = "MIT OR Apache-2.0"
 repository = "https://github.com/apollographql/apollo-rs"
diff --git a/crates/apollo-parser/README.md b/crates/apollo-parser/README.md
index 5fae791..06a6ea1 100644
--- a/crates/apollo-parser/README.md
+++ b/crates/apollo-parser/README.md
@@ -35,7 +35,7 @@ Or add this to your `Cargo.toml` for a manual installation:
 ```toml
 # Just an example, change to the necessary package version.
 [dependencies]
-apollo-parser = "0.8.1"
+apollo-parser = "0.8.2"
 ```
 
 ## Rust versions
diff --git a/crates/apollo-smith/CHANGELOG.md b/crates/apollo-smith/CHANGELOG.md
index b9beadf..8d3afc1 100644
--- a/crates/apollo-smith/CHANGELOG.md
+++ b/crates/apollo-smith/CHANGELOG.md
@@ -18,7 +18,11 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
 ## Maintenance
 
 ## Documentation -->
-# [0.11.0](https://crates.io/crates/apollo-smith/0.10.0) - 2024-09-03
+# [0.12.0](https://crates.io/crates/apollo-smith/0.12.0) - 2024-09-09
+
+- **Update apollo-compiler dependency to `=1.0.0-beta.22`**
+
+# [0.11.0](https://crates.io/crates/apollo-smith/0.11.0) - 2024-09-03
 
 - **Update apollo-compiler dependency to `=1.0.0-beta.21`**
 
diff --git a/crates/apollo-smith/Cargo.toml b/crates/apollo-smith/Cargo.toml
index 3cea8bb..d4d69fe 100644
--- a/crates/apollo-smith/Cargo.toml
+++ b/crates/apollo-smith/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "apollo-smith"
-version = "0.11.0" # When bumping, also update README.md
+version = "0.12.0" # When bumping, also update README.md
 edition = "2021"
 authors = ["Benjamin Coenen <benjamin.coenen@apollographql.com>"]
 license = "MIT OR Apache-2.0"
@@ -22,7 +22,7 @@ categories = [
 ]
 
 [dependencies]
-apollo-compiler = { path = "../apollo-compiler", version = "=1.0.0-beta.21" }
+apollo-compiler = { path = "../apollo-compiler", version = "=1.0.0-beta.22" }
 apollo-parser = { path = "../apollo-parser", version = "0.8.0" }
 arbitrary = { version = "1.3.0", features = ["derive"] }
 indexmap = "2.0.0"
diff --git a/crates/apollo-smith/README.md b/crates/apollo-smith/README.md
index 29a7db3..bc70bd7 100644
--- a/crates/apollo-smith/README.md
+++ b/crates/apollo-smith/README.md
@@ -49,7 +49,7 @@ and add `apollo-smith` to your Cargo.toml:
 ## fuzz/Cargo.toml
 
 [dependencies]
-apollo-smith = "0.11.0"
+apollo-smith = "0.12.0"
 ```
 
 It can then be used in a `fuzz_target` along with the [`arbitrary`] crate,


Test patch:
diff --git a/crates/apollo-compiler/tests/extensions.rs b/crates/apollo-compiler/tests/extensions.rs
index d35ad17..cf0ef1d 100644
--- a/crates/apollo-compiler/tests/extensions.rs
+++ b/crates/apollo-compiler/tests/extensions.rs
@@ -92,6 +92,43 @@ fn test_invalid_orphan_extensions_schema_def_with_duplicate_root_operation() {
     assert!(err.contains("duplicate definitions for the `query` root operation type"));
 }
 
+#[test]
+fn test_orphan_schema_extension_with_root_type_disables_implicit_root_types() {
+    let input = r#"
+        extend schema { query: Query }
+        type Query { viruses: [Virus] }
+        type Virus { mutations: [Mutation] }
+        type Mutation { something: String }
+    "#;
+
+    let schema = Schema::builder()
+        .adopt_orphan_extensions()
+        .parse(input, "schema.graphql")
+        .build()
+        .unwrap();
+
+    assert!(schema.schema_definition.mutation.is_none());
+    schema.validate().unwrap();
+}
+
+#[test]
+fn test_orphan_schema_extension_without_root_type_enables_implicit_root_types() {
+    let input = r#"
+        directive @something on SCHEMA
+        extend schema @something
+        type Query { field: Int }
+    "#;
+
+    let schema = Schema::builder()
+        .adopt_orphan_extensions()
+        .parse(input, "schema.graphql")
+        .build()
+        .unwrap();
+
+    assert!(schema.schema_definition.query.is_some());
+    schema.validate().unwrap();
+}
+
 #[test]
 fn test_orphan_extensions_kind_mismatch() {
     let input = r#"