fn validate_transform_fragment_spread()

in compiler/crates/relay-transforms/src/match_/match_transform.rs [254:520]


    fn validate_transform_fragment_spread(
        &mut self,
        spread: &FragmentSpread,
    ) -> Result<Transformed<Selection>, Diagnostic> {
        let module_directive = spread
            .directives
            .named(MATCH_CONSTANTS.module_directive_name);

        // Only process the fragment spread with @module
        if let Some(module_directive) = module_directive {
            let should_use_no_inline = self.no_inline_flag.is_enabled_for(spread.fragment.item);
            // @arguments on the fragment spread is not allowed without @no_inline
            if !should_use_no_inline && !spread.arguments.is_empty() {
                return Err(Diagnostic::error(
                    ValidationMessage::InvalidModuleWithArguments,
                    spread.arguments[0].name.location,
                ));
            }

            // no other directives are allowed
            if spread.directives.len() != 1
                && !(spread.directives.len() == 2
                    && spread
                        .directives
                        .named(DEFER_STREAM_CONSTANTS.defer_name)
                        .is_some())
            {
                // allow @defer and @module in typegen transforms
                return Err(Diagnostic::error(
                    ValidationMessage::InvalidModuleWithAdditionalDirectives {
                        spread_name: spread.fragment.item,
                    },
                    spread.directives[1].name.location,
                ));
            }

            self.validate_js_module_type(spread.fragment.location)?;

            let fragment = self.program.fragment(spread.fragment.item).unwrap();

            // Disallow @inline on fragments whose spreads are decorated with @module
            if let Some(inline_data_directive) = fragment.directives.named(*INLINE_DIRECTIVE_NAME) {
                return Err(Diagnostic::error(
                    ValidationMessage::InvalidModuleWithInline,
                    module_directive.name.location,
                )
                .annotate(
                    "@inline directive location",
                    inline_data_directive.name.location,
                ));
            }

            let module_directive_name_argument =
                get_module_directive_name_argument(module_directive, spread.fragment.location)?;
            let (js_field_id, has_js_field_id_arg, has_js_field_branch_arg) =
                self.get_js_field_args(fragment, spread)?;

            let parent_name = self.path.last();
            // self.match_directive_key_argument is the value passed to @match(key: "...") that we
            // most recently encountered while traversing the operation, or the document name
            let match_directive_key_argument = self
                .match_directive_key_argument
                .unwrap_or(self.document_name);

            // If this is the first time we are encountering @module at this path, also ensure
            // that we have not previously encountered another @module associated with the same
            // match_directive_key_argument.
            //
            // This ensures that all of the @module's associated with a given @match occur at
            // a single path.
            let matches = match self.matches_for_path.get_mut(&self.path) {
                None => {
                    let existing_match_with_key = self.matches_for_path.values().any(|entry| {
                        entry.match_directive_key_argument == match_directive_key_argument
                    });
                    if existing_match_with_key {
                        let parent_name = parent_name.expect("Cannot have @module selections at multiple paths unless the selections are within fields.");
                        return Err(Diagnostic::error(
                            ValidationMessage::InvalidModuleSelectionWithoutKey {
                                document_name: self.document_name,
                                parent_name: parent_name.item,
                            },
                            parent_name.location,
                        ));
                    }
                    self.matches_for_path.insert(
                        self.path.clone(),
                        Matches {
                            match_directive_key_argument,
                            types: Default::default(),
                        },
                    );
                    self.matches_for_path.get_mut(&self.path).unwrap()
                }
                Some(matches) => matches,
            };

            if match_directive_key_argument != matches.match_directive_key_argument {
                // The user can't override the key locally (per @module),
                // so this is just an internal sanity check
                panic!(
                    "Invalid @module selection: expected all selections at path '{:?} to have the same 'key', got '{}' and '{}'.",
                    &self.path, match_directive_key_argument, matches.match_directive_key_argument
                );
            }

            // For each @module we encounter at this path, we also keep track of the type condition of the
            // spread fragment (i.e. the type from which the fragment makes selections), and ensure that
            // if we have multiple @module directives at the same path and type condition, they are
            // exactly the same: i.e. are on a spread of the same fragment, and have the same
            // value passed to name in @module(name: "...").
            //
            // This is required because, as currently set up, the resulting payload will have fields
            // __typeName, __module_operation_FragmentName and __module_component_FragmentName.
            // If multiple @module directives shared a fragment spread's type condition, but differed:
            // - in which fragment was spread (resulting in a different __module_operation_FragmentName
            //   value in the response), or
            // - in the @module(name: "...") parameter (resulting in a different
            //   __module_component_FragmentName value in the response)
            // The server could nonetheless only return the correct 3D payload for one of the @module's.
            let previous_match_for_type = matches.types.get(&fragment.type_condition);
            if let Some(previous_match_for_type) = previous_match_for_type {
                if previous_match_for_type.fragment.item != spread.fragment.item
                    || previous_match_for_type.module_directive_name_argument
                        != module_directive_name_argument
                {
                    return Err(Diagnostic::error(
                        ValidationMessage::InvalidModuleSelectionMultipleMatches {
                            type_name: self.program.schema.get_type_name(fragment.type_condition),
                            alias_path: self
                                .path
                                .iter()
                                .map(|with_loc| with_loc.item.lookup())
                                .collect::<Vec<&str>>()
                                .join("."),
                        },
                        spread.fragment.location,
                    )
                    .annotate(
                        "related location",
                        previous_match_for_type.fragment.location,
                    ));
                }
            } else {
                matches.types.insert(
                    fragment.type_condition,
                    TypeMatch {
                        fragment: spread.fragment,
                        module_directive_name_argument,
                    },
                );
            }

            // Done validating. Build out the resulting fragment spread.
            let mut component_field_arguments = vec![build_string_literal_argument(
                MATCH_CONSTANTS.js_field_module_arg,
                module_directive_name_argument,
                module_directive.name.location,
            )];

            let mut normalization_name = get_normalization_operation_name(spread.fragment.item);
            normalization_name.push_str(".graphql");
            let mut operation_field_arguments = vec![build_string_literal_argument(
                MATCH_CONSTANTS.js_field_module_arg,
                normalization_name.intern(),
                module_directive.name.location,
            )];

            let module_id = if self.path.is_empty() {
                self.document_name
            } else {
                let mut str = String::new();
                str.push_str(self.document_name.lookup());
                for path in &self.path {
                    str.push('.');
                    str.push_str(path.item.lookup());
                }
                str.intern()
            };

            if has_js_field_id_arg {
                let id_arg = build_string_literal_argument(
                    MATCH_CONSTANTS.js_field_id_arg,
                    module_id,
                    module_directive.name.location,
                );
                component_field_arguments.push(id_arg.clone());
                operation_field_arguments.push(id_arg);
            }

            if has_js_field_branch_arg && self.enable_3d_branch_arg_generation {
                let branch_arg = build_string_literal_argument(
                    MATCH_CONSTANTS.js_field_branch_arg,
                    self.program
                        .schema
                        .as_ref()
                        .get_type_name(fragment.type_condition),
                    module_directive.name.location,
                );
                component_field_arguments.push(branch_arg.clone());
                operation_field_arguments.push(branch_arg);
            }

            let component_field = Selection::ScalarField(Arc::new(ScalarField {
                alias: Some(WithLocation::new(
                    module_directive.name.location,
                    format!("__module_component_{}", match_directive_key_argument).intern(),
                )),
                definition: WithLocation::new(module_directive.name.location, js_field_id),
                arguments: component_field_arguments,
                directives: Default::default(),
            }));

            let operation_field = Selection::ScalarField(Arc::new(ScalarField {
                alias: Some(WithLocation::new(
                    module_directive.name.location,
                    format!("__module_operation_{}", match_directive_key_argument).intern(),
                )),
                definition: WithLocation::new(module_directive.name.location, js_field_id),
                arguments: operation_field_arguments,
                directives: Default::default(),
            }));

            let next_spread = Selection::FragmentSpread(Arc::new(FragmentSpread {
                directives: spread
                    .directives
                    .iter()
                    .filter(|directive| {
                        directive.name.item != MATCH_CONSTANTS.module_directive_name
                    })
                    .cloned()
                    .collect(),
                ..spread.clone()
            }));

            if should_use_no_inline {
                self.no_inline_fragments
                    .entry(fragment.name.item)
                    .or_insert_with(std::vec::Vec::new)
                    .push(self.document_name);
            }
            Ok(Transformed::Replace(Selection::InlineFragment(Arc::new(
                InlineFragment {
                    type_condition: Some(fragment.type_condition),
                    directives: vec![],
                    selections: vec![Selection::InlineFragment(Arc::new(InlineFragment {
                        type_condition: Some(fragment.type_condition),
                        directives: vec![
                            ModuleMetadata {
                                key: match_directive_key_argument,
                                module_id,
                                module_name: module_directive_name_argument,
                                source_document_name: self.document_name,
                                fragment_name: spread.fragment.item,
                                location: module_directive.name.location,
                                no_inline: should_use_no_inline,
                            }
                            .into(),
                        ],
                        selections: vec![next_spread, operation_field, component_field],
                    }))],
                },
            ))))
        } else {
            Ok(Transformed::Keep)
        }
    }