/*
 * 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::HashMap;
use std::collections::HashSet;
use std::collections::VecDeque;
use std::marker::PhantomData;
use std::sync::Arc;

use common::Diagnostic;
use common::DiagnosticsResult;
use common::Location;
use common::PointerAddress;
use dashmap::DashMap;
use dashmap::DashSet;
use errors::par_try_map;
use errors::validate_map;
use graphql_ir::node_identifier::LocationAgnosticBehavior;
use graphql_ir::Argument;
use graphql_ir::Field as IRField;
use graphql_ir::FragmentDefinition;
use graphql_ir::FragmentDefinitionName;
use graphql_ir::LinkedField;
use graphql_ir::OperationDefinition;
use graphql_ir::Program;
use graphql_ir::ScalarField;
use graphql_ir::Selection;
use intern::string_key::StringKey;
use intern::Lookup;
use schema::SDLSchema;
use schema::Schema;
use schema::Type;
use schema::TypeReference;
use thiserror::Error;

use self::ignoring_type_and_location::arguments_equals;

/// Note:set `further_optimization` will enable: (1) cache the paired-fields; and (2) avoid duplicate fragment validations in multi-core machines.
pub fn validate_selection_conflict<B: LocationAgnosticBehavior + Sync>(
    program: &Program,
    further_optimization: bool,
) -> DiagnosticsResult<()> {
    ValidateSelectionConflict::<B>::new(program, further_optimization).validate_program(program)
}

#[derive(Clone, PartialEq, Debug)]
enum Field<'s> {
    LinkedField(&'s LinkedField),
    ScalarField(&'s ScalarField),
}

type Fields<'s> = HashMap<StringKey, Vec<Field<'s>>, intern::BuildIdHasher<u32>>;

struct ValidateSelectionConflict<'s, TBehavior: LocationAgnosticBehavior> {
    program: &'s Program,
    fragment_cache: DashMap<StringKey, Arc<Fields<'s>>, intern::BuildIdHasher<u32>>,
    fields_cache: DashMap<PointerAddress, Arc<Fields<'s>>>,
    further_optimization: bool,
    verified_fields_pair: DashSet<(PointerAddress, PointerAddress, bool)>,
    _behavior: PhantomData<TBehavior>,
}

impl<'s, B: LocationAgnosticBehavior + Sync> ValidateSelectionConflict<'s, B> {
    fn new(program: &'s Program, further_optimization: bool) -> Self {
        Self {
            program,
            fragment_cache: Default::default(),
            fields_cache: Default::default(),
            further_optimization,
            verified_fields_pair: Default::default(),
            _behavior: PhantomData::<B>,
        }
    }

    fn validate_program(&self, program: &'s Program) -> DiagnosticsResult<()> {
        if self.further_optimization {
            self.prewarm_fragments(program)?;
        }

        par_try_map(&program.operations, |operation| {
            self.validate_operation(operation)
        })?;
        Ok(())
    }

    fn prewarm_fragments(&self, program: &'s Program) -> DiagnosticsResult<()> {
        // Validate the fragments in topology order.
        let mut unclaimed_fragment_queue: VecDeque<FragmentDefinitionName> = VecDeque::new();

        // Construct the dependency graph, which is represented by two maps:
        // DAG1: fragment K -> Used by: {Fragment v_1, v_2, v_3, ...}
        // DAG2: fragment K -> Spreading: {Fragment v_1, v_2, v_3, ...}
        let mut dag_used_by: HashMap<FragmentDefinitionName, HashSet<FragmentDefinitionName>> =
            HashMap::new();
        let mut dag_spreading: HashMap<FragmentDefinitionName, HashSet<FragmentDefinitionName>> =
            HashMap::new();
        for fragment in program.fragments() {
            let fragment_ = fragment.name.item;
            let spreads = fragment
                .selections
                .iter()
                .flat_map(|s| s.spreaded_fragments())
                .collect::<Vec<_>>();

            for spread in &spreads {
                let spread_ = spread.fragment.item;
                // got "fragment_" spreads "...spread_"
                dag_used_by.entry(spread_).or_default().insert(fragment_);
                dag_spreading.entry(fragment_).or_default().insert(spread_);
            }
            if spreads.is_empty() {
                unclaimed_fragment_queue.push_back(fragment_);
            }
        }

        let dummy_hashset = HashSet::new();
        while let Some(visiting) = unclaimed_fragment_queue.pop_front() {
            if let Err(e) = self.validate_and_collect_fragment(
                program
                    .fragment(visiting)
                    .expect("fragment must have been registered"),
            ) {
                return Err(e);
            }

            for used_by in dag_used_by.get(&visiting).unwrap_or(&dummy_hashset) {
                // fragment "used_by" now can assume "...now" cached.
                let entries = dag_spreading.entry(*used_by).or_default();
                entries.remove(&visiting);
                if entries.is_empty() {
                    unclaimed_fragment_queue.push_back(*used_by);
                }
            }
        }
        Ok(())
    }

    fn validate_operation(&self, operation: &'s OperationDefinition) -> DiagnosticsResult<()> {
        self.validate_selections(&operation.selections)?;
        Ok(())
    }

    fn validate_selections(&self, selections: &'s [Selection]) -> DiagnosticsResult<Fields<'s>> {
        let mut fields = Default::default();
        validate_map(selections, |selection| {
            self.validate_selection(&mut fields, selection)
        })?;
        Ok(fields)
    }

    fn validate_selection(
        &self,
        fields: &mut Fields<'s>,
        selection: &'s Selection,
    ) -> DiagnosticsResult<()> {
        match selection {
            Selection::LinkedField(field) => {
                self.validate_linked_field_selections(field)?;
                let field = Field::LinkedField(field.as_ref());
                self.validate_and_insert_field_selection(fields, &field, false)
            }
            Selection::ScalarField(field) => {
                let field = Field::ScalarField(field.as_ref());
                self.validate_and_insert_field_selection(fields, &field, false)
            }
            Selection::Condition(condition) => {
                let new_fields = self.validate_selections(&condition.selections)?;
                self.validate_and_merge_fields(fields, &new_fields, false)
            }
            Selection::InlineFragment(fragment) => {
                let new_fields = self.validate_selections(&fragment.selections)?;
                self.validate_and_merge_fields(fields, &new_fields, false)
            }
            Selection::FragmentSpread(spread) => {
                let fragment = self.program.fragment(spread.fragment.item).unwrap();
                let new_fields = self.validate_and_collect_fragment(fragment)?;
                self.validate_and_merge_fields(fields, &new_fields, false)
            }
        }
    }

    fn validate_and_collect_fragment(
        &self,
        fragment: &'s FragmentDefinition,
    ) -> DiagnosticsResult<Arc<Fields<'s>>> {
        if let Some(cached) = self.fragment_cache.get(&fragment.name.item.0) {
            return Ok(Arc::clone(&cached));
        }
        let fields = Arc::new(self.validate_selections(&fragment.selections)?);
        self.fragment_cache
            .insert(fragment.name.item.0, Arc::clone(&fields));
        Ok(fields)
    }

    fn validate_linked_field_selections(
        &self,
        field: &'s LinkedField,
    ) -> DiagnosticsResult<Arc<Fields<'s>>> {
        let key = PointerAddress::new(field);
        if let Some(fields) = self.fields_cache.get(&key) {
            return Ok(Arc::clone(&fields));
        }
        let fields = Arc::new(self.validate_selections(&field.selections)?);
        self.fields_cache.insert(key, Arc::clone(&fields));
        Ok(fields)
    }

    fn validate_and_merge_fields(
        &self,
        left: &mut Fields<'s>,
        right: &Fields<'s>,
        parent_fields_mutually_exclusive: bool,
    ) -> DiagnosticsResult<()> {
        validate_map(right.values().flatten(), |field| {
            self.validate_and_insert_field_selection(left, field, parent_fields_mutually_exclusive)
        })
    }

    fn validate_and_insert_field_selection(
        &self,
        fields: &mut Fields<'s>,
        field: &Field<'s>,
        parent_fields_mutually_exclusive: bool,
    ) -> DiagnosticsResult<()> {
        let key = field.get_response_key(&self.program.schema);
        if !fields.contains_key(&key) {
            fields.entry(key).or_default().push(field.clone());
            return Ok(());
        }

        let mut errors = vec![];
        let addr1 = field.pointer_address();

        for existing_field in fields.get(&key).unwrap() {
            if field == existing_field {
                return if errors.is_empty() {
                    Ok(())
                } else {
                    Err(errors)
                };
            }

            let addr2 = existing_field.pointer_address();
            if self.further_optimization
                && self.verified_fields_pair.contains(&(
                    addr1,
                    addr2,
                    parent_fields_mutually_exclusive,
                ))
            {
                continue;
            }

            let l_definition = existing_field.get_field_definition(&self.program.schema);
            let r_definition = field.get_field_definition(&self.program.schema);

            let is_parent_fields_mutually_exclusive = || {
                parent_fields_mutually_exclusive
                    || l_definition.parent_type != r_definition.parent_type
                        && matches!(
                            (l_definition.parent_type, r_definition.parent_type),
                            (Some(Type::Object(_)), Some(Type::Object(_)))
                        )
            };

            match (existing_field, &field) {
                (Field::LinkedField(l), Field::LinkedField(r)) => {
                    let fields_mutually_exclusive = is_parent_fields_mutually_exclusive();
                    if !fields_mutually_exclusive {
                        if let Err(err) = self.validate_same_field(
                            key,
                            l_definition.name.item,
                            r_definition.name.item,
                            *l,
                            *r,
                        ) {
                            errors.push(err);
                        };
                    }
                    if has_same_type_reference_wrapping(&l_definition.type_, &r_definition.type_) {
                        let mut l_fields = self.validate_linked_field_selections(l)?;
                        let r_fields = self.validate_linked_field_selections(r)?;

                        if let Err(errs) = self.validate_and_merge_fields(
                            Arc::make_mut(&mut l_fields),
                            &r_fields,
                            fields_mutually_exclusive,
                        ) {
                            errors.extend(errs);
                        }
                    } else {
                        errors.push(
                            Diagnostic::error(
                                ValidationMessage::AmbiguousFieldType {
                                    response_key: key,
                                    l_name: l_definition.name.item,
                                    r_name: r_definition.name.item,
                                    l_type_string: self
                                        .program
                                        .schema
                                        .get_type_string(&l_definition.type_),
                                    r_type_string: self
                                        .program
                                        .schema
                                        .get_type_string(&r_definition.type_),
                                },
                                l.definition.location,
                            )
                            .annotate("the other field", r.definition.location),
                        );
                    }
                }
                (Field::ScalarField(l), Field::ScalarField(r)) => {
                    if !is_parent_fields_mutually_exclusive() {
                        if let Err(err) = self.validate_same_field(
                            key,
                            l_definition.name.item,
                            r_definition.name.item,
                            *l,
                            *r,
                        ) {
                            errors.push(err);
                        };
                    } else if l_definition.type_ != r_definition.type_ {
                        errors.push(
                            Diagnostic::error(
                                ValidationMessage::AmbiguousFieldType {
                                    response_key: key,
                                    l_name: l_definition.name.item,
                                    r_name: r_definition.name.item,
                                    l_type_string: self
                                        .program
                                        .schema
                                        .get_type_string(&l_definition.type_),
                                    r_type_string: self
                                        .program
                                        .schema
                                        .get_type_string(&r_definition.type_),
                                },
                                l.definition.location,
                            )
                            .annotate("the other field", r.definition.location),
                        );
                    }
                }
                (existing_field, _) => {
                    errors.push(
                        Diagnostic::error(
                            ValidationMessage::AmbiguousFieldType {
                                response_key: key,
                                l_name: l_definition.name.item,
                                r_name: r_definition.name.item,
                                l_type_string: self
                                    .program
                                    .schema
                                    .get_type_string(&l_definition.type_),
                                r_type_string: self
                                    .program
                                    .schema
                                    .get_type_string(&r_definition.type_),
                            },
                            existing_field.loc(),
                        )
                        .annotate("the other field", field.loc()),
                    );
                }
            }

            // Save the verified pair into cache. The same pair of fields can appear under different parent
            // fields, and the validation rule differs according to `parent_fields_mutually_exclusive`.
            if self.further_optimization {
                self.verified_fields_pair
                    .insert((addr1, addr2, parent_fields_mutually_exclusive));
            }
        }
        if errors.is_empty() {
            fields.entry(key).or_default().push(field.clone());
            Ok(())
        } else {
            Err(errors)
        }
    }

    fn validate_same_field<F: IRField>(
        &self,
        response_key: StringKey,
        l_name: StringKey,
        r_name: StringKey,
        l: &F,
        r: &F,
    ) -> Result<(), Diagnostic> {
        if l_name != r_name {
            Err(Diagnostic::error(
                ValidationMessage::AmbiguousFieldAlias {
                    response_key,
                    l_name,
                    r_name,
                },
                l.definition().location,
            )
            .annotate("the other field", r.definition().location))
        } else if !(arguments_equals::<B>(l.arguments(), r.arguments())) {
            Err(self.create_conflicting_fields_error(
                response_key,
                l.definition().location,
                l.arguments(),
                r.definition().location,
                r.arguments(),
            ))
        } else {
            let left_stream_directive = l
                .directives()
                .iter()
                .find(|d| d.name.item.0.lookup() == "stream");
            let right_stream_directive = r
                .directives()
                .iter()
                .find(|d| d.name.item.0.lookup() == "stream");
            match (left_stream_directive, right_stream_directive) {
                (Some(_), None) => Err(Diagnostic::error(
                    ValidationMessage::StreamConflictOnlyUsedInOnePlace { response_key },
                    l.definition().location,
                )
                .annotate("not marked in", r.definition().location)),
                (None, Some(_)) => Err(Diagnostic::error(
                    ValidationMessage::StreamConflictOnlyUsedInOnePlace { response_key },
                    r.definition().location,
                )
                .annotate("not marked in", l.definition().location)),
                (Some(_), Some(_)) => Err(Diagnostic::error(
                    ValidationMessage::StreamConflictUsedInMultiplePlaces { response_key },
                    l.definition().location,
                )
                .annotate("the other field", r.definition().location)),
                (None, None) => Ok(()),
            }
        }
    }

    fn create_conflicting_fields_error(
        &self,
        field_name: StringKey,
        location_a: Location,
        arguments_a: &[Argument],
        location_b: Location,
        arguments_b: &[Argument],
    ) -> Diagnostic {
        let arguments_a_printed = graphql_text_printer::print_arguments(
            &self.program.schema,
            arguments_a,
            graphql_text_printer::PrinterOptions::default(),
        );
        let arguments_b_printed = graphql_text_printer::print_arguments(
            &self.program.schema,
            arguments_b,
            graphql_text_printer::PrinterOptions::default(),
        );

        Diagnostic::error(
            ValidationMessage::InvalidSameFieldWithDifferentArguments {
                field_name,
                arguments_a: arguments_a_printed.clone(),
            },
            location_a,
        )
        .annotate(
            format!(
                "which conflicts with this field with applied argument values {}",
                &arguments_b_printed,
            ),
            location_b,
        )
        .metadata_for_machine("err", "InvalidSameFieldWithDifferentArguments")
        .metadata_for_machine("field_name", field_name.lookup())
        .metadata_for_machine("arg_a", arguments_a_printed)
        .metadata_for_machine("arg_b", arguments_b_printed)
        .metadata_for_machine("loc_a", format!("{:?}", location_a))
        .metadata_for_machine("loc_b", format!("{:?}", location_b))
    }
}

fn has_same_type_reference_wrapping(l: &TypeReference<Type>, r: &TypeReference<Type>) -> bool {
    match (l, r) {
        (TypeReference::Named(_), TypeReference::Named(_)) => true,
        (TypeReference::NonNull(l), TypeReference::NonNull(r))
        | (TypeReference::List(l), TypeReference::List(r)) => {
            has_same_type_reference_wrapping(l, r)
        }
        _ => false,
    }
}

impl<'s> Field<'s> {
    fn get_response_key(&self, schema: &SDLSchema) -> StringKey {
        match self {
            Field::LinkedField(f) => f.alias_or_name(schema),
            Field::ScalarField(f) => f.alias_or_name(schema),
        }
    }

    fn get_field_definition(&self, schema: &'s SDLSchema) -> &'s schema::definitions::Field {
        match self {
            Field::LinkedField(f) => schema.field(f.definition.item),
            Field::ScalarField(f) => schema.field(f.definition.item),
        }
    }

    fn loc(&self) -> Location {
        match self {
            Field::LinkedField(f) => f.definition.location,
            Field::ScalarField(f) => f.definition.location,
        }
    }

    fn pointer_address(&self) -> PointerAddress {
        match self {
            Field::LinkedField(f) => PointerAddress::new(&f.definition),
            Field::ScalarField(f) => PointerAddress::new(&f.definition),
        }
    }
}

mod ignoring_type_and_location {
    use graphql_ir::node_identifier::LocationAgnosticBehavior;
    use graphql_ir::node_identifier::LocationAgnosticPartialEq;
    use graphql_ir::Argument;
    use graphql_ir::Value;

    /// Verify that two sets of arguments are equivalent - same argument names
    /// and values. Notably, this ignores the types of arguments and values,
    /// which may not always be inferred identically.
    pub fn arguments_equals<B: LocationAgnosticBehavior>(a: &[Argument], b: &[Argument]) -> bool {
        order_agnostic_slice_equals(a, b, |a, b| {
            a.name.item.0.location_agnostic_eq::<B>(&b.name.item.0)
                && value_equals::<B>(&a.value.item, &b.value.item)
        })
    }

    fn value_equals<B: LocationAgnosticBehavior>(a: &Value, b: &Value) -> bool {
        match (a, b) {
            (Value::Constant(a), Value::Constant(b)) => a.location_agnostic_eq::<B>(b),
            (Value::Variable(a), Value::Variable(b)) => {
                a.name.item.0.location_agnostic_eq::<B>(&b.name.item.0)
            }
            (Value::List(a), Value::List(b)) => slice_equals(a, b, value_equals::<B>),
            (Value::Object(a), Value::Object(b)) => arguments_equals::<B>(a, b),
            _ => false,
        }
    }

    fn order_agnostic_slice_equals<T, F>(a: &[T], b: &[T], eq: F) -> bool
    where
        F: Fn(&T, &T) -> bool,
    {
        if a.len() != b.len() {
            false
        } else {
            let len = a.len();
            let mut matched = vec![false; len];
            for l in a {
                for i in 0..len {
                    if !matched[i] && eq(l, &b[i]) {
                        matched[i] = true;
                        break;
                    }
                }
            }
            matched.into_iter().all(|v| v)
        }
    }

    fn slice_equals<T, F>(a: &[T], b: &[T], eq: F) -> bool
    where
        F: Fn(&T, &T) -> bool,
    {
        a.len() == b.len() && a.iter().zip(b).all(|(a, b)| eq(a, b))
    }
}

#[derive(Clone, Debug, Error, Eq, PartialEq, Ord, PartialOrd, Hash)]
enum ValidationMessage {
    #[error(
        "Field '{response_key}' is ambiguous because it references two different fields: '{l_name}' and '{r_name}'"
    )]
    AmbiguousFieldAlias {
        response_key: StringKey,
        l_name: StringKey,
        r_name: StringKey,
    },

    #[error(
        "Field '{response_key}' is ambiguous because it references fields with different types: '{l_name}' with type '{l_type_string}' and '{r_name}' with type '{r_type_string}'"
    )]
    AmbiguousFieldType {
        response_key: StringKey,
        l_name: StringKey,
        l_type_string: String,
        r_name: StringKey,
        r_type_string: String,
    },

    #[error(
        "Expected all fields on the same parent with the name or alias `{field_name}` to have the same argument values after applying fragment arguments. This field has the applied argument values: {arguments_a}"
    )]
    InvalidSameFieldWithDifferentArguments {
        field_name: StringKey,
        arguments_a: String,
    },

    #[error(
        "Field '{response_key}' is marked with @stream in one place, and not marked in another place. Please use an alias to distinguish the two fields."
    )]
    StreamConflictOnlyUsedInOnePlace { response_key: StringKey },

    #[error(
        "Field '{response_key}' is marked with @stream in multiple places. Please use an alias to distinguish them."
    )]
    StreamConflictUsedInMultiplePlaces { response_key: StringKey },
}


/*
 * 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.
 *
 * @generated SignedSource<<82816cf559b0847f27b559ede51c9022>>
 */

mod validate_selection_conflict;

use validate_selection_conflict::transform_fixture;
use fixture_tests::test_fixture;

#[test]
fn same_alias_list_non_list() {
    let input = include_str!("validate_selection_conflict/fixtures/same-alias-list-non-list.graphql");
    let expected = include_str!("validate_selection_conflict/fixtures/same-alias-list-non-list.expected");
    test_fixture(transform_fixture, "same-alias-list-non-list.graphql", "validate_selection_conflict/fixtures/same-alias-list-non-list.expected", input, expected);
}

#[test]
fn same_alias_nested() {
    let input = include_str!("validate_selection_conflict/fixtures/same-alias-nested.graphql");
    let expected = include_str!("validate_selection_conflict/fixtures/same-alias-nested.expected");
    test_fixture(transform_fixture, "same-alias-nested.graphql", "validate_selection_conflict/fixtures/same-alias-nested.expected", input, expected);
}

#[test]
fn same_alias_nested_mutually_exclusive() {
    let input = include_str!("validate_selection_conflict/fixtures/same-alias-nested-mutually-exclusive.graphql");
    let expected = include_str!("validate_selection_conflict/fixtures/same-alias-nested-mutually-exclusive.expected");
    test_fixture(transform_fixture, "same-alias-nested-mutually-exclusive.graphql", "validate_selection_conflict/fixtures/same-alias-nested-mutually-exclusive.expected", input, expected);
}

#[test]
fn same_alias_on_different_types() {
    let input = include_str!("validate_selection_conflict/fixtures/same-alias-on-different-types.graphql");
    let expected = include_str!("validate_selection_conflict/fixtures/same-alias-on-different-types.expected");
    test_fixture(transform_fixture, "same-alias-on-different-types.graphql", "validate_selection_conflict/fixtures/same-alias-on-different-types.expected", input, expected);
}

#[test]
fn same_alias_on_different_types_inline_fragments() {
    let input = include_str!("validate_selection_conflict/fixtures/same-alias-on-different-types-inline-fragments.graphql");
    let expected = include_str!("validate_selection_conflict/fixtures/same-alias-on-different-types-inline-fragments.expected");
    test_fixture(transform_fixture, "same-alias-on-different-types-inline-fragments.graphql", "validate_selection_conflict/fixtures/same-alias-on-different-types-inline-fragments.expected", input, expected);
}

#[test]
fn same_alias_under_different_inline_fragments() {
    let input = include_str!("validate_selection_conflict/fixtures/same-alias-under-different-inline-fragments.graphql");
    let expected = include_str!("validate_selection_conflict/fixtures/same-alias-under-different-inline-fragments.expected");
    test_fixture(transform_fixture, "same-alias-under-different-inline-fragments.graphql", "validate_selection_conflict/fixtures/same-alias-under-different-inline-fragments.expected", input, expected);
}

#[test]
fn same_alias_under_duplicated_linked_field() {
    let input = include_str!("validate_selection_conflict/fixtures/same-alias-under-duplicated-linked-field.graphql");
    let expected = include_str!("validate_selection_conflict/fixtures/same-alias-under-duplicated-linked-field.expected");
    test_fixture(transform_fixture, "same-alias-under-duplicated-linked-field.graphql", "validate_selection_conflict/fixtures/same-alias-under-duplicated-linked-field.expected", input, expected);
}


/*
 * 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::sync::Arc;

use common::DirectiveName;
use common::SourceLocationKey;
use common::TextSource;
use fixture_tests::Fixture;
use graphql_cli::DiagnosticPrinter;
use graphql_ir::build;
use graphql_ir::node_identifier::LocationAgnosticBehavior;
use graphql_ir::Program;
use graphql_ir_validations::validate_selection_conflict;
use graphql_syntax::parse_executable;
use graphql_test_helpers::diagnostics_to_sorted_string;
use relay_test_schema::TEST_SCHEMA;

#[derive(Clone)]
struct LocationAgnosticBehaviorForTestOnly;
impl LocationAgnosticBehavior for LocationAgnosticBehaviorForTestOnly {
    fn should_skip_in_node_identifier(_name: DirectiveName) -> bool {
        false
    }
    fn hash_for_name_only(_name: DirectiveName) -> bool {
        false
    }
}

pub fn transform_fixture(fixture: &Fixture<'_>) -> Result<String, String> {
    let source_location = SourceLocationKey::standalone(fixture.file_name);

    let ast = parse_executable(fixture.content, source_location).unwrap();
    let ir_result = build(&TEST_SCHEMA, &ast.definitions);
    let ir = match ir_result {
        Ok(res) => res,
        Err(errors) => {
            let mut errs = errors
                .into_iter()
                .map(|err| {
                    let printer = DiagnosticPrinter::new(|_| {
                        Some(TextSource::from_whole_document(fixture.content.to_string()))
                    });
                    printer.diagnostic_to_string(&err)
                })
                .collect::<Vec<_>>();
            errs.sort();
            return Err(errs.join("\n\n"));
        }
    };

    let program = Program::from_definitions(Arc::clone(&TEST_SCHEMA), ir);
    validate_selection_conflict::<LocationAgnosticBehaviorForTestOnly>(&program, true)
        .map_err(|diagnostics| diagnostics_to_sorted_string(fixture.content, &diagnostics))?;

    Ok("OK".to_owned())
}


/*
 * 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::fmt;

use logos::Lexer;
use logos::Logos;

#[derive(Default, Eq, PartialEq)]
pub struct TokenKindExtras {
    /// Token callbacks might store an error token kind in here before failing.
    /// This is then picked up in the parser to turn the `Error` token into a
    /// more specific variant.
    pub error_token: Option<TokenKind>,
}

/// Lexer for the GraphQL specification: http://spec.graphql.org/
#[derive(Logos, Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[logos(extras = TokenKindExtras)]
pub enum TokenKind {
    #[regex(r"[ \t\r\n\f,\ufeff]+|#[^\n\r]*", logos::skip)]
    #[error]
    Error,

    ErrorUnterminatedString,
    ErrorUnsupportedStringCharacter,
    ErrorUnterminatedBlockString,
    Empty,

    // Valid tokens
    #[token("&")]
    Ampersand,

    #[token("@")]
    At,

    #[token("}")]
    CloseBrace,

    #[token("]")]
    CloseBracket,

    #[token(")")]
    CloseParen,

    #[token(":")]
    Colon,

    #[token("$")]
    Dollar,

    EndOfFile,

    #[token("=")]
    Equals,

    #[token("!")]
    Exclamation,

    // IntegerPart:    -?(0|[1-9][0-9]*)
    // FractionalPart: \\.[0-9]+
    // ExponentPart:   [eE][+-]?[0-9]+
    #[regex("-?(0|[1-9][0-9]*)(\\.[0-9]+[eE][+-]?[0-9]+|\\.[0-9]+|[eE][+-]?[0-9]+)")]
    FloatLiteral,

    #[regex("[a-zA-Z_][a-zA-Z0-9_]*")]
    Identifier,

    #[regex("-?(0|[1-9][0-9]*)")]
    IntegerLiteral,

    #[regex("-?0[0-9]+(\\.[0-9]+[eE][+-]?[0-9]+|\\.[0-9]+|[eE][+-]?[0-9]+)?")]
    ErrorNumberLiteralLeadingZero,

    #[regex("-?(0|[1-9][0-9]*)(\\.[0-9]+[eE][+-]?[0-9]+|\\.[0-9]+|[eE][+-]?[0-9]+)?[.a-zA-Z_]")]
    ErrorNumberLiteralTrailingInvalid,

    #[regex("-?(\\.[0-9]+[eE][+-]?[0-9]+|\\.[0-9]+)")]
    ErrorFloatLiteralMissingZero,

    #[token("{")]
    OpenBrace,

    #[token("[")]
    OpenBracket,

    #[token("(")]
    OpenParen,

    #[token(".")]
    Period,

    #[token("..")]
    PeriodPeriod,

    #[token("|")]
    Pipe,

    #[token("...")]
    Spread,

    #[token("\"", lex_string)]
    StringLiteral,

    #[token("\"\"\"", lex_block_string)]
    BlockStringLiteral,
}

#[derive(Logos, Debug)]
pub enum StringToken {
    #[error]
    Error,

    #[regex(r#"\\["\\/bfnrt]"#)]
    EscapedCharacter,

    #[regex(r#"\\u[0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f]"#)]
    EscapedUnicode,

    #[token("\"")]
    Quote,

    #[regex(r#"\n|\r|\r\n"#)]
    LineTerminator,

    #[regex(r#"[\u0009\u0020\u0021\u0023-\u005B\u005D-\uFFFF]+"#)]
    StringCharacters,
}

fn lex_string(lexer: &mut Lexer<'_, TokenKind>) -> bool {
    let remainder = lexer.remainder();
    let mut string_lexer = StringToken::lexer(remainder);
    while let Some(string_token) = string_lexer.next() {
        match string_token {
            StringToken::Quote => {
                lexer.bump(string_lexer.span().end);
                return true;
            }
            StringToken::LineTerminator => {
                lexer.bump(string_lexer.span().start);
                lexer.extras.error_token = Some(TokenKind::ErrorUnterminatedString);
                return false;
            }
            StringToken::EscapedCharacter
            | StringToken::EscapedUnicode
            | StringToken::StringCharacters => {}
            StringToken::Error => {
                lexer.extras.error_token = Some(TokenKind::ErrorUnsupportedStringCharacter);
                return false;
            }
        }
    }
    lexer.extras.error_token = Some(TokenKind::ErrorUnterminatedString);
    false
}

fn lex_block_string(lexer: &mut Lexer<'_, TokenKind>) -> bool {
    let remainder = lexer.remainder();
    let mut string_lexer = BlockStringToken::lexer(remainder);
    while let Some(string_token) = string_lexer.next() {
        match string_token {
            BlockStringToken::TripleQuote => {
                lexer.bump(string_lexer.span().end);
                return true;
            }
            BlockStringToken::EscapedTripleQuote | BlockStringToken::Other => {}
            BlockStringToken::Error => unreachable!(),
        }
    }
    lexer.extras.error_token = Some(TokenKind::ErrorUnterminatedBlockString);
    false
}

#[derive(Logos, Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum BlockStringToken {
    #[error]
    Error,

    #[token("\\\"\"\"")]
    EscapedTripleQuote,

    #[token("\"\"\"")]
    TripleQuote,

    #[regex(r#"[\u0009\u000A\u000D\u0020-\uFFFF]"#)]
    Other,
}

impl fmt::Display for TokenKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let message = match self {
            TokenKind::Ampersand => "ampersand ('&')",
            TokenKind::At => "at ('@')",
            TokenKind::CloseBrace => "closing brace ('}')",
            TokenKind::CloseBracket => "closing bracket (']')",
            TokenKind::CloseParen => "closing paren (')')",
            TokenKind::Colon => "colon (':')",
            TokenKind::Dollar => "dollar ('$')",
            TokenKind::EndOfFile => "end of file",
            TokenKind::Equals => "equals ('=')",
            TokenKind::Exclamation => "exclamation mark ('!')",
            TokenKind::FloatLiteral => "floating point value (e.g. '3.14')",
            TokenKind::Identifier => "non-variable identifier (e.g. 'x' or 'Foo')",
            TokenKind::IntegerLiteral => "integer value (e.g. '0' or '42')",
            TokenKind::OpenBrace => "open brace ('{')",
            TokenKind::OpenBracket => "open bracket ('[')",
            TokenKind::OpenParen => "open parenthesis ('(')",
            TokenKind::Period => "period ('.')",
            TokenKind::PeriodPeriod => "double period ('..')",
            TokenKind::Pipe => "pipe ('|')",
            TokenKind::Spread => "spread ('...')",
            TokenKind::BlockStringLiteral => "block string (e.g. '\"\"\"hi\"\"\"')",
            TokenKind::Error => "error",
            TokenKind::ErrorFloatLiteralMissingZero => "unsupported number (int or float) literal",
            TokenKind::ErrorNumberLiteralLeadingZero => "unsupported number (int or float) literal",
            TokenKind::ErrorNumberLiteralTrailingInvalid => {
                "unsupported number (int or float) literal"
            }
            TokenKind::StringLiteral => "string literal (e.g. '\"...\"')",
            TokenKind::ErrorUnterminatedString => "unterminated string",
            TokenKind::ErrorUnsupportedStringCharacter => "unsupported character in string",
            TokenKind::ErrorUnterminatedBlockString => "unterminated block string",
            TokenKind::Empty => "missing expected kind",
        };
        f.write_str(message)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn assert_token(source: &str, kind: TokenKind, length: usize) {
        let mut lexer = TokenKind::lexer(source);
        assert_eq!(
            lexer.next(),
            Some(kind),
            "Testing the lexing of string '{}'",
            source
        );
        assert_eq!(
            lexer.span(),
            0..length,
            "Testing the lexing of string '{}'",
            source
        );
    }

    #[test]
    fn test_number_successes() {
        assert_token("4", TokenKind::IntegerLiteral, 1);
        assert_token("4.123", TokenKind::FloatLiteral, 5);
        assert_token("-4", TokenKind::IntegerLiteral, 2);
        assert_token("9", TokenKind::IntegerLiteral, 1);
        assert_token("0", TokenKind::IntegerLiteral, 1);
        assert_token("-4.123", TokenKind::FloatLiteral, 6);
        assert_token("0.123", TokenKind::FloatLiteral, 5);
        assert_token("123e4", TokenKind::FloatLiteral, 5);
        assert_token("123E4", TokenKind::FloatLiteral, 5);
        assert_token("123e-4", TokenKind::FloatLiteral, 6);
        assert_token("123e+4", TokenKind::FloatLiteral, 6);
        assert_token("-1.123e4", TokenKind::FloatLiteral, 8);
        assert_token("-1.123E4", TokenKind::FloatLiteral, 8);
        assert_token("-1.123e-4", TokenKind::FloatLiteral, 9);
        assert_token("-1.123e+4", TokenKind::FloatLiteral, 9);
        assert_token("-1.123e4567", TokenKind::FloatLiteral, 11);
        assert_token("-0", TokenKind::IntegerLiteral, 2);
    }

    #[test]
    fn test_number_failures() {
        assert_token("00", TokenKind::ErrorNumberLiteralLeadingZero, 2);
        assert_token("01", TokenKind::ErrorNumberLiteralLeadingZero, 2);
        assert_token("-01", TokenKind::ErrorNumberLiteralLeadingZero, 3);
        assert_token("+1", TokenKind::Error, 1);
        assert_token("01.23", TokenKind::ErrorNumberLiteralLeadingZero, 5);
        assert_token("1.", TokenKind::ErrorNumberLiteralTrailingInvalid, 2);
        assert_token("1e", TokenKind::ErrorNumberLiteralTrailingInvalid, 2);
        assert_token("1.e1", TokenKind::ErrorNumberLiteralTrailingInvalid, 2);
        assert_token("1.A", TokenKind::ErrorNumberLiteralTrailingInvalid, 2);
        assert_token("-A", TokenKind::Error, 1);
        assert_token("1.0e", TokenKind::ErrorNumberLiteralTrailingInvalid, 4);
        assert_token("1.0eA", TokenKind::ErrorNumberLiteralTrailingInvalid, 4);
        assert_token("1.2e3e", TokenKind::ErrorNumberLiteralTrailingInvalid, 6);
        assert_token("1.2e3.4", TokenKind::ErrorNumberLiteralTrailingInvalid, 6);
        assert_token("1.23.4", TokenKind::ErrorNumberLiteralTrailingInvalid, 5);
        assert_token(".123", TokenKind::ErrorFloatLiteralMissingZero, 4);

        // check that we don't consume trailing valid items
        assert_token("1.23.{}", TokenKind::ErrorNumberLiteralTrailingInvalid, 5);
        assert_token("1.23. {}", TokenKind::ErrorNumberLiteralTrailingInvalid, 5);
        assert_token("1.23. []", TokenKind::ErrorNumberLiteralTrailingInvalid, 5);
        assert_token("1.23. foo", TokenKind::ErrorNumberLiteralTrailingInvalid, 5);
        assert_token(
            "1.23. $foo",
            TokenKind::ErrorNumberLiteralTrailingInvalid,
            5,
        );
    }

    #[test]
    fn test_lexing() {
        let input = "
          query EmptyQuery($id: ID!) {
            node(id: $id) {
              id @skip(if: false)
              ...E1
            }
          }
        ";
        let mut lexer = TokenKind::lexer(input);

        assert_eq!(lexer.next(), Some(TokenKind::Identifier));
        assert_eq!(lexer.slice(), "query");

        assert_eq!(lexer.next(), Some(TokenKind::Identifier));
        assert_eq!(lexer.slice(), "EmptyQuery");

        assert_eq!(lexer.next(), Some(TokenKind::OpenParen));
        assert_eq!(lexer.slice(), "(");

        assert_eq!(lexer.next(), Some(TokenKind::Dollar));
        assert_eq!(lexer.slice(), "$");

        assert_eq!(lexer.next(), Some(TokenKind::Identifier));
        assert_eq!(lexer.slice(), "id");

        assert_eq!(lexer.next(), Some(TokenKind::Colon));
        assert_eq!(lexer.slice(), ":");

        assert_eq!(lexer.next(), Some(TokenKind::Identifier));
        assert_eq!(lexer.slice(), "ID");

        assert_eq!(lexer.next(), Some(TokenKind::Exclamation));
        assert_eq!(lexer.slice(), "!");

        assert_eq!(lexer.next(), Some(TokenKind::CloseParen));
        assert_eq!(lexer.slice(), ")");

        assert_eq!(lexer.next(), Some(TokenKind::OpenBrace));
        assert_eq!(lexer.slice(), "{");

        assert_eq!(lexer.next(), Some(TokenKind::Identifier));
        assert_eq!(lexer.slice(), "node");

        assert_eq!(lexer.next(), Some(TokenKind::OpenParen));
        assert_eq!(lexer.slice(), "(");

        assert_eq!(lexer.next(), Some(TokenKind::Identifier));
        assert_eq!(lexer.slice(), "id");

        assert_eq!(lexer.next(), Some(TokenKind::Colon));
        assert_eq!(lexer.slice(), ":");

        assert_eq!(lexer.next(), Some(TokenKind::Dollar));
        assert_eq!(lexer.slice(), "$");

        assert_eq!(lexer.next(), Some(TokenKind::Identifier));
        assert_eq!(lexer.slice(), "id");

        assert_eq!(lexer.next(), Some(TokenKind::CloseParen));
        assert_eq!(lexer.slice(), ")");

        assert_eq!(lexer.next(), Some(TokenKind::OpenBrace));
        assert_eq!(lexer.slice(), "{");

        assert_eq!(lexer.next(), Some(TokenKind::Identifier));
        assert_eq!(lexer.slice(), "id");

        assert_eq!(lexer.next(), Some(TokenKind::At));
        assert_eq!(lexer.slice(), "@");

        assert_eq!(lexer.next(), Some(TokenKind::Identifier));
        assert_eq!(lexer.slice(), "skip");

        assert_eq!(lexer.next(), Some(TokenKind::OpenParen));
        assert_eq!(lexer.slice(), "(");

        assert_eq!(lexer.next(), Some(TokenKind::Identifier));
        assert_eq!(lexer.slice(), "if");

        assert_eq!(lexer.next(), Some(TokenKind::Colon));
        assert_eq!(lexer.slice(), ":");

        assert_eq!(lexer.next(), Some(TokenKind::Identifier));
        assert_eq!(lexer.slice(), "false");

        assert_eq!(lexer.next(), Some(TokenKind::CloseParen));
        assert_eq!(lexer.slice(), ")");

        assert_eq!(lexer.next(), Some(TokenKind::Spread));
        assert_eq!(lexer.slice(), "...");

        assert_eq!(lexer.next(), Some(TokenKind::Identifier));
        assert_eq!(lexer.slice(), "E1");

        assert_eq!(lexer.next(), Some(TokenKind::CloseBrace));
        assert_eq!(lexer.slice(), "}");

        assert_eq!(lexer.next(), Some(TokenKind::CloseBrace));
        assert_eq!(lexer.slice(), "}");

        assert_eq!(lexer.next(), None);
    }

    #[test]
    fn test_string_lexing() {
        let input = r#"
            "test"
            "escaped \" quote"
            "unterminated
            "
        "#;
        let mut lexer = TokenKind::lexer(input);

        assert_eq!(lexer.next(), Some(TokenKind::StringLiteral));
        assert_eq!(lexer.slice(), "\"test\"");

        assert_eq!(lexer.next(), Some(TokenKind::StringLiteral));
        assert_eq!(lexer.slice(), r#""escaped \" quote""#);

        assert_eq!(lexer.next(), Some(TokenKind::Error));
        assert_eq!(
            lexer.extras.error_token,
            Some(TokenKind::ErrorUnterminatedString)
        );
        assert_eq!(lexer.slice(), "\"unterminated");
    }

    #[test]
    fn test_invalid_character_lexing() {
        let input = r#"
            {
                %%%
                __typename
                *
            }
        "#;
        let mut lexer = TokenKind::lexer(input);

        assert_eq!(lexer.next(), Some(TokenKind::OpenBrace));
        assert_eq!(lexer.slice(), "{");

        assert_eq!(lexer.next(), Some(TokenKind::Error));
        assert_eq!(lexer.slice(), "%");

        assert_eq!(lexer.next(), Some(TokenKind::Error));
        assert_eq!(lexer.slice(), "%");

        assert_eq!(lexer.next(), Some(TokenKind::Error));
        assert_eq!(lexer.slice(), "%");

        assert_eq!(lexer.next(), Some(TokenKind::Identifier));
        assert_eq!(lexer.slice(), "__typename");

        assert_eq!(lexer.next(), Some(TokenKind::Error));
        assert_eq!(lexer.slice(), "*");

        assert_eq!(lexer.next(), Some(TokenKind::CloseBrace));
        assert_eq!(lexer.slice(), "}");

        assert_eq!(lexer.next(), None);
    }

    #[test]
    fn test_block_string_lexing() {
        let input = r#"
            # escaped
            """tes\"""t"""
            # empty
            """"""
            # 2 quotes in a string
            """"" """
            """
                multi-
                line
            """
            """unterminated
        "#;
        let mut lexer = TokenKind::lexer(input);

        assert_eq!(lexer.next(), Some(TokenKind::BlockStringLiteral));
        assert_eq!(lexer.slice(), r#""""tes\"""t""""#);

        assert_eq!(lexer.next(), Some(TokenKind::BlockStringLiteral));
        assert_eq!(lexer.slice(), r#""""""""#);

        assert_eq!(lexer.next(), Some(TokenKind::BlockStringLiteral));
        assert_eq!(lexer.slice(), r#"""""" """"#);

        assert_eq!(lexer.next(), Some(TokenKind::BlockStringLiteral));
        assert_eq!(
            lexer.slice(),
            r#""""
                multi-
                line
            """"#
        );

        assert_eq!(lexer.next(), Some(TokenKind::Error));
        assert_eq!(
            lexer.extras.error_token,
            Some(TokenKind::ErrorUnterminatedBlockString)
        );
        // Unterminated string just consumes the starting quotes
        assert_eq!(lexer.slice(), r#"""""#);
    }

    #[test]
    fn test_bom_lexing() {
        let input = "\u{feff}";

        let mut lexer = TokenKind::lexer(input);

        assert_eq!(lexer.next(), None);
    }
}


/*
 * 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.
 */

#![deny(warnings)]
#![deny(rust_2018_idioms)]
#![deny(clippy::all)]
#![allow(clippy::large_enum_variant)]

mod lexer;
mod node;
mod parser;
mod source;
mod syntax_error;
mod utils;

use common::DiagnosticsResult;
use common::SourceLocationKey;
use common::WithDiagnostics;
pub use lexer::TokenKind;
pub use node::*;
pub use parser::FragmentArgumentSyntaxKind;
pub use parser::ParserFeatures;
pub use source::GraphQLSource;
pub use syntax_error::SyntaxError;
pub use utils::*;

use crate::parser::Parser;

/// Parses a GraphQL document that might contain type system and executable
/// definitions.
pub fn parse_document(
    source: &str,
    source_location: SourceLocationKey,
) -> DiagnosticsResult<Document> {
    let features = ParserFeatures::default();
    let parser = Parser::new(source, source_location, features);
    parser.parse_document()
}

/// Parses a GraphQL document that might contain type system and executable
/// definitions with custom feature flags passed as `features`.
pub fn parse_document_with_features(
    source: &str,
    source_location: SourceLocationKey,
    features: ParserFeatures,
) -> DiagnosticsResult<Document> {
    let parser = Parser::new(source, source_location, features);
    parser.parse_document()
}

/// Parses a GraphQL document that's restricted to executable definitions.
pub fn parse_executable(
    source: &str,
    source_location: SourceLocationKey,
) -> DiagnosticsResult<ExecutableDocument> {
    parse_executable_with_error_recovery(source, source_location).into()
}

/// Parses a GraphQL document that's restricted to executable definitions,
/// with error recovery.
pub fn parse_executable_with_error_recovery(
    source: &str,
    source_location: SourceLocationKey,
) -> WithDiagnostics<ExecutableDocument> {
    let features = ParserFeatures::default();
    let parser = Parser::new(source, source_location, features);
    parser.parse_executable_document()
}

/// Parses a GraphQL document that's restricted to executable definitions,
/// with error recovery and customed ParserFeatures.
pub fn parse_executable_with_error_recovery_and_parser_features(
    source: &str,
    source_location: SourceLocationKey,
    features: ParserFeatures,
) -> WithDiagnostics<ExecutableDocument> {
    let parser = Parser::new(source, source_location, features);
    parser.parse_executable_document()
}

/// Parses a GraphQL document that's restricted to executable
/// definitions with custom feature flags passed as `features`.
pub fn parse_executable_with_features(
    source: &str,
    source_location: SourceLocationKey,
    features: ParserFeatures,
) -> DiagnosticsResult<ExecutableDocument> {
    parse_executable_with_error_recovery_and_parser_features(source, source_location, features)
        .into()
}

/// Parses a GraphQL document that's restricted to type system definitions
/// including schema definition, type definitions and type system extensions.
pub fn parse_schema_document(
    source: &str,
    source_location: SourceLocationKey,
) -> DiagnosticsResult<SchemaDocument> {
    let features = ParserFeatures::default();
    let parser = Parser::new(source, source_location, features);
    parser.parse_schema_document()
}

pub fn parse_field_definition(
    source: &str,
    source_location: SourceLocationKey,
    offset: u32,
) -> DiagnosticsResult<FieldDefinition> {
    let features = ParserFeatures::default();
    let parser = Parser::with_offset(source, source_location, features, offset);
    parser.parse_field_definition()
}

pub fn parse_field_definition_stub(
    source: &str,
    source_location: SourceLocationKey,
    offset: u32,
) -> DiagnosticsResult<FieldDefinitionStub> {
    let features = ParserFeatures::default();
    let parser = Parser::with_offset(source, source_location, features, offset);
    parser.parse_field_definition_stub()
}

/// Parses a GraphQL type, such as `ID` or `[User!]!`.
pub fn parse_type(
    source: &str,
    source_location: SourceLocationKey,
    offset: u32,
) -> DiagnosticsResult<TypeAnnotation> {
    let features = ParserFeatures::default();
    let parser = Parser::with_offset(source, source_location, features, offset);
    parser.parse_type()
}

/// Parses a GraphQL identifier, such as `foo` or `User`.
pub fn parse_identifier(
    source: &str,
    source_location: SourceLocationKey,
    offset: u32,
) -> DiagnosticsResult<Identifier> {
    let features = ParserFeatures::default();
    let parser = Parser::with_offset(source, source_location, features, offset);
    parser.parse_identifier_result()
}

/// Parses a GraphQL identifier followed optionally by `implements Foo & Bar`.
pub fn parse_identifier_and_implements_interfaces(
    source: &str,
    source_location: SourceLocationKey,
    offset: u32,
) -> DiagnosticsResult<(Identifier, Vec<Identifier>)> {
    let features = ParserFeatures::default();
    let parser = Parser::with_offset(source, source_location, features, offset);
    parser.parse_identifier_and_implements_interfaces_result()
}

/// Parses a GraphQL document that's restricted to type system definitions
/// including schema definition, type definitions and type system extensions.
pub fn parse_directive(
    source: &str,
    source_location: SourceLocationKey,
    offset: u32,
) -> DiagnosticsResult<Directive> {
    let features = ParserFeatures::default();
    let parser = Parser::with_offset(source, source_location, features, offset);
    parser.parse_directive()
}