Skip to main content

graphql_schema_diff/
patch.rs

1mod directives;
2mod paths;
3mod schema_definitions;
4mod type_definitions;
5
6use self::paths::Paths;
7use crate::{Change, ChangeKind};
8
9const INDENTATION: &str = "  ";
10
11/// Apply a diff to a source schema. The spans in the diff from the original target schema must have been resolved by [resolve_spans()] and not sorted.
12pub fn patch<S>(source: &str, diff: &[Change], resolved_spans: &[S]) -> Result<PatchedSchema, cynic_parser::Error>
13where
14    S: AsRef<str>,
15{
16    let parsed = Some(source)
17        .filter(|source| !source.trim().is_empty()) // FIXME: doesn't take comments into account
18        .map(cynic_parser::parse_type_system_document)
19        .transpose()?
20        .unwrap_or_default();
21
22    let mut schema = String::with_capacity(source.len() / 2);
23    let paths = Paths::new(diff, resolved_spans, source);
24
25    for change in paths.iter_top_level() {
26        match change.kind() {
27            ChangeKind::AddSchemaDefinition
28            | ChangeKind::AddObjectType
29            | ChangeKind::AddUnion
30            | ChangeKind::AddEnum
31            | ChangeKind::AddScalar
32            | ChangeKind::AddInterface
33            | ChangeKind::AddDirectiveDefinition
34            | ChangeKind::AddInputObject => {
35                schema.push_str(change.resolved_str());
36                schema.push_str("\n\n");
37            }
38            ChangeKind::AddSchemaExtension => {
39                schema.push_str("extend ");
40                schema.push_str(change.resolved_str());
41                schema.push_str("\n\n");
42            }
43            _ => (),
44        }
45    }
46
47    for definition in parsed.definitions() {
48        match definition {
49            cynic_parser::type_system::Definition::Schema(def) => {
50                schema_definitions::patch_schema_definition(
51                    def,
52                    DefinitionOrExtension::Definition,
53                    &mut schema,
54                    &paths,
55                );
56            }
57            cynic_parser::type_system::Definition::SchemaExtension(def) => {
58                schema_definitions::patch_schema_definition(def, DefinitionOrExtension::Extension, &mut schema, &paths);
59            }
60            cynic_parser::type_system::Definition::Type(ty) => {
61                type_definitions::patch_type_definition(ty, DefinitionOrExtension::Definition, &mut schema, &paths);
62            }
63            cynic_parser::type_system::Definition::TypeExtension(ty) => {
64                type_definitions::patch_type_definition(ty, DefinitionOrExtension::Extension, &mut schema, &paths);
65            }
66            cynic_parser::type_system::Definition::Directive(directive_definition) => {
67                directives::patch_directive_definition(directive_definition, &mut schema, &paths);
68            }
69        }
70    }
71
72    Ok(PatchedSchema { schema })
73}
74
75enum DefinitionOrExtension {
76    Extension,
77    Definition,
78}
79
80impl DefinitionOrExtension {
81    /// Returns `true` if the definition or extension is [`Extension`].
82    ///
83    /// [`Extension`]: DefinitionOrExtension::Extension
84    #[must_use]
85    fn is_extension(&self) -> bool {
86        matches!(self, Self::Extension)
87    }
88
89    /// Returns `true` if the definition or extension is [`Definition`].
90    ///
91    /// [`Definition`]: DefinitionOrExtension::Definition
92    #[must_use]
93    fn is_definition(&self) -> bool {
94        matches!(self, Self::Definition)
95    }
96}
97
98/// A schema patched with [patch()].
99pub struct PatchedSchema {
100    schema: String,
101}
102
103impl PatchedSchema {
104    /// Turn into just the patched schema.
105    pub fn into_schema(self) -> String {
106        self.schema
107    }
108
109    /// The patched schema.
110    pub fn schema(&self) -> &str {
111        &self.schema
112    }
113}