in compiler/crates/relay-transforms/src/match_/match_transform.rs [522:702]
fn validate_transform_linked_field_with_match_directive(
&mut self,
field: &LinkedField,
match_directive: &Directive,
) -> Result<Transformed<Selection>, Diagnostic> {
// Validate and keep track of the module key
let field_definition = self.program.schema.field(field.definition.item);
let key_arg = match_directive.arguments.named(MATCH_CONSTANTS.key_arg);
if let Some(arg) = key_arg {
if let Value::Constant(ConstantValue::String(str)) = arg.value.item {
if str.lookup().starts_with(self.document_name.lookup()) {
self.match_directive_key_argument = Some(str);
}
}
if self.match_directive_key_argument.is_none() {
return Err(Diagnostic::error(
ValidationMessage::InvalidMatchKeyArgument {
document_name: self.document_name,
},
arg.value.location,
));
}
}
let previous_parent_type = self.parent_type;
self.parent_type = field_definition.type_.inner();
self.path.push(Path {
location: field.definition.location,
item: field.alias_or_name(&self.program.schema),
});
let next_selections = self.transform_selections(&field.selections);
self.path.pop();
self.parent_type = previous_parent_type;
// The linked field definition should have: 'supported: [String]'
let supported_arg_definition = field_definition
.arguments
.named(MATCH_CONSTANTS.supported_arg);
match supported_arg_definition {
None => {
if key_arg.is_none() {
return Err(Diagnostic::error(
ValidationMessage::InvalidMatchWithNoSupportedArgument,
match_directive.name.location,
));
}
return Ok(if let TransformedValue::Keep = next_selections {
Transformed::Keep
} else {
Transformed::Replace(Selection::LinkedField(Arc::new(LinkedField {
alias: field.alias,
definition: field.definition,
arguments: field.arguments.clone(),
directives: field.directives.clone(),
selections: next_selections.replace_or_else(|| field.selections.clone()),
})))
});
}
Some(supported_arg_definition) => {
let is_supported_string = {
if let TypeReference::List(of) = supported_arg_definition.type_.nullable_type()
{
if let TypeReference::Named(of) = of.nullable_type() {
self.program.schema.is_string(*of)
} else {
false
}
} else {
false
}
};
if !is_supported_string {
return Err(Diagnostic::error(
ValidationMessage::InvalidMatchNotOnNonNullListString {
field_name: field_definition.name.item,
},
field.definition.location,
));
}
}
}
// The linked field should be an abstract type
if !field_definition.type_.inner().is_abstract_type() {
return Err(Diagnostic::error(
ValidationMessage::InvalidMatchNotOnUnionOrInterface {
field_name: field_definition.name.item,
},
field.definition.location,
));
}
// The supported arg shouldn't be defined by the user
let supported_arg = field.arguments.named(MATCH_CONSTANTS.supported_arg);
if let Some(supported_arg) = supported_arg {
return Err(Diagnostic::error(
ValidationMessage::InvalidMatchNoUserSuppliedSupportedArg {
supported_arg: MATCH_CONSTANTS.supported_arg,
},
supported_arg.name.location,
));
}
// Track fragment spread types that has @module
// Validate that there are only `__typename`, and `...spread @module` selections
let mut seen_types = IndexSet::with_hasher(FnvBuildHasher::default());
for selection in &field.selections {
match selection {
Selection::FragmentSpread(field) => {
let has_directive_with_module = field.directives.iter().any(|directive| {
directive.name.item == MATCH_CONSTANTS.module_directive_name
});
if has_directive_with_module {
let fragment = self.program.fragment(field.fragment.item).unwrap();
seen_types.insert(fragment.type_condition);
} else {
self.push_fragment_spread_with_module_selection_err(
field.fragment.location,
match_directive.name.location,
);
}
}
Selection::ScalarField(field) => {
if field.definition.item != self.program.schema.typename_field() {
self.push_fragment_spread_with_module_selection_err(
field.definition.location,
match_directive.name.location,
);
}
}
Selection::LinkedField(field) => self
.push_fragment_spread_with_module_selection_err(
field.definition.location,
match_directive.name.location,
),
// TODO: no location on InlineFragment and Condition yet
_ => self.push_fragment_spread_with_module_selection_err(
field.definition.location,
match_directive.name.location,
),
}
}
if seen_types.is_empty() {
return Err(Diagnostic::error(
ValidationMessage::InvalidMatchNoModuleSelection,
match_directive.name.location,
));
}
let mut next_arguments = field.arguments.clone();
next_arguments.push(Argument {
name: WithLocation::new(field.definition.location, MATCH_CONSTANTS.supported_arg),
value: WithLocation::new(
field.definition.location,
Value::Constant(ConstantValue::List(
seen_types
.into_iter()
.map(|type_| {
ConstantValue::String(self.program.schema.get_type_name(type_))
})
.collect(),
)),
),
});
let mut next_directives = Vec::with_capacity(field.directives.len() - 1);
for directive in &field.directives {
if directive.name.item != MATCH_CONSTANTS.match_directive_name {
next_directives.push(directive.clone());
}
}
Ok(Transformed::Replace(Selection::LinkedField(Arc::new(
LinkedField {
alias: field.alias,
definition: field.definition,
arguments: next_arguments,
directives: next_directives,
selections: next_selections.replace_or_else(|| field.selections.clone()),
},
))))
}