use apollo_compiler::schema::ExtendedType;
use apollo_compiler::ty;
use crate::error::FederationError;
use crate::error::MultipleFederationErrors;
use crate::error::SingleFederationError;
use crate::schema::FederationSchema;
use crate::schema::ListSizeDirective;
pub(crate) fn validate_list_size_directives(
schema: &FederationSchema,
errors: &mut MultipleFederationErrors,
) -> Result<(), FederationError> {
for list_size_directive in schema.list_size_directive_applications()? {
match list_size_directive {
Ok(list_size) => {
validate_applied_to_list(&list_size, errors);
validate_assumed_size_not_negative(&list_size, errors);
validate_slicing_arguments_are_valid_integers(&list_size, errors);
validate_sized_fields_are_valid_lists(schema, &list_size, errors);
}
Err(e) => errors.push(e),
}
}
Ok(())
}
fn validate_applied_to_list(list_size: &ListSizeDirective, errors: &mut MultipleFederationErrors) {
let has_sized_fields = list_size
.directive
.sized_fields
.as_ref()
.is_some_and(|s| !s.is_empty());
if !has_sized_fields && !list_size.target.ty.is_list() {
errors
.errors
.push(SingleFederationError::ListSizeAppliedToNonList {
message: format!(
"\"{}.{}\" is not a list",
list_size.parent_type, list_size.target.name
),
});
}
}
fn validate_assumed_size_not_negative(
list_size: &ListSizeDirective,
errors: &mut MultipleFederationErrors,
) {
if let Some(size) = list_size.directive.assumed_size
&& size < 0
{
errors
.errors
.push(SingleFederationError::ListSizeInvalidAssumedSize {
message: format!(
"Assumed size of \"{}.{}\" cannot be negative",
list_size.parent_type, list_size.target.name
),
});
}
}
fn validate_slicing_arguments_are_valid_integers(
list_size: &ListSizeDirective,
errors: &mut MultipleFederationErrors,
) {
let Some(slicing_argument_names) = list_size.directive.slicing_argument_names.as_ref() else {
return;
};
for arg_name in slicing_argument_names {
if let Some(slicing_argument) = list_size.target.argument_by_name(arg_name.as_str()) {
if *slicing_argument.ty != ty!(Int) && *slicing_argument.ty != ty!(Int!) {
errors
.errors
.push(SingleFederationError::ListSizeInvalidSlicingArgument {
message: format!(
"Slicing argument \"{}.{}({}:)\" must be Int or Int!",
list_size.parent_type, list_size.target.name, arg_name,
),
});
}
} else {
errors
.errors
.push(SingleFederationError::ListSizeInvalidSlicingArgument {
message: format!(
"Slicing argument \"{arg_name}\" is not an argument of \"{}.{}\"",
list_size.parent_type, list_size.target.name
),
});
}
}
}
fn validate_sized_fields_are_valid_lists(
schema: &FederationSchema,
list_size: &ListSizeDirective,
errors: &mut MultipleFederationErrors,
) {
let Some(sized_field_names) = list_size.directive.sized_fields.as_ref() else {
return;
};
let target_type = list_size.target.ty.inner_named_type();
let fields = match schema.schema().types.get(target_type) {
Some(ExtendedType::Object(obj)) => &obj.fields,
Some(ExtendedType::Interface(itf)) => &itf.fields,
_ => {
errors
.errors
.push(SingleFederationError::ListSizeInvalidSizedField {
message: format!(
"Sized fields cannot be used because \"{target_type}\" is not a composite type"
),
});
return;
}
};
for field_name in sized_field_names {
if let Some(field) = fields.get(field_name.as_str()) {
if !field.ty.is_list() {
errors
.errors
.push(SingleFederationError::ListSizeAppliedToNonList {
message: format!(
"Sized field \"{target_type}.{field_name}\" is not a list"
),
});
}
} else {
errors
.errors
.push(SingleFederationError::ListSizeInvalidSizedField {
message: format!(
"Sized field \"{field_name}\" is not a field on type \"{target_type}\""
),
})
}
}
}