use crate::*;
pub(crate) type DiffMap<K, V> = HashMap<K, (Option<V>, Option<V>)>;
#[derive(Default)]
pub(crate) struct DiffState<'a> {
pub(crate) schema_definition_map: [Option<ast::SchemaDefinition<'a>>; 2],
pub(crate) types_map: DiffMap<&'a str, DefinitionKind>,
pub(crate) fields_map: DiffMap<[&'a str; 2], Option<ast::Type<'a>>>,
pub(crate) interface_impls: DiffMap<&'a str, Vec<&'a str>>,
pub(crate) arguments_map: DiffMap<[&'a str; 3], (ast::Type<'a>, Option<ast::Value<'a>>)>,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[repr(u8)]
pub(crate) enum DefinitionKind {
Directive,
Enum,
InputObject,
Interface,
Object,
Scalar,
Union,
}
impl DiffState<'_> {
pub(crate) fn into_changes(self) -> Vec<Change> {
let DiffState {
schema_definition_map,
types_map,
fields_map,
arguments_map,
interface_impls,
} = self;
let mut changes = Vec::new();
push_schema_definition_changes(schema_definition_map, &mut changes);
push_interface_implementer_changes(interface_impls, &mut changes);
push_definition_changes(&types_map, &mut changes);
push_field_changes(&fields_map, &types_map, &mut changes);
push_argument_changes(&fields_map, &arguments_map, &mut changes);
changes.sort();
changes
}
}
fn push_interface_implementer_changes(interface_impls: DiffMap<&str, Vec<&str>>, changes: &mut Vec<Change>) {
for (implementer, (src, target)) in &interface_impls {
let src = src.as_deref().unwrap_or(&[]);
let target = target.as_deref().unwrap_or(&[]);
for src_impl in src {
if !target.contains(src_impl) {
changes.push(Change {
path: format!("{}.{}", src_impl, implementer),
kind: ChangeKind::RemoveInterfaceImplementation,
});
}
}
for target_impl in target {
if !src.contains(target_impl) {
changes.push(Change {
path: format!("{}.{}", target_impl, implementer),
kind: ChangeKind::AddInterfaceImplementation,
});
}
}
}
}
fn push_argument_changes(
fields_map: &DiffMap<[&str; 2], Option<ast::Type<'_>>>,
arguments_map: &DiffMap<[&str; 3], (ast::Type<'_>, Option<ast::Value<'_>>)>,
changes: &mut Vec<Change>,
) {
for (path @ [type_name, field_name, _arg_name], (src, target)) in arguments_map {
let path = *path;
let parent_is_gone = || matches!(&fields_map[&[*type_name, *field_name]], (Some(_), None));
let kind = match (src, target) {
(None, None) => unreachable!(),
(None, Some(_)) => Some(ChangeKind::AddFieldArgument),
(Some(_), None) if !parent_is_gone() => Some(ChangeKind::RemoveFieldArgument),
(Some(_), None) => None,
(Some((src_type, src_default)), Some((target_type, target_default))) => {
if src_type != target_type {
changes.push(Change {
path: path.join("."),
kind: ChangeKind::ChangeFieldArgumentType,
});
}
match (src_default, target_default) {
(None, Some(_)) => Some(ChangeKind::AddFieldArgumentDefault),
(Some(_), None) => Some(ChangeKind::RemoveFieldArgumentDefault),
(Some(a), Some(b)) if a != b => Some(ChangeKind::ChangeFieldArgumentDefault),
_ => None,
}
}
};
if let Some(kind) = kind {
changes.push(Change {
path: path.join("."),
kind,
});
}
}
}
fn push_field_changes(
fields_map: &DiffMap<[&str; 2], Option<ast::Type<'_>>>,
types_map: &DiffMap<&str, DefinitionKind>,
changes: &mut Vec<Change>,
) {
for (path @ [type_name, _field_name], (src, target)) in fields_map {
let parent = &types_map[type_name];
let parent_is_gone = || matches!(parent, (Some(_), None));
let definition = match parent {
(None, None) => unreachable!(),
(Some(a), Some(b)) if a != b => {
continue; }
(Some(_), None) | (None, Some(_)) => continue,
(Some(kind), Some(_)) => *kind,
};
let change_kind = match (src, target, definition) {
(None, None, _) | (_, _, DefinitionKind::Scalar | DefinitionKind::Directive) => {
unreachable!()
}
(None, Some(_), DefinitionKind::Object | DefinitionKind::Interface | DefinitionKind::InputObject) => {
Some(ChangeKind::AddField)
}
(None, Some(_), DefinitionKind::Enum) => Some(ChangeKind::AddEnumValue),
(Some(_), None, DefinitionKind::Enum) if !parent_is_gone() => Some(ChangeKind::RemoveEnumValue),
(None, Some(_), DefinitionKind::Union) => Some(ChangeKind::AddUnionMember),
(Some(_), None, DefinitionKind::Union) if !parent_is_gone() => Some(ChangeKind::RemoveUnionMember),
(Some(_), None, DefinitionKind::Object | DefinitionKind::Interface | DefinitionKind::InputObject)
if !parent_is_gone() =>
{
Some(ChangeKind::RemoveField)
}
(
Some(ty_a),
Some(ty_b),
DefinitionKind::Object | DefinitionKind::InputObject | DefinitionKind::Interface,
) if ty_a.as_ref() != ty_b.as_ref() => Some(ChangeKind::ChangeFieldType),
(Some(_), None, _) => None,
(Some(_), Some(_), _) => None,
};
if let Some(kind) = change_kind {
changes.push(Change {
path: path.join("."),
kind,
});
}
}
}
fn push_definition_changes(
types_map: &HashMap<&str, (Option<DefinitionKind>, Option<DefinitionKind>)>,
changes: &mut Vec<Change>,
) {
for (name, entries) in types_map {
match entries {
(None, None) => unreachable!(),
(None, Some(definition)) => push_added_type(name, *definition, changes),
(Some(definition), None) => push_removed_type(name, *definition, changes),
(Some(a), Some(b)) if a != b => {
push_removed_type(name, *a, changes);
push_added_type(name, *b, changes);
}
(Some(_), Some(_)) => (),
}
}
}
fn push_added_type(name: &str, kind: DefinitionKind, changes: &mut Vec<Change>) {
let change_kind = match kind {
DefinitionKind::Directive => ChangeKind::AddDirectiveDefinition,
DefinitionKind::Enum => ChangeKind::AddEnum,
DefinitionKind::InputObject => ChangeKind::AddInputObject,
DefinitionKind::Interface => ChangeKind::AddInterface,
DefinitionKind::Object => ChangeKind::AddObjectType,
DefinitionKind::Scalar => ChangeKind::AddScalar,
DefinitionKind::Union => ChangeKind::AddUnion,
};
changes.push(Change {
path: name.to_owned(),
kind: change_kind,
});
}
fn push_removed_type(name: &str, kind: DefinitionKind, changes: &mut Vec<Change>) {
let change_kind = match kind {
DefinitionKind::Directive => ChangeKind::RemoveDirectiveDefinition,
DefinitionKind::Enum => ChangeKind::RemoveEnum,
DefinitionKind::InputObject => ChangeKind::RemoveInputObject,
DefinitionKind::Interface => ChangeKind::RemoveInterface,
DefinitionKind::Object => ChangeKind::RemoveObjectType,
DefinitionKind::Scalar => ChangeKind::RemoveScalar,
DefinitionKind::Union => ChangeKind::RemoveUnion,
};
changes.push(Change {
path: name.to_owned(),
kind: change_kind,
});
}
fn push_schema_definition_changes(
schema_definition_map: [Option<ast::SchemaDefinition<'_>>; 2],
changes: &mut Vec<Change>,
) {
match schema_definition_map {
[None, None] => (),
[Some(src), Some(target)] => {
let [src_query, src_mutation, src_subscription] =
[src.query_type(), src.mutation_type(), src.subscription_type()];
let [target_query, target_mutation, target_subscription] =
[target.query_type(), target.mutation_type(), target.subscription_type()];
if src_query != target_query {
changes.push(Change {
path: String::new(),
kind: ChangeKind::ChangeQueryType,
});
}
if src_mutation != target_mutation {
changes.push(Change {
path: String::new(),
kind: ChangeKind::ChangeMutationType,
});
}
if src_subscription != target_subscription {
changes.push(Change {
path: String::new(),
kind: ChangeKind::ChangeSubscriptionType,
});
}
}
[None, Some(_)] => changes.push(Change {
path: String::new(),
kind: ChangeKind::AddSchemaDefinition,
}),
[Some(_), None] => changes.push(Change {
path: String::new(),
kind: ChangeKind::RemoveSchemaDefinition,
}),
}
}