Skip to main content

graphql_schema_diff/
lib.rs

1#![doc = include_str!("../README.md")]
2
3pub mod path;
4
5mod change;
6mod patch;
7mod state;
8mod traverse_schemas;
9
10pub use self::{
11    change::{Change, ChangeKind, Span},
12    patch::{PatchedSchema, patch},
13};
14
15use self::state::*;
16use cynic_parser::type_system as ast;
17use std::collections::HashMap;
18
19/// Diff two already parsed ASTs. See [diff()] for the string based API.
20pub fn diff_asts(
21    source: Option<&cynic_parser::TypeSystemDocument>,
22    target: Option<&cynic_parser::TypeSystemDocument>,
23) -> Vec<Change> {
24    let mut state = DiffState::default();
25
26    traverse_schemas::traverse_schemas([source, target], &mut state);
27
28    state.into_changes()
29}
30
31/// Diff two GraphQL schemas.
32pub fn diff(source: &str, target: &str) -> Result<Vec<Change>, cynic_parser::Error> {
33    let [source, target] = [source, target].map(|sdl| -> Result<_, cynic_parser::Error> {
34        if sdl.trim().is_empty() {
35            Ok(None)
36        } else {
37            Ok(Some(cynic_parser::parse_type_system_document(sdl)?))
38        }
39    });
40    let [source, target] = [source?, target?];
41
42    Ok(diff_asts(source.as_ref(), target.as_ref()))
43}
44
45/// Resolve the spans from [Change]s and the corresponding schemas.
46pub fn resolve_spans<'a: 'b, 'b>(
47    source: &'a str,
48    target: &'a str,
49    changes: &'b [Change],
50) -> impl Iterator<Item = &'a str> + 'b {
51    changes.iter().map(move |change| {
52        let relevant_schema = match change.kind {
53            ChangeKind::ChangeQueryType => target,
54            ChangeKind::ChangeMutationType => target,
55            ChangeKind::ChangeSubscriptionType => target,
56            ChangeKind::RemoveObjectType => source,
57            ChangeKind::AddObjectType => target,
58            ChangeKind::AddInterfaceImplementation => target,
59            ChangeKind::RemoveInterfaceImplementation => source,
60            ChangeKind::ChangeFieldType => target,
61            ChangeKind::RemoveField => source,
62            ChangeKind::AddField => target,
63            ChangeKind::AddUnion => target,
64            ChangeKind::RemoveUnion => source,
65            ChangeKind::AddUnionMember => target,
66            ChangeKind::RemoveUnionMember => source,
67            ChangeKind::AddEnum => target,
68            ChangeKind::RemoveEnum => source,
69            ChangeKind::AddEnumValue => target,
70            ChangeKind::RemoveEnumValue => source,
71            ChangeKind::AddScalar => target,
72            ChangeKind::RemoveScalar => source,
73            ChangeKind::AddInterface => target,
74            ChangeKind::RemoveInterface => source,
75            ChangeKind::AddDirectiveDefinition => target,
76            ChangeKind::RemoveDirectiveDefinition => source,
77            ChangeKind::AddSchemaDefinition => target,
78            ChangeKind::AddSchemaExtension => target,
79            ChangeKind::RemoveSchemaExtension => source,
80            ChangeKind::RemoveSchemaDefinition => source,
81            ChangeKind::AddInputObject => target,
82            ChangeKind::RemoveInputObject => source,
83            ChangeKind::AddFieldArgument => target,
84            ChangeKind::RemoveFieldArgument => source,
85            ChangeKind::AddFieldArgumentDefault => target,
86            ChangeKind::RemoveFieldArgumentDefault => source,
87            ChangeKind::ChangeFieldArgumentDefault => target,
88            ChangeKind::ChangeFieldArgumentType => target,
89            ChangeKind::AddDirective => target,
90            ChangeKind::RemoveDirective => source,
91            ChangeKind::ChangeDescription => target,
92        };
93
94        &relevant_schema[change.span]
95    })
96}
97
98/// Resolve the source spans from [Change]s and the source schema, for change kinds that have a
99/// meaningful source span (see [Change::source_span]). Source spans are always relative to the
100/// `source` schema, since a [Change] always goes from source to target. Yields `None` for changes
101/// without a source span.
102pub fn resolve_source_spans<'a: 'b, 'b>(
103    source: &'a str,
104    changes: &'b [Change],
105) -> impl Iterator<Item = Option<&'a str>> + 'b {
106    changes.iter().map(move |change| Some(&source[change.source_span?]))
107}