#![doc = include_str!("../README.md")]
pub mod database;
pub mod diagnostics;
#[cfg(test)]
mod tests;
pub mod validation;
use std::{path::Path, sync::Arc};
use salsa::ParallelDatabase;
use validation::ValidationDatabase;
pub use database::{hir, AstDatabase, FileId, HirDatabase, InputDatabase, RootDatabase, Source};
pub use diagnostics::ApolloDiagnostic;
pub struct ApolloCompiler {
    pub db: RootDatabase,
}
pub type Snapshot = salsa::Snapshot<RootDatabase>;
#[allow(clippy::new_without_default)]
impl ApolloCompiler {
    pub fn new() -> Self {
        let mut db = RootDatabase::default();
        db.set_recursion_limit(None);
        db.set_token_limit(None);
        db.set_type_system_hir_input(None);
        db.set_source_files(vec![]);
        Self { db }
    }
    pub fn recursion_limit(mut self, limit: usize) -> Self {
        if !self.db.source_files().is_empty() {
            panic!(
                "There are already parsed files in the compiler. \
                 Setting recursion limit after files are parsed is not supported."
            );
        }
        self.db.set_recursion_limit(Some(limit));
        self
    }
    pub fn token_limit(mut self, limit: usize) -> Self {
        if !self.db.source_files().is_empty() {
            panic!(
                "There are already parsed files in the compiler. \
                 Setting token limit after files are parsed is not supported."
            );
        }
        self.db.set_token_limit(Some(limit));
        self
    }
    pub fn set_type_system_hir(&mut self, schema: Arc<hir::TypeSystem>) {
        if !self.db.type_definition_files().is_empty() {
            panic!(
                "Having both string inputs and pre-computed inputs \
                 for type system definitions is not supported"
            )
        }
        self.db.set_type_system_hir_input(Some(schema))
    }
    fn add_input(&mut self, source: Source) -> FileId {
        let file_id = FileId::new();
        let mut sources = self.db.source_files();
        sources.push(file_id);
        self.db.set_input(file_id, source);
        self.db.set_source_files(sources);
        file_id
    }
    fn add_implicit_types(&mut self) {
        let f_name = "built_in_types.graphql";
        if self.db.source_file(f_name.into()).is_none() {
            let file_id = 0.into();
            let mut sources = self.db.source_files();
            sources.push(file_id);
            let implicit_tys = include_str!("built_in_types.graphql");
            self.db
                .set_input(file_id, Source::built_in(f_name.into(), implicit_tys));
            self.db.set_source_files(sources);
        }
    }
    pub fn add_document(&mut self, input: &str, path: impl AsRef<Path>) -> FileId {
        if self.db.type_system_hir_input().is_some() {
            panic!(
                "Having both string inputs and pre-computed inputs \
                 for type system definitions is not supported"
            )
        }
        let filename = path.as_ref().to_owned();
        self.add_implicit_types();
        self.add_input(Source::document(filename, input))
    }
    pub fn add_type_system(&mut self, input: &str, path: impl AsRef<Path>) -> FileId {
        if self.db.type_system_hir_input().is_some() {
            panic!(
                "Having both string inputs and pre-computed inputs \
                 for type system definitions is not supported"
            )
        }
        let filename = path.as_ref().to_owned();
        self.add_implicit_types();
        self.add_input(Source::schema(filename, input))
    }
    pub fn add_executable(&mut self, input: &str, path: impl AsRef<Path>) -> FileId {
        let filename = path.as_ref().to_owned();
        self.add_input(Source::executable(filename, input))
    }
    pub fn update_document(&mut self, file_id: FileId, input: &str) {
        let document = self.db.input(file_id);
        self.db.set_input(
            file_id,
            Source::document(document.filename().to_owned(), input),
        )
    }
    pub fn update_type_system(&mut self, file_id: FileId, input: &str) {
        let schema = self.db.input(file_id);
        self.db
            .set_input(file_id, Source::schema(schema.filename().to_owned(), input))
    }
    pub fn update_executable(&mut self, file_id: FileId, input: &str) {
        let executable = self.db.input(file_id);
        self.db.set_input(
            file_id,
            Source::executable(executable.filename().to_owned(), input),
        )
    }
    pub fn snapshot(&self) -> Snapshot {
        self.db.snapshot()
    }
    pub fn validate(&self) -> Vec<ApolloDiagnostic> {
        self.db.validate()
    }
}
#[cfg(test)]
mod test {
    use std::collections::HashMap;
    use super::*;
    use crate::hir::TypeDefinition;
    #[test]
    fn it_creates_compiler_from_multiple_sources() {
        let schema = r#"
type Query {
  name: String
  price: Int
  dimensions: Int
  size: Int
  weight: Int
}"#;
        let query = r#"
query ExampleQuery {
  name
  price
  dimensions
  size
  weight
}
      "#;
        let mut compiler = ApolloCompiler::new();
        compiler.add_document(schema, "schema.graphql");
        compiler.add_executable(query, "query.graphql");
    }
    #[test]
    fn it_accesses_operation_definition_parts() {
        let input = r#"
query ExampleQuery($definedVariable: Int, $definedVariable2: Int) {
  topProducts(first: $definedVariable) {
    type
  }
  customer { ... vipCustomer }
}
fragment vipCustomer on User {
  id
  name
  profilePic(size: $definedVariable2)
}
type Query {
  topProducts(first: Int): Product
  customer: User
}
type Product {
  type: String
  price(setPrice: Int): Int
}
type User {
  id: ID
  name: String
  profilePic(size: Int): URL
}
scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
"#;
        let mut compiler = ApolloCompiler::new();
        let document_id = compiler.add_document(input, "document.graphql");
        let diagnostics = compiler.validate();
        for diagnostic in &diagnostics {
            println!("{diagnostic}");
        }
        assert!(diagnostics.is_empty());
        let operations = compiler.db.operations(document_id);
        let operation_names: Vec<_> = operations.iter().filter_map(|op| op.name()).collect();
        assert_eq!(["ExampleQuery"], operation_names.as_slice());
        let fragments = compiler.db.fragments(document_id);
        let fragment_names: Vec<_> = fragments.keys().map(|name| &**name).collect();
        assert_eq!(["vipCustomer"], fragment_names.as_slice());
        let operation_variables: Vec<String> = match operations
            .iter()
            .find(|op| op.name() == Some("ExampleQuery"))
        {
            Some(op) => op
                .variables()
                .iter()
                .map(|var| var.name().to_string())
                .collect(),
            None => Vec::new(),
        };
        assert_eq!(
            ["definedVariable", "definedVariable2"],
            operation_variables.as_slice()
        );
    }
    #[test]
    fn it_accesses_fields() {
        let input = r#"
query ExampleQuery {
  name
  price
  dimensions
  size
  weight
}
type Query {
  name: String
  price: Int
  dimensions: Int
  size: Int
  weight: Int
}
"#;
        let mut compiler = ApolloCompiler::new();
        let document_id = compiler.add_document(input, "document.graphql");
        let diagnostics = compiler.validate();
        for diagnostic in &diagnostics {
            println!("{diagnostic}");
        }
        assert!(diagnostics.is_empty());
        let operations = compiler.db.operations(document_id);
        let fields = operations
            .iter()
            .find(|op| op.name() == Some("ExampleQuery"))
            .unwrap()
            .fields(&compiler.db);
        let field_names: Vec<&str> = fields.iter().map(|f| f.name()).collect();
        assert_eq!(
            field_names,
            ["name", "price", "dimensions", "size", "weight"]
        );
    }
    #[test]
    fn it_accesses_inline_fragment_field_types() {
        let input = r#"
query ExampleQuery {
  interface {
    a
    ... on Concrete {
      b
    }
  }
  union {
    ... on Concrete {
      a
      b
    }
  }
}
type Query {
  interface: Interface
  union: Union
}
interface Interface {
  a: String
}
type Concrete implements Interface {
  a: String
  b: Int
  c: Boolean
}
union Union = Concrete
"#;
        let mut compiler = ApolloCompiler::new();
        let document_id = compiler.add_document(input, "document.graphql");
        let diagnostics = compiler.validate();
        assert!(diagnostics.is_empty());
        let operations = compiler.db.operations(document_id);
        let fields = operations
            .iter()
            .find(|op| op.name() == Some("ExampleQuery"))
            .unwrap()
            .fields(&compiler.db);
        let interface_field = fields.iter().find(|f| f.name() == "interface").unwrap();
        let interface_fields = interface_field.selection_set().fields();
        let interface_selection_fields_types: HashMap<_, _> = interface_fields
            .iter()
            .map(|f| (f.name(), f.ty(&compiler.db).map(|f| f.name())))
            .collect();
        assert_eq!(
            interface_selection_fields_types,
            HashMap::from([("a", Some("String".to_string()))])
        );
        let inline_fragments: Vec<_> = interface_field.selection_set().inline_fragments();
        assert_eq!(inline_fragments.len(), 1);
        let inline_fragment = inline_fragments.first().unwrap();
        assert_eq!(inline_fragment.type_condition(), Some("Concrete"));
        let inline_fragment_fields = inline_fragment.selection_set().fields();
        let inline_fragment_fields_types: HashMap<_, _> = inline_fragment_fields
            .iter()
            .map(|f| (f.name(), f.ty(&compiler.db).map(|ty| ty.name())))
            .collect();
        assert_eq!(
            inline_fragment_fields_types,
            HashMap::from([("b", Some("Int".to_string()))])
        );
        let union_field = fields.iter().find(|f| f.name() == "union").unwrap();
        let union_inline_fragments: Vec<_> = union_field.selection_set().inline_fragments();
        assert_eq!(union_inline_fragments.len(), 1);
        let union_inline_fragment = union_inline_fragments.first().unwrap();
        assert_eq!(union_inline_fragment.type_condition(), Some("Concrete"));
        let union_inline_fragment_fields = union_inline_fragment.selection_set().fields();
        let union_inline_fragment_field_types: HashMap<_, _> = union_inline_fragment_fields
            .iter()
            .map(|f| (f.name(), f.ty(&compiler.db).map(|ty| ty.name())))
            .collect();
        assert_eq!(
            union_inline_fragment_field_types,
            HashMap::from([
                ("a", Some("String".to_string())),
                ("b", Some("Int".to_string())),
            ])
        );
    }
    #[test]
    fn it_accesses_field_definitions_from_operation_definition() {
        let input = r#"
query getProduct {
  size
  topProducts {
    name
    inStock
  }
}
type Query {
  topProducts: Product
  name: String
  size: Int
}
type Product {
  inStock: Boolean @join__field(graph: INVENTORY)
  name: String @join__field(graph: PRODUCTS)
  price: Int
  shippingEstimate: Int
  upc: String!
  weight: Int
}
directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet) on FIELD_DEFINITION
"#;
        let mut compiler = ApolloCompiler::new();
        let document_id = compiler.add_document(input, "document.graphql");
        let diagnostics = compiler.validate();
        for diagnostic in &diagnostics {
            println!("{diagnostic}");
        }
        assert!(diagnostics.is_empty());
        let operations = compiler.db.operations(document_id);
        let get_product_op = operations
            .iter()
            .find(|op| op.name() == Some("getProduct"))
            .unwrap();
        let op_fields = get_product_op.fields(&compiler.db);
        let name_field_def: Vec<String> = op_fields
            .iter()
            .filter_map(|field| Some(field.ty(&compiler.db)?.name()))
            .collect();
        assert_eq!(name_field_def, ["Int", "Product"]);
        let top_products = op_fields
            .iter()
            .find(|f| f.name() == "topProducts")
            .unwrap()
            .selection_set()
            .fields();
        let top_product_fields: Vec<String> = top_products
            .iter()
            .filter_map(|f| Some(f.ty(&compiler.db)?.name()))
            .collect();
        assert_eq!(top_product_fields, ["String", "Boolean"]);
        let in_stock_field = op_fields
            .iter()
            .find(|f| f.name() == "topProducts")
            .unwrap()
            .selection_set()
            .field("inStock")
            .unwrap()
            .field_definition(&compiler.db)
            .unwrap();
        let in_stock_directive: Vec<&str> = in_stock_field
            .directives()
            .iter()
            .map(|dir| dir.name())
            .collect();
        assert_eq!(in_stock_directive, ["join__field"]);
    }
    #[test]
    fn it_supports_multiple_independent_queries() {
        let schema = r#"
type Query {
  topProducts: Product
  customer: User
}
type Product {
  type: String
  price(setPrice: Int): Int
}
type User {
  id: ID
  name: String
  profilePic(size: Int): URL
}
scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
"#;
        let product_query = r#"query getProduct { topProducts { type } }"#;
        let customer_query = r#"{ customer { id } }"#;
        let colliding_query = r#"query getProduct { topProducts { type, price } }"#;
        let mut compiler = ApolloCompiler::new();
        compiler.add_type_system(schema, "schema.graphql");
        compiler.add_executable(product_query, "query.graphql");
        compiler.add_executable(customer_query, "query.graphql");
        compiler.add_executable(colliding_query, "query.graphql");
        assert_eq!(compiler.validate(), &[]);
    }
    #[test]
    fn it_accesses_fragment_definition_field_types() {
        let schema = r#"
type Query {
  topProducts: Product
  customer: User
}
type Product {
  type: String
  price(setPrice: Int): Int
}
type User {
  id: ID
  name: String
  profilePic(size: Int): URL
}
scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
"#;
        let query = r#"
query getProduct {
  topProducts {
    type
  }
  customer {
    ... vipCustomer
  }
}
fragment vipCustomer on User {
  id
  name
  profilePic(size: 50)
}
"#;
        let mut compiler = ApolloCompiler::new();
        compiler.add_type_system(schema, "schema.graphql");
        let query_id = compiler.add_executable(query, "query.graphql");
        let diagnostics = compiler.validate();
        for diagnostic in &diagnostics {
            println!("{diagnostic}");
        }
        assert!(diagnostics.is_empty());
        let op = compiler
            .db
            .find_operation(query_id, Some("getProduct".into()));
        let fragment_in_op: Vec<crate::hir::FragmentDefinition> = op
            .unwrap()
            .fields(&compiler.db)
            .iter()
            .find(|field| field.name() == "customer")
            .unwrap()
            .selection_set()
            .selection()
            .iter()
            .filter_map(|sel| match sel {
                crate::hir::Selection::FragmentSpread(frag) => {
                    Some(frag.fragment(&compiler.db)?.as_ref().clone())
                }
                _ => None,
            })
            .collect();
        let fragment_fields: Vec<crate::hir::Field> = fragment_in_op
            .iter()
            .flat_map(|frag| frag.selection_set().fields())
            .collect();
        let field_ty: Vec<String> = fragment_fields
            .iter()
            .filter_map(|f| Some(f.ty(&compiler.db)?.name()))
            .collect();
        assert_eq!(field_ty, ["ID", "String", "URL"])
    }
    #[test]
    fn it_accesses_schema_operation_types() {
        let input = r#"
schema {
  query: customPetQuery,
}
type customPetQuery {
  name: String,
  age: Int
}
type Subscription {
  changeInPetHousehold: Result
}
type Mutation {
  addPet (name: String!, petType: PetType): Result!
}
type Result {
  id: String
}
"#;
        let mut compiler = ApolloCompiler::new();
        compiler.add_document(input, "document.graphql");
        let diagnostics = compiler.validate();
        for diagnostic in &diagnostics {
            println!("{diagnostic}");
        }
        assert!(diagnostics.is_empty());
    }
    #[test]
    fn it_accesses_scalar_definitions() {
        let input = r#"
type Query {
  website: URL,
  amount: Int
}
scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
"#;
        let mut compiler = ApolloCompiler::new();
        compiler.add_document(input, "document.graphql");
        let diagnostics = compiler.validate();
        for diagnostic in &diagnostics {
            println!("{diagnostic}");
        }
        assert!(diagnostics.is_empty());
        let scalars = compiler.db.scalars();
        let directives: Vec<&str> = scalars["URL"]
            .self_directives()
            .iter()
            .map(|directive| directive.name())
            .collect();
        assert_eq!(directives, ["specifiedBy"]);
    }
    #[test]
    fn it_accesses_enum_definitions() {
        let input = r#"
type Query {
  pet: Pet,
}
enum Pet {
    CAT
    DOG
    FOX
}
"#;
        let mut compiler = ApolloCompiler::new();
        compiler.add_document(input, "document.graphql");
        let diagnostics = compiler.validate();
        for diagnostic in &diagnostics {
            println!("{diagnostic}");
        }
        assert!(diagnostics.is_empty());
        let enums = compiler.db.enums();
        let enum_values: Vec<&str> = enums["Pet"]
            .self_values()
            .iter()
            .map(|enum_val| enum_val.enum_value())
            .collect();
        assert_eq!(enum_values, ["CAT", "DOG", "FOX"]);
    }
    #[test]
    fn it_accesses_union_definitions() {
        let input = r#"
schema {
  query: SearchQuery
}
union SearchResult = Photo | Person
type Person {
  name: String
  age: Int
}
type Photo {
  height: Int
  width: Int
}
type SearchQuery {
  firstSearchResult: SearchResult
}
"#;
        let mut compiler = ApolloCompiler::new();
        compiler.add_document(input, "document.graphql");
        let diagnostics = compiler.validate();
        for diagnostic in &diagnostics {
            println!("{diagnostic}");
        }
        assert!(diagnostics.is_empty());
        let unions = compiler.db.unions();
        let union_members: Vec<&str> = unions["SearchResult"]
            .self_members()
            .iter()
            .map(|member| member.name())
            .collect();
        assert_eq!(union_members, ["Photo", "Person"]);
        let photo_object = unions["SearchResult"]
            .self_members()
            .iter()
            .find(|mem| mem.name() == "Person")
            .unwrap()
            .object(&compiler.db);
        if let Some(photo) = photo_object {
            let fields: Vec<&str> = photo
                .self_fields()
                .iter()
                .map(|field| field.name())
                .collect();
            assert_eq!(fields, ["name", "age"])
        }
    }
    #[test]
    fn it_accesses_directive_definitions() {
        let input = r#"
type Query {
    literature: Book
}
directive @delegateField(name: String!) repeatable on OBJECT | INTERFACE
type Book @delegateField(name: "pageCount") @delegateField(name: "author") {
  id: ID!
}
"#;
        let mut compiler = ApolloCompiler::new();
        compiler.add_document(input, "document.graphql");
        let diagnostics = compiler.validate();
        for diagnostic in &diagnostics {
            println!("{diagnostic}");
        }
        assert!(diagnostics.is_empty());
        let directives = compiler.db.directive_definitions();
        let locations: Vec<_> = directives["delegateField"]
            .directive_locations()
            .iter()
            .map(|loc| loc.name())
            .collect();
        assert_eq!(locations, ["OBJECT", "INTERFACE"]);
    }
    #[test]
    fn it_accesses_input_object_definitions() {
        let input = r#"
type Query {
  website: URL,
  amount: Int
}
scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
input Point2D {
  x: Float
  y: Float
}
"#;
        let mut compiler = ApolloCompiler::new();
        compiler.add_document(input, "document.graphql");
        let diagnostics = compiler.validate();
        for diagnostic in &diagnostics {
            println!("{diagnostic}");
        }
        assert!(diagnostics.is_empty());
        let input_objects = compiler.db.input_objects();
        let fields: Vec<&str> = input_objects["Point2D"]
            .self_fields()
            .iter()
            .map(|val| val.name())
            .collect();
        assert_eq!(fields, ["x", "y"]);
    }
    #[test]
    fn it_accesses_object_directive_name() {
        let input = r#"
type Book @directiveA(name: "pageCount") @directiveB(name: "author") {
  id: ID!
}
directive @directiveA(name: String) on OBJECT | INTERFACE
directive @directiveB(name: String) on OBJECT | INTERFACE
"#;
        let mut compiler = ApolloCompiler::new();
        compiler.add_document(input, "document.graphql");
        let diagnostics = compiler.validate();
        for diagnostic in &diagnostics {
            println!("{diagnostic}");
        }
        assert!(diagnostics.is_empty());
        let book_obj = compiler
            .db
            .find_object_type_by_name("Book".to_string())
            .unwrap();
        let directive_names: Vec<&str> = book_obj
            .self_directives()
            .iter()
            .map(|d| d.name())
            .collect();
        assert_eq!(directive_names, ["directiveA", "directiveB"]);
    }
    #[test]
    fn it_accesses_object_field_types_directive_name() {
        let input = r#"
type Person {
  name: String
  picture(size: Number): Url
}
enum Number {
    INT
    FLOAT
}
scalar Url @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
"#;
        let mut compiler = ApolloCompiler::new();
        compiler.add_document(input, "document.graphql");
        let diagnostics = compiler.validate();
        for diagnostic in &diagnostics {
            println!("{diagnostic}");
        }
        assert!(diagnostics.is_empty());
        let person_obj = compiler.db.find_object_type_by_name("Person".to_string());
        if let Some(person) = person_obj {
            let field_ty_directive: Vec<String> = person
                .self_fields()
                .iter()
                .filter_map(|f| {
                    if let Some(field_ty) = f.ty().type_def(&compiler.db) {
                        match field_ty {
                            TypeDefinition::ScalarTypeDefinition(scalar) => {
                                let dir_names: Vec<String> = scalar
                                    .self_directives()
                                    .iter()
                                    .map(|dir| dir.name().to_owned())
                                    .collect();
                                return Some(dir_names);
                            }
                            _ => return None,
                        }
                    }
                    None
                })
                .flatten()
                .collect();
            assert_eq!(field_ty_directive, ["specifiedBy"]);
            let field_arg_ty_vals: Vec<String> = person
                .self_fields()
                .iter()
                .flat_map(|f| {
                    let enum_vals: Vec<String> = f
                        .arguments()
                        .input_values()
                        .iter()
                        .filter_map(|val| {
                            if let Some(input_ty) = val.ty().type_def(&compiler.db) {
                                match input_ty {
                                    TypeDefinition::EnumTypeDefinition(enum_) => {
                                        let dir_names: Vec<String> = enum_
                                            .self_values()
                                            .iter()
                                            .map(|enum_val| enum_val.enum_value().to_owned())
                                            .collect();
                                        return Some(dir_names);
                                    }
                                    _ => return None,
                                }
                            }
                            None
                        })
                        .flatten()
                        .collect();
                    enum_vals
                })
                .collect();
            assert_eq!(field_arg_ty_vals, ["INT", "FLOAT"])
        }
    }
    #[test]
    fn it_accesses_input_object_field_types_directive_name() {
        let input = r#"
input Person {
  name: String
  picture: Url
}
scalar Url @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
"#;
        let mut compiler = ApolloCompiler::new();
        compiler.add_document(input, "document.graphql");
        let diagnostics = compiler.validate();
        for diagnostic in &diagnostics {
            println!("{diagnostic}");
        }
        assert!(diagnostics.is_empty());
        let person_obj = compiler.db.find_input_object_by_name("Person".to_string());
        if let Some(person) = person_obj {
            let field_ty_directive: Vec<String> = person
                .self_fields()
                .iter()
                .filter_map(|f| {
                    if let Some(field_ty) = f.ty().type_def(&compiler.db) {
                        match field_ty {
                            TypeDefinition::ScalarTypeDefinition(scalar) => {
                                let dir_names: Vec<String> = scalar
                                    .self_directives()
                                    .iter()
                                    .map(|dir| dir.name().to_owned())
                                    .collect();
                                return Some(dir_names);
                            }
                            _ => return None,
                        }
                    }
                    None
                })
                .flatten()
                .collect();
            assert_eq!(field_ty_directive, ["specifiedBy"]);
        }
    }
    #[test]
    fn it_accesses_object_defitions() {
        let input = r#"
schema
  @core(feature: "https://specs.apollo.dev/core/v0.1"),
  @core(feature: "https://specs.apollo.dev/join/v0.1")
{
  query: Query
  mutation: Mutation
}
directive @core(feature: String!) repeatable on SCHEMA
directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet) on FIELD_DEFINITION
directive @join__type(graph: join__Graph!, key: join__FieldSet) repeatable on OBJECT | INTERFACE
directive @join__owner(graph: join__Graph!) on OBJECT | INTERFACE
directive @join__graph(name: String!, url: String!) on ENUM_VALUE
scalar join__FieldSet
enum join__Graph {
  ACCOUNTS @join__graph(name: "accounts" url: "http://localhost:4001")
  INVENTORY @join__graph(name: "inventory" url: "http://localhost:4004")
  PRODUCTS @join__graph(name: "products" url: "http://localhost:4003")
  REVIEWS @join__graph(name: "reviews" url: "http://localhost:4002")
}
type Mutation {
  createProduct(name: String, upc: ID!): Product @join__field(graph: PRODUCTS)
  createReview(body: String, id: ID!, upc: ID!): Review @join__field(graph: REVIEWS)
}
type Product
  @join__owner(graph: PRODUCTS)
  @join__type(graph: PRODUCTS, key: "upc")
  @join__type(graph: INVENTORY, key: "upc")
  @join__type(graph: REVIEWS, key: "upc")
{
  inStock: Boolean @join__field(graph: INVENTORY)
  name: String @join__field(graph: PRODUCTS)
  price: Int @join__field(graph: PRODUCTS)
  reviews: [Review] @join__field(graph: REVIEWS)
  reviewsForAuthor(authorID: ID!): [Review] @join__field(graph: REVIEWS)
  shippingEstimate: Int @join__field(graph: INVENTORY, requires: "price weight")
  upc: String! @join__field(graph: PRODUCTS)
  weight: Int @join__field(graph: PRODUCTS)
}
type Query {
  me: User @join__field(graph: ACCOUNTS)
  topProducts(first: Int = 5): [Product] @join__field(graph: PRODUCTS)
}
type Review
  @join__owner(graph: REVIEWS)
  @join__type(graph: REVIEWS, key: "id")
{
  author: User @join__field(graph: REVIEWS, provides: "username")
  body: String @join__field(graph: REVIEWS)
  id: ID! @join__field(graph: REVIEWS)
  product: Product @join__field(graph: REVIEWS)
}
type User
  @join__owner(graph: ACCOUNTS)
  @join__type(graph: ACCOUNTS, key: "id")
  @join__type(graph: REVIEWS, key: "id")
{
  id: ID! @join__field(graph: ACCOUNTS)
  name: String @join__field(graph: ACCOUNTS)
  reviews: [Review] @join__field(graph: REVIEWS)
  username: String @join__field(graph: ACCOUNTS)
}
"#;
        let mut compiler = ApolloCompiler::new();
        compiler.add_document(input, "document.graphql");
        let diagnostics = compiler.validate();
        for diagnostic in &diagnostics {
            println!("{diagnostic}");
        }
        assert_eq!(diagnostics.len(), 1);
        let object_types = compiler.db.object_types();
        let object_names: Vec<_> = object_types.keys().map(|name| &**name).collect();
        assert_eq!(
            ["Mutation", "Product", "Query", "Review", "User"],
            object_names.as_slice()
        );
    }
    #[test]
    fn it_can_access_root_db_in_thread() {
        let input = r#"
type Query {
  website: URL,
  amount: Int
}
scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986")
"#;
        let mut compiler = ApolloCompiler::new();
        compiler.add_document(input, "document.graphql");
        let diagnostics = compiler.validate();
        for diagnostic in &diagnostics {
            println!("{diagnostic}");
        }
        assert!(diagnostics.is_empty());
        let snapshot = compiler.snapshot();
        let snapshot2 = compiler.snapshot();
        let thread1 = std::thread::spawn(move || snapshot.find_object_type_by_name("Query".into()));
        let thread2 = std::thread::spawn(move || snapshot2.scalars());
        thread1.join().expect("object_type_by_name panicked");
        thread2.join().expect("scalars failed");
    }
    #[test]
    fn inputs_can_be_updated() {
        let input = r#"
type Query {
  website: URL,
  amount: Int
}
"#;
        let mut compiler = ApolloCompiler::new();
        let input_id = compiler.add_document(input, "document.graphql");
        let object_type = compiler
            .db
            .find_object_type_by_name("Query".into())
            .unwrap();
        assert!(object_type.self_directives().is_empty());
        let input = r#"
type Query @withDirective {
  website: URL,
  amount: Int
}
"#;
        compiler.update_document(input_id, input);
        let object_type = compiler
            .db
            .find_object_type_by_name("Query".into())
            .unwrap();
        assert_eq!(object_type.self_directives().len(), 1);
    }
    #[test]
    fn precomputed_schema_can_multi_thread() {
        let schema = r#"
type Query {
    website: URL,
    amount: Int
}
"#;
        let query = "{ website }";
        let mut compiler = ApolloCompiler::new();
        compiler.add_type_system(schema, "schema.graphql");
        let type_system = compiler.db.type_system();
        let handles: Vec<_> = (0..2)
            .map(|_| {
                let cloned = std::sync::Arc::clone(&type_system); std::thread::spawn(move || {
                    let mut compiler = ApolloCompiler::new();
                    let query_id = compiler.add_executable(query, "query.graphql");
                    compiler.set_type_system_hir(cloned);
                    compiler
                        .db
                        .find_operation(query_id, None)
                        .unwrap()
                        .fields(&compiler.db)[0]
                        .ty(&compiler.db)
                        .unwrap()
                        .name()
                })
            })
            .collect();
        assert_eq!(handles.len(), 2);
        for handle in handles {
            assert_eq!(handle.join().unwrap(), "URL");
        }
    }
    #[test]
    fn interfaces_in_precomputed_hir() {
        let schema = r#"
        schema {
            query: Query
          }
          type Query {
            peopleCount: Int!
            person: Person!
          }
          interface Pet {
            name: String!
          }
          type Dog implements Pet {
            name: String!
            dogBreed: DogBreed!
          }
          type Cat implements Pet {
            name: String!
            catBreed: CatBreed!
          }
          type Person {
            firstName: String!
            lastName: String!
            age: Int
            pets: [Pet!]!
          }
          enum DogBreed {
            CHIHUAHUA
            RETRIEVER
            LAB
          }
          enum CatBreed {
            TABBY
            MIX
          }
        "#;
        let mut compiler = ApolloCompiler::new();
        compiler.add_type_system(schema, "schema.graphql");
        let type_system = compiler.db.type_system();
        let mut another_compiler = ApolloCompiler::new();
        another_compiler.set_type_system_hir(type_system);
        let interfaces = another_compiler.db.interfaces();
        assert_eq!(interfaces.len(), 1);
        assert!(another_compiler.validate().is_empty())
    }
}