use cynic_parser::type_system::{Directive, DirectiveDefinition};
use crate::ChangeKind;
use super::paths::Paths;
pub(super) fn patch_directive_definition<T: AsRef<str>>(
directive_definition: DirectiveDefinition<'_>,
schema: &mut String,
paths: &Paths<'_, T>,
) {
if paths
.iter_exact([directive_definition.name(), "", "", ""])
.any(|change| matches!(change.kind(), ChangeKind::RemoveDirectiveDefinition))
{
return;
}
let span = directive_definition.span();
schema.push_str(&paths.source()[span.start..span.end]);
schema.push_str("\n\n");
}
pub(in crate::patch) enum DirectiveContext<'a> {
Type(&'a str),
Field(&'a str, &'a str),
Argument(&'a str, &'a str, &'a str),
}
pub(in crate::patch) fn patch_directives<'a, T>(
directives: impl Iterator<Item = Directive<'a>>,
schema: &mut String,
paths: &Paths<'_, T>,
context: DirectiveContext<'_>,
) where
T: AsRef<str>,
{
let changes: Vec<_> = paths.directive_usage_changes_at(context).collect();
let removed: Vec<(&str, usize)> = changes
.iter()
.filter(|c| matches!(c.kind(), ChangeKind::RemoveDirective))
.filter_map(|c| parse_directive_name_and_index(c.path()))
.collect();
let mut per_name_idx: std::collections::HashMap<&str, usize> = std::collections::HashMap::new();
for directive in directives {
let name = directive.name();
let idx = *per_name_idx.entry(name).or_default();
per_name_idx.insert(name, idx + 1);
if !removed.iter().any(|&(n, i)| n == name && i == idx) {
render_directive(directive, schema, paths);
}
}
for change in changes.iter().filter(|c| matches!(c.kind(), ChangeKind::AddDirective)) {
schema.push(' ');
schema.push_str(change.resolved_str());
}
}
fn parse_directive_name_and_index(path: &str) -> Option<(&str, usize)> {
let segment = path.rsplit('.').next()?;
let without_at = segment.strip_prefix('@')?;
let bracket = without_at.rfind('[')?;
let name = &without_at[..bracket];
let idx: usize = without_at[bracket + 1..].strip_suffix(']')?.parse().ok()?;
Some((name, idx))
}
fn render_directive<T: AsRef<str>>(directive: Directive<'_>, schema: &mut String, paths: &Paths<'_, T>) {
schema.push_str(" @");
schema.push_str(directive.name());
let mut arguments = directive.arguments().peekable();
if arguments.peek().is_none() {
return;
}
schema.push('(');
while let Some(argument) = arguments.next() {
let span = argument.span();
schema.push_str(&paths.source()[span.start..span.end]);
if arguments.peek().is_some() {
schema.push_str(", ");
}
}
schema.push(')');
}