graphql_composition/
diagnostics.rs

1//! Composition warnings and errors.
2
3use std::fmt;
4
5/// Warnings and errors produced by composition.
6#[derive(Default, Debug)]
7pub struct Diagnostics(Vec<Diagnostic>);
8
9impl Diagnostics {
10    /// Is any of the diagnostics fatal, i.e. a hard error?
11    pub fn any_fatal(&self) -> bool {
12        self.0.iter().any(|diagnostic| diagnostic.severity.is_error())
13    }
14
15    /// Is there any diagnostic warning or error
16    pub fn is_empty(&self) -> bool {
17        self.0.is_empty()
18    }
19
20    /// Iterate over all diagnostics.
21    pub fn iter(&self) -> impl Iterator<Item = &Diagnostic> {
22        self.0.iter()
23    }
24
25    /// Iterate non-fatal diagnostics.
26    pub fn iter_warnings(&self) -> impl Iterator<Item = &str> {
27        self.0
28            .iter()
29            .filter(|diagnostic| diagnostic.severity.is_warning())
30            .map(|diagnostic| diagnostic.message.as_str())
31    }
32
33    /// Iterate fatal diagnostics.
34    pub fn iter_errors(&self) -> impl Iterator<Item = &str> {
35        self.0
36            .iter()
37            .filter(|diagnostic| diagnostic.severity.is_error())
38            .map(|diagnostic| diagnostic.message.as_str())
39    }
40
41    pub(crate) fn clone_all_from(&mut self, other: &Diagnostics) {
42        self.0.extend(other.0.iter().cloned())
43    }
44
45    /// Iterate over all diagnostic messages.
46    pub fn iter_messages(&self) -> impl Iterator<Item = &str> {
47        self.0.iter().map(|diagnostic| diagnostic.message.as_str())
48    }
49
50    pub(crate) fn push_composite_schemas_source_schema_validation_error(
51        &mut self,
52        source_schema_name: &str,
53        message: impl fmt::Display,
54        error_code: CompositeSchemasSourceSchemaValidationErrorCode,
55    ) {
56        self.0.push(Diagnostic {
57            message: format!("[{source_schema_name}] {message}"),
58            severity: error_code.severity(),
59            error_code: Some(error_code.into()),
60        });
61    }
62
63    pub(crate) fn push_composite_schemas_pre_merge_validation_error(
64        &mut self,
65        message: String,
66        error_code: CompositeSchemasPreMergeValidationErrorCode,
67    ) {
68        self.0.push(Diagnostic {
69            message,
70            severity: error_code.severity(),
71            error_code: Some(error_code.into()),
72        });
73    }
74
75    pub(crate) fn push_composite_schemas_post_merge_validation_error(
76        &mut self,
77        message: String,
78        error_code: CompositeSchemasPostMergeValidationErrorCode,
79    ) {
80        self.0.push(Diagnostic {
81            message,
82            severity: error_code.severity(),
83            error_code: Some(error_code.into()),
84        });
85    }
86
87    pub(crate) fn push_fatal(&mut self, message: String) {
88        self.0.push(Diagnostic {
89            message,
90            severity: Severity::Error,
91            error_code: None,
92        });
93    }
94
95    pub(crate) fn push_warning(&mut self, message: String) {
96        self.0.push(Diagnostic {
97            message,
98            severity: Severity::Warning,
99            error_code: None,
100        });
101    }
102}
103
104/// A composition diagnostic.
105#[derive(Debug, Clone)]
106pub struct Diagnostic {
107    message: String,
108    severity: Severity,
109    error_code: Option<CompositeSchemasErrorCode>,
110}
111
112impl Diagnostic {
113    /// The warning or error message.
114    pub fn message(&self) -> &str {
115        &self.message
116    }
117
118    /// See [Severity].
119    pub fn severity(&self) -> Severity {
120        self.severity
121    }
122
123    /// The composite schemas error code
124    pub fn composite_schemas_error_code(&self) -> Option<CompositeSchemasErrorCode> {
125        self.error_code
126    }
127}
128
129/// The severity of a [Diagnostic].
130#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
131pub enum Severity {
132    /// A fatal error.
133    Error,
134    /// A warning to be displayed to the user.
135    Warning,
136}
137
138impl Severity {
139    /// Returns `true` if the severity is [`Error`].
140    ///
141    /// [`Error`]: Severity::Error
142    #[must_use]
143    pub fn is_error(&self) -> bool {
144        matches!(self, Self::Error)
145    }
146
147    /// Returns `true` if the severity is [`Warning`].
148    ///
149    /// [`Warning`]: Severity::Warning
150    #[must_use]
151    pub fn is_warning(&self) -> bool {
152        matches!(self, Self::Warning)
153    }
154}
155
156/// Composite Schemas spec [error codes](https://graphql.github.io/composite-schemas-spec/draft/#sec-Schema-Composition).
157#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
158#[non_exhaustive]
159pub enum CompositeSchemasErrorCode {
160    /// See [CompositeSchemasSourceSchemaValidationErrorCode]
161    SourceSchema(CompositeSchemasSourceSchemaValidationErrorCode),
162    /// See [CompositeSchemasPreMergeValidationErrorCode]
163    PreMerge(CompositeSchemasPreMergeValidationErrorCode),
164    /// See [CompositeSchemasPostMergeValidationErrorCode]
165    PostMerge(CompositeSchemasPostMergeValidationErrorCode),
166}
167
168impl From<CompositeSchemasPostMergeValidationErrorCode> for CompositeSchemasErrorCode {
169    fn from(v: CompositeSchemasPostMergeValidationErrorCode) -> Self {
170        Self::PostMerge(v)
171    }
172}
173
174impl From<CompositeSchemasSourceSchemaValidationErrorCode> for CompositeSchemasErrorCode {
175    fn from(v: CompositeSchemasSourceSchemaValidationErrorCode) -> Self {
176        Self::SourceSchema(v)
177    }
178}
179
180impl From<CompositeSchemasPreMergeValidationErrorCode> for CompositeSchemasErrorCode {
181    fn from(v: CompositeSchemasPreMergeValidationErrorCode) -> Self {
182        Self::PreMerge(v)
183    }
184}
185
186/// Composite Schemas spec [source schema validation](https://graphql.github.io/composite-schemas-spec/draft/#sec-Validate-Source-Schemas) error codes.
187#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
188#[non_exhaustive]
189pub enum CompositeSchemasSourceSchemaValidationErrorCode {
190    /// https://graphql.github.io/composite-schemas-spec/draft/#sec-Query-Root-Type-Inaccessible
191    QueryRootTypeInaccessible,
192    /// https://graphql.github.io/composite-schemas-spec/draft/#sec-Lookup-Returns-Non-Nullable-Type
193    LookupReturnsNonNullableType,
194    /// https://graphql.github.io/composite-schemas-spec/draft/#sec-Override-from-Self
195    OverrideFromSelf,
196    /// https://graphql.github.io/composite-schemas-spec/draft/#sec-Provides-Directive-in-Fields-Argument
197    ProvidesDirectiveInFieldsArgument,
198}
199
200impl CompositeSchemasSourceSchemaValidationErrorCode {
201    fn severity(&self) -> Severity {
202        use CompositeSchemasSourceSchemaValidationErrorCode::*;
203
204        match self {
205            QueryRootTypeInaccessible | OverrideFromSelf | ProvidesDirectiveInFieldsArgument => Severity::Error,
206
207            LookupReturnsNonNullableType => Severity::Warning,
208        }
209    }
210}
211
212/// Composite Schemas spec [pre-merge validation](https://graphql.github.io/composite-schemas-spec/draft/#sec-Pre-Merge-Validation) error codes.
213#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
214pub enum CompositeSchemasPreMergeValidationErrorCode {
215    /// https://graphql.github.io/composite-schemas-spec/draft/#sec-Type-Kind-Mismatch
216    TypeKindMismatch,
217    /// https://graphql.github.io/composite-schemas-spec/draft/#sec-Override-Source-Has-Override
218    OverrideSourceHasOverride,
219}
220
221impl CompositeSchemasPreMergeValidationErrorCode {
222    fn severity(&self) -> Severity {
223        use CompositeSchemasPreMergeValidationErrorCode::*;
224
225        match self {
226            TypeKindMismatch | OverrideSourceHasOverride => Severity::Error,
227        }
228    }
229}
230
231/// Composite Schemas spec [post-merge validation](https://graphql.github.io/composite-schemas-spec/draft/#sec-Post-Merge-Validation) error codes.
232#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
233pub enum CompositeSchemasPostMergeValidationErrorCode {
234    /// https://graphql.github.io/composite-schemas-spec/draft/#sec-Invalid-Field-Sharing
235    InvalidFieldSharing,
236}
237
238impl CompositeSchemasPostMergeValidationErrorCode {
239    fn severity(&self) -> Severity {
240        use CompositeSchemasPostMergeValidationErrorCode::*;
241
242        match self {
243            InvalidFieldSharing => Severity::Error,
244        }
245    }
246}