fn build_fragment_variable_definitions()

in compiler/crates/graphql-ir/src/signatures.rs [178:382]


fn build_fragment_variable_definitions(
    schema: &SDLSchema,
    fragment: &graphql_syntax::FragmentDefinition,
    directive: &graphql_syntax::Directive,
    enable_provided_variables: &FeatureFlag,
) -> DiagnosticsResult<Vec<VariableDefinition>> {
    if let Some(arguments) = &directive.arguments {
        Ok(arguments
            .items
            .iter()
            .map(|variable_arg| {
                if let graphql_syntax::Value::Constant(graphql_syntax::ConstantValue::Object(
                    object,
                )) = &variable_arg.value
                {
                    let mut type_arg = None;
                    let mut default_arg = None;
                    let mut unused_local_variable_arg = None;
                    let mut provider_arg = None;
                    let mut directives_arg = None;
                    let mut extra_items = Vec::new();
                    for item in &object.items {
                        let name = item.name.value;
                        if name == *TYPE {
                            type_arg = Some(item);
                        } else if name == *DEFAULT_VALUE {
                            default_arg = Some(item);
                        } else if name == *UNUSED_LOCAL_VARIABLE_DEPRECATED {
                            unused_local_variable_arg = Some(item);
                        } else if name == *DIRECTIVES {
                            directives_arg = Some(item);
                        } else if name == *PROVIDER {
                            if !enable_provided_variables.is_enabled_for(fragment.name.value) {
                                return Err(vec![Diagnostic::error(
                                    format!("Invalid usage of provided variable: this feature is gated and currently set to {}",
                                    enable_provided_variables),
                                    fragment.location.with_span(item.span),
                                )]);
                            }
                            provider_arg = Some(item);
                        } else {
                            extra_items.push(item);
                        }
                    }
                    // Check that no extraneous keys were supplied
                    if !extra_items.is_empty() {
                        return Err(extra_items
                            .iter()
                            .map(|item| {
                                Diagnostic::error(
                                    ValidationMessage::InvalidArgumentDefinitionsKey(
                                        item.name.value,
                                    ),
                                    fragment.location.with_span(item.span),
                                )
                            })
                            .collect());
                    }

                    let variable_name = &variable_arg.name;
                    let mut directives = Vec::new();

                    // Convert variable type, validate that it's an input type
                    let type_ = get_argument_type(schema, fragment.location, type_arg, object)?;
                    if !type_.inner().is_input_type() {
                        return Err(Diagnostic::error(
                            ValidationMessage::ExpectedFragmentArgumentToHaveInputType(
                                schema.get_type_name(type_.inner()),
                            ),
                            fragment.location.with_span(variable_arg.value.span()),
                        )
                        .into());
                    }

                    if let Some(unused_local_variable_arg) = unused_local_variable_arg {
                        if !matches!(
                            unused_local_variable_arg,
                            graphql_syntax::ConstantArgument {
                                value: graphql_syntax::ConstantValue::Boolean(
                                    graphql_syntax::BooleanNode { value: true, .. }
                                ),
                                ..
                            }
                        ) {
                            return Err(vec![Diagnostic::error(
                                ValidationMessage::InvalidUnusedFragmentVariableSuppressionArg,
                                fragment
                                    .location
                                    .with_span(unused_local_variable_arg.value.span()),
                            )]);
                        }
                        directives.push(crate::Directive {
                            name: WithLocation::new(
                                fragment.location.with_span(unused_local_variable_arg.span),
                                *UNUSED_LOCAL_VARIABLE_DEPRECATED,
                            ),
                            arguments: Vec::new(),
                            data: None,
                        });
                    }

                    if let Some(provider_arg) = provider_arg {
                        let provider_module_name = provider_arg.value.get_string_literal().ok_or_else(|| {
                            vec![Diagnostic::error(
                                ValidationMessage::LiteralStringArgumentExpectedForDirective{arg_name: *PROVIDER, directive_name: *ARGUMENT_DEFINITION },
                                fragment
                                    .location
                                    .with_span(provider_arg.value.span()),
                            )]
                        })?;
                        if let Some(default_arg_) = default_arg {
                            return Err(vec![Diagnostic::error(
                                ValidationMessage::ProvidedVariableIncompatibleWithDefaultValue{argument_name: variable_name.value},
                                fragment
                                    .location
                                    .with_span(provider_arg.span),
                            ).annotate("Default value declared here",
                            fragment
                            .location
                            .with_span(default_arg_.span))]);
                        }
                        directives.push(crate::Directive {
                            name: WithLocation::new(
                                fragment.location.with_span(provider_arg.span),
                                ProvidedVariableMetadata::directive_name(),
                            ),
                            arguments: Vec::new(),
                            data: Some(Box::new(ProvidedVariableMetadata{
                                module_name: provider_module_name,
                                original_variable_name: variable_name.value
                            })),
                        });
                    }

                    if let Some(directives_arg) = directives_arg {
                        if let graphql_syntax::ConstantValue::List(items) = &directives_arg.value {
                            for item in &items.items {
                                if let graphql_syntax::ConstantValue::String(directive_string) = item {
                                    let ast_directive = graphql_syntax::parse_directive(
                                        directive_string.value.lookup(),
                                        // We currently don't have the ability to pass offset locations
                                        // to the parser call, so we first use a generated location and
                                        // later override it with an approximation.
                                        SourceLocationKey::generated(),
                                    )
                                    .map_err(|mut diagnostics| {
                                        for diagnostic in &mut diagnostics {
                                            diagnostic.override_location(fragment.location.with_span(directive_string.token.span));
                                        }
                                        diagnostics
                                    })?;
                                    let directive = build_directive(
                                        schema,
                                        &ast_directive,
                                        graphql_syntax::DirectiveLocation::VariableDefinition,
                                        // We currently don't have the ability to pass offset locations
                                        // to the parser call, so we first use a generated location and
                                        // later override it with an approximation.
                                        Location::generated(),
                                    )
                                    .map_err(|mut diagnostics| {
                                        for diagnostic in &mut diagnostics {
                                            diagnostic.override_location(fragment.location.with_span(directive_string.token.span));
                                        }
                                        diagnostics
                                    })?;
                                    directives.push(directive);
                                } else {
                                    return Err(vec![Diagnostic::error(
                                        ValidationMessage::ArgumentDefinitionsDirectivesNotStringListLiteral,
                                        fragment.location.with_span(item.span()),
                                    )]);
                                }
                            }
                        } else {
                            return Err(vec![Diagnostic::error(
                                ValidationMessage::ArgumentDefinitionsDirectivesNotStringListLiteral,
                                fragment.location.with_span(directives_arg.value.span()),
                            )]);
                        }
                    }

                    let default_value =
                        get_default_value(schema, fragment.location, default_arg, &type_)?;

                    Ok(VariableDefinition {
                        name: variable_name
                            .name_with_location(fragment.location.source_location()),
                        type_,
                        directives,
                        default_value,
                    })
                } else {
                    Err(Diagnostic::error(
                        ValidationMessage::ExpectedArgumentDefinitionToBeObject(),
                        fragment.location.with_span(variable_arg.value.span()),
                    )
                    .into())
                }
            })
            .collect::<DiagnosticsResult<Vec<VariableDefinition>>>()?)
    } else {
        Ok(Default::default())
    }
}