in compiler/crates/relay-transforms/src/assignable_fragment_spread/validate_updatable_directive.rs [135:248]
fn validate_inline_fragments_with_parent(
&self,
parent_field: &LinkedField,
// Cannot accept an iterator here because we need the length of the vec!
fragment_spreads: Vec<&'a InlineFragment>,
) -> DiagnosticsResult<()> {
// If we have no fragment spreads, return early
if fragment_spreads.is_empty() {
return Ok(());
}
// If a linked field contains inline fragments, it must *only* contain inline fragments.
// This is because there exists a limitation of our typegen: if there are fields within
// inline fragments with type refinements, and at the top-level, the parent field is not
// emitted as a disjoint union where the key is the __typename field. Instead, the union
// is flattened and every field is optional. This breaks type safety for updatable fragments,
// as users would be able to assign to scalar fields that are not present on a given type.
let mut errors = vec![];
if parent_field.selections.len() != fragment_spreads.len() {
errors.push(Diagnostic::error(
ValidationMessage::UpdatableOnlyInlineFragments {
outer_type_plural: self.executable_definition_info.unwrap().type_plural,
},
parent_field.definition.location,
));
}
// Furthermore, inline fragments are only allowed if the parent type is an interface or union
let parent_field_id = parent_field.definition.item;
let parent_named_type = self.program.schema.field(parent_field_id).type_.inner();
if !parent_named_type.is_abstract_type() {
errors.push(Diagnostic::error(
ValidationMessage::UpdatableInlineFragmentsOnlyOnInterfacesOrUnions {
outer_type_plural: self.executable_definition_info.unwrap().type_plural,
},
parent_field.definition.location,
));
}
let mut previously_encountered_concrete_types = HashSet::new();
for fragment_spread in fragment_spreads.into_iter() {
// A fragment spread on a linked field is valid iff:
// - it contains a type condition
// - that type condition is for a concrete type
// - that concrete type has not occurred before
// - it contains a typename field with no alias
match fragment_spread.type_condition {
None => errors.push(Diagnostic::error(
ValidationMessage::UpdatableInlineFragmentsRequireTypeConditions {
outer_type_plural: self.executable_definition_info.unwrap().type_plural,
parent_field_type: self.program.schema.get_type_name(parent_named_type),
},
parent_field.definition.location,
)),
Some(type_condition) => {
let type_condition_name = self.program.schema.get_type_name(type_condition);
if type_condition.is_abstract_type() {
errors.push(Diagnostic::error(
ValidationMessage::UpdatableInlineFragmentsTypeConditionsMustBeConcrete {
outer_type_plural: self.executable_definition_info.unwrap().type_plural,
type_condition: type_condition_name,
},
parent_field.definition.location,
))
}
if previously_encountered_concrete_types.contains(&type_condition_name) {
errors.push(Diagnostic::error(
ValidationMessage::UpdatablePreviouslyEncounteredTypeCondition {
outer_type_plural: self
.executable_definition_info
.unwrap()
.type_plural,
type_condition: type_condition_name,
parent_field_alias_or_name: parent_field
.alias_or_name(&self.program.schema),
},
parent_field.definition.location,
));
} else {
previously_encountered_concrete_types.insert(type_condition_name);
}
}
}
// Attempt to find a typename field with no alias, in order to guarantee that
// the linked field (parent_field) with fragment spreads is written by
// relay-typegen as a disjoint union.
if !fragment_spread.selections.iter().any(|selection| {
if let Selection::ScalarField(scalar_field) = selection {
scalar_field.definition.item == self.program.schema.typename_field()
&& scalar_field.alias.is_none()
} else {
false
}
}) {
errors.push(Diagnostic::error(
ValidationMessage::UpdatableInlineFragmentsMustHaveTypenameFields {
outer_type_plural: self.executable_definition_info.unwrap().type_plural,
parent_field_alias_or_name: parent_field
.alias_or_name(&self.program.schema),
},
parent_field.definition.location,
))
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}