<NME> mod.rs
<BEF> /*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::collections::BTreeMap;

use common::Diagnostic;
use common::SourceLocationKey;
use common::TextSource;
use fixture_tests::Fixture;
use graphql_cli::DiagnosticPrinter;
use schema::build_schema_from_flat_buffer;
use schema::build_schema_with_extensions;
use schema::serialize_as_flatbuffer;
    let parts: Vec<_> = fixture.content.split("%extensions%").collect();
    let result = match parts.as_slice() {
        [base] => build_schema_with_extensions::<_, &str>(&[base], &[]),
        [base, extensions] => {
            build_schema_with_extensions(&[base], &[(extensions, SourceLocationKey::generated())])
        }
        _ => panic!("Expected a single extension block"),
    };

            build_schema_with_extensions::<_, &str>(&[(base, SourceLocationKey::generated())], &[])
        }
        [base, extensions] => {
            // prepend a comment so the correct line + column number is reported for client extension
            // (since we source base and client schemas from one file)
            let nchars_base = base.chars().count() + SCHEMA_SEPARATOR.chars().count();
            assert!(nchars_base > 0);
            let prepended_extension = format!("{}\n{}", "#".repeat(nchars_base - 1), extensions);
            build_schema_with_extensions(
                &[(base, SourceLocationKey::generated())],
                &[(
                    prepended_extension,
                    SourceLocationKey::standalone(fixture.file_name),
                )],
            )
        }
        _ => panic!("Expected a single extension block"),
    };

    result
        .map(print_schema_and_flat_buffer_schema)
        .map_err(|diagnostics| diagnostics_to_sorted_string(fixture.content, &diagnostics))
}

fn print_schema_and_flat_buffer_schema(schema: SDLSchema) -> String {
    let schema = schema.unwrap_in_memory_impl();
    let bytes = serialize_as_flatbuffer(&schema);
    let fb_schema = build_schema_from_flat_buffer(&bytes).unwrap();
    let mut objects = Vec::new();
    let mut interfaces = Vec::new();
    let mut unions = Vec::new();
    let mut scalars = Vec::new();
    let mut enums = Vec::new();
    let mut input_objects = Vec::new();
    let mut fields = Vec::new();
    let mut directives = Vec::new();

    // Hydrate types
    for (key, _value) in schema.get_type_map().collect::<BTreeMap<_, _>>() {
        let type_ = fb_schema.get_type(*key).unwrap();
        // Hyderate fields
        match type_ {
            Type::Object(id) => {
                let object = fb_schema.object(id);
                for field_id in object.fields.clone() {
                    fields.push(fb_schema.field(field_id));
                }
                objects.push(object);
            }
            Type::Interface(id) => {
                let interface = fb_schema.interface(id);
                for field_id in interface.fields.clone() {
                    fields.push(fb_schema.field(field_id));
                }
                interfaces.push(interface);
            }
            Type::Union(id) => unions.push(fb_schema.union(id)),
            Type::Scalar(id) => scalars.push(fb_schema.scalar(id)),
            Type::Enum(id) => enums.push(fb_schema.enum_(id)),
            Type::InputObject(id) => input_objects.push(fb_schema.input_object(id)),
        };
    }
    // Hydrate directives
    let mut ordered_directives = schema.get_directives().collect::<Vec<_>>();
    ordered_directives.sort_by_key(|directive| directive.name);
    for directive in ordered_directives {
        directives.push(fb_schema.get_directive(directive.name).unwrap());
    }
    let fb_schema_snapshot = format!(
        r#"FB Schema {{
directives: {:#?}
enums: {:#?}
fields: {:#?}
input_objects: {:#?}
interfaces: {:#?}
objects: {:#?}
scalars: {:#?}
unions: {:#?}
}}"#,
        directives, enums, fields, input_objects, interfaces, objects, scalars, unions,
    );
    format!(
        "Text Schema:{}\n\nFlatBuffer Schema:{}",
        schema.snapshot_print(),
        fb_schema_snapshot
    )
}

// NOTE: copied from graphql-test-helpers to avoid cyclic dependency breaking Rust Analyzer
fn diagnostics_to_sorted_string(source: &str, diagnostics: &[Diagnostic]) -> String {
    let printer =
        DiagnosticPrinter::new(|_| Some(TextSource::from_whole_document(source.to_string())));
    let mut printed = diagnostics
        .iter()
        .map(|diagnostic| printer.diagnostic_to_string(diagnostic))
        .collect::<Vec<_>>();
    printed.sort();
    printed.join("\n\n")
}

<MSG> Example of using location information in schema validation error

Reviewed By: josephsavona

Differential Revision: D30973412

fbshipit-source-id: 5dca97d93f019d582fcf64ca188c2369756558b1

<DFF> @@ -18,9 +18,10 @@ pub fn transform_fixture(fixture: &Fixture<'_>) -> Result<String, String> {
     let parts: Vec<_> = fixture.content.split("%extensions%").collect();
     let result = match parts.as_slice() {
         [base] => build_schema_with_extensions::<_, &str>(&[base], &[]),
-        [base, extensions] => {
-            build_schema_with_extensions(&[base], &[(extensions, SourceLocationKey::generated())])
-        }
+        [base, extensions] => build_schema_with_extensions(
+            &[base],
+            &[(extensions, SourceLocationKey::standalone(fixture.file_name))],
+        ),
         _ => panic!("Expected a single extension block"),
     };
 

{'commit': 'diff --git a/compiler/crates/schema/src/in_memory/mod.rs b/compiler/crates/schema/src/in_memory/mod.rs\nindex 95e1077e4dfd..a0b6b6c52da8 100644\n--- a/compiler/crates/schema/src/in_memory/mod.rs\n+++ b/compiler/crates/schema/src/in_memory/mod.rs\n@@ -937,7 +937,7 @@ impl InMemorySchema {\n     fn add_definition(\n         &mut self,\n         definition: &TypeSystemDefinition,\n-        _location_key: &SourceLocationKey,\n+        location_key: &SourceLocationKey,\n         is_extension: bool,\n     ) -> DiagnosticsResult<()> {\n         match definition {\n@@ -1161,7 +1161,7 @@ impl InMemorySchema {\n                     let obj = self.objects.get(index).ok_or_else(|| {\n                         vec![Diagnostic::error(\n                             SchemaError::ExtendUndefinedType(name.value),\n-                            Location::generated(),\n+                            Location::new(location_key.clone(), name.span),\n                         )]\n                     })?;\n \n@@ -1195,7 +1195,10 @@ impl InMemorySchema {\n                     );\n                 }\n                 _ => {\n-                    return todo_add_location(SchemaError::ExtendUndefinedType(name.value));\n+                    return Err(vec![Diagnostic::error(\n+                        SchemaError::ExtendUndefinedType(name.value),\n+                        Location::new(location_key.clone(), name.span),\n+                    )]);\n                 }\n             },\n             TypeSystemDefinition::InterfaceTypeExtension(InterfaceTypeExtension {\n@@ -1209,7 +1212,7 @@ impl InMemorySchema {\n                     let interface = self.interfaces.get(index).ok_or_else(|| {\n                         vec![Diagnostic::error(\n                             SchemaError::ExtendUndefinedType(name.value),\n-                            Location::generated(),\n+                            Location::new(location_key.clone(), name.span),\n                         )]\n                     })?;\n                     let field_ids = &interface.fields;\n@@ -1232,7 +1235,10 @@ impl InMemorySchema {\n                     );\n                 }\n                 _ => {\n-                    return todo_add_location(SchemaError::ExtendUndefinedType(name.value));\n+                    return Err(vec![Diagnostic::error(\n+                        SchemaError::ExtendUndefinedType(name.value),\n+                        Location::new(location_key.clone(), name.span),\n+                    )]);\n                 }\n             },\n             TypeSystemDefinition::SchemaExtension { .. } => todo!("SchemaExtension"),\ndiff --git a/compiler/crates/schema/tests/build_schema/fixtures/extend-interface-before-define.expected b/compiler/crates/schema/tests/build_schema/fixtures/extend-interface-before-define.expected\nindex c3fc740db54e..57ecf406e569 100644\n--- a/compiler/crates/schema/tests/build_schema/fixtures/extend-interface-before-define.expected\n+++ b/compiler/crates/schema/tests/build_schema/fixtures/extend-interface-before-define.expected\n@@ -19,7 +19,8 @@ type MyType {\n ==================================== ERROR ====================================\n ✖︎ Cannot extend type \'MyType\', the type is not defined on the server schema.\n \n-  <generated>:1:1\n-    1 │ # expected-to-throw\n-      │ ^\n-    2 │\n+  <generated>:11:13\n+   10 │ \n+   11 │ extend type MyType {\n+      │             ^^^^^^\n+   12 │   will_this_crash: Int\ndiff --git a/compiler/crates/schema/tests/build_schema/fixtures/extend-object-before-define.expected b/compiler/crates/schema/tests/build_schema/fixtures/extend-object-before-define.expected\nindex dc262a69a076..222722c7c018 100644\n--- a/compiler/crates/schema/tests/build_schema/fixtures/extend-object-before-define.expected\n+++ b/compiler/crates/schema/tests/build_schema/fixtures/extend-object-before-define.expected\n@@ -22,7 +22,8 @@ type MyType {\n ==================================== ERROR ====================================\n ✖︎ Cannot extend type \'MyType\', the type is not defined on the server schema.\n \n-  <generated>:1:1\n-    1 │ # expected-to-throw\n-      │ ^\n-    2 │\n+  <generated>:14:13\n+   13 │ # -- https://spec.graphql.org/June2018/#InterfaceTypeExtension\n+   14 │ extend type MyType {\n+      │             ^^^^^^\n+   15 │   will_this_crash: Int\ndiff --git a/compiler/crates/schema/tests/build_schema/mod.rs b/compiler/crates/schema/tests/build_schema/mod.rs\nindex b2af09ff6d21..8bf7cf9a57e9 100644\n--- a/compiler/crates/schema/tests/build_schema/mod.rs\n+++ b/compiler/crates/schema/tests/build_schema/mod.rs\n@@ -18,9 +18,10 @@ pub fn transform_fixture(fixture: &Fixture<\'_>) -> Result<String, String> {\n     let parts: Vec<_> = fixture.content.split("%extensions%").collect();\n     let result = match parts.as_slice() {\n         [base] => build_schema_with_extensions::<_, &str>(&[base], &[]),\n-        [base, extensions] => {\n-            build_schema_with_extensions(&[base], &[(extensions, SourceLocationKey::generated())])\n-        }\n+        [base, extensions] => build_schema_with_extensions(\n+            &[base],\n+            &[(extensions, SourceLocationKey::standalone(fixture.file_name))],\n+        ),\n         _ => panic!("Expected a single extension block"),\n     };\n \n', 'commit_subject': 'Example of using location information in schema validation error', 'repo_name': 'facebook/relay', 'committer': {'name': 'Facebook GitHub Bot', 'email': 'f08905f3496a7b7d60e0da97307f6ad7594abc92@users.noreply.github.com', 'time_sec': '1631917678', 'tz_offset': '-420', 'date': {'seconds': '1631917678'}}, 'commit_tree': '95d616ba1afca22f57acff620af455413d40aece', 'commit_message': 'Example of using location information in schema validation error\n\nReviewed By: josephsavona\n\nDifferential Revision: D30973412\n\nfbshipit-source-id: 5dca97d93f019d582fcf64ca188c2369756558b1\n', 'language_name': 'Rust', 'license': 'mit', 'hunks': '@@ -18,9 +18,10 @@ pub fn transform_fixture(fixture: &Fixture<\'_>) -> Result<String, String> {\n     let parts: Vec<_> = fixture.content.split("%extensions%").collect();\n     let result = match parts.as_slice() {\n         [base] => build_schema_with_extensions::<_, &str>(&[base], &[]),\n-        [base, extensions] => {\n-            build_schema_with_extensions(&[base], &[(extensions, SourceLocationKey::generated())])\n-        }\n+        [base, extensions] => build_schema_with_extensions(\n+            &[base],\n+            &[(extensions, SourceLocationKey::standalone(fixture.file_name))],\n+        ),\n         _ => panic!("Expected a single extension block"),\n     };\n \n', 'addition_count': 4, 'deletion_count': 3, 'src_file': 'a/compiler/crates/schema/tests/build_schema/mod.rs', 'tgt_file': 'b/compiler/crates/schema/tests/build_schema/mod.rs', 'file_extension': '.rs', 'before_file': '/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nuse std::collections::BTreeMap;\n\nuse common::Diagnostic;\nuse common::SourceLocationKey;\nuse common::TextSource;\nuse fixture_tests::Fixture;\nuse graphql_cli::DiagnosticPrinter;\nuse schema::build_schema_from_flat_buffer;\nuse schema::build_schema_with_extensions;\nuse schema::serialize_as_flatbuffer;\n    let parts: Vec<_> = fixture.content.split("%extensions%").collect();\n    let result = match parts.as_slice() {\n        [base] => build_schema_with_extensions::<_, &str>(&[base], &[]),\n        [base, extensions] => {\n            build_schema_with_extensions(&[base], &[(extensions, SourceLocationKey::generated())])\n        }\n        _ => panic!("Expected a single extension block"),\n    };\n\n            build_schema_with_extensions::<_, &str>(&[(base, SourceLocationKey::generated())], &[])\n        }\n        [base, extensions] => {\n            // prepend a comment so the correct line + column number is reported for client extension\n            // (since we source base and client schemas from one file)\n            let nchars_base = base.chars().count() + SCHEMA_SEPARATOR.chars().count();\n            assert!(nchars_base > 0);\n            let prepended_extension = format!("{}\\n{}", "#".repeat(nchars_base - 1), extensions);\n            build_schema_with_extensions(\n                &[(base, SourceLocationKey::generated())],\n                &[(\n                    prepended_extension,\n                    SourceLocationKey::standalone(fixture.file_name),\n                )],\n            )\n        }\n        _ => panic!("Expected a single extension block"),\n    };\n\n    result\n        .map(print_schema_and_flat_buffer_schema)\n        .map_err(|diagnostics| diagnostics_to_sorted_string(fixture.content, &diagnostics))\n}\n\nfn print_schema_and_flat_buffer_schema(schema: SDLSchema) -> String {\n    let schema = schema.unwrap_in_memory_impl();\n    let bytes = serialize_as_flatbuffer(&schema);\n    let fb_schema = build_schema_from_flat_buffer(&bytes).unwrap();\n    let mut objects = Vec::new();\n    let mut interfaces = Vec::new();\n    let mut unions = Vec::new();\n    let mut scalars = Vec::new();\n    let mut enums = Vec::new();\n    let mut input_objects = Vec::new();\n    let mut fields = Vec::new();\n    let mut directives = Vec::new();\n\n    // Hydrate types\n    for (key, _value) in schema.get_type_map().collect::<BTreeMap<_, _>>() {\n        let type_ = fb_schema.get_type(*key).unwrap();\n        // Hyderate fields\n        match type_ {\n            Type::Object(id) => {\n                let object = fb_schema.object(id);\n                for field_id in object.fields.clone() {\n                    fields.push(fb_schema.field(field_id));\n                }\n                objects.push(object);\n            }\n            Type::Interface(id) => {\n                let interface = fb_schema.interface(id);\n                for field_id in interface.fields.clone() {\n                    fields.push(fb_schema.field(field_id));\n                }\n                interfaces.push(interface);\n            }\n            Type::Union(id) => unions.push(fb_schema.union(id)),\n            Type::Scalar(id) => scalars.push(fb_schema.scalar(id)),\n            Type::Enum(id) => enums.push(fb_schema.enum_(id)),\n            Type::InputObject(id) => input_objects.push(fb_schema.input_object(id)),\n        };\n    }\n    // Hydrate directives\n    let mut ordered_directives = schema.get_directives().collect::<Vec<_>>();\n    ordered_directives.sort_by_key(|directive| directive.name);\n    for directive in ordered_directives {\n        directives.push(fb_schema.get_directive(directive.name).unwrap());\n    }\n    let fb_schema_snapshot = format!(\n        r#"FB Schema {{\ndirectives: {:#?}\nenums: {:#?}\nfields: {:#?}\ninput_objects: {:#?}\ninterfaces: {:#?}\nobjects: {:#?}\nscalars: {:#?}\nunions: {:#?}\n}}"#,\n        directives, enums, fields, input_objects, interfaces, objects, scalars, unions,\n    );\n    format!(\n        "Text Schema:{}\\n\\nFlatBuffer Schema:{}",\n        schema.snapshot_print(),\n        fb_schema_snapshot\n    )\n}\n\n// NOTE: copied from graphql-test-helpers to avoid cyclic dependency breaking Rust Analyzer\nfn diagnostics_to_sorted_string(source: &str, diagnostics: &[Diagnostic]) -> String {\n    let printer =\n        DiagnosticPrinter::new(|_| Some(TextSource::from_whole_document(source.to_string())));\n    let mut printed = diagnostics\n        .iter()\n        .map(|diagnostic| printer.diagnostic_to_string(diagnostic))\n        .collect::<Vec<_>>();\n    printed.sort();\n    printed.join("\\n\\n")\n}\n', 'message': 'Example of using location information in schema validation error\n\nReviewed By: josephsavona\n\nDifferential Revision: D30973412\n\nfbshipit-source-id: 5dca97d93f019d582fcf64ca188c2369756558b1\n', 'author_name': 'Facebook GitHub Bot', 'lang': 'rs'}