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: CompositeSchemasErrorCode,
55    ) {
56        self.0.push(Diagnostic {
57            message: format!("[{source_schema_name}] {message}"),
58            severity: error_code.severity(),
59            error_code: Some(error_code),
60        });
61    }
62
63    pub(crate) fn push_fatal(&mut self, message: String) {
64        self.0.push(Diagnostic {
65            message,
66            severity: Severity::Error,
67            error_code: None,
68        });
69    }
70
71    pub(crate) fn push_warning(&mut self, message: String) {
72        self.0.push(Diagnostic {
73            message,
74            severity: Severity::Warning,
75            error_code: None,
76        });
77    }
78}
79
80/// A composition diagnostic.
81#[derive(Debug, Clone)]
82pub struct Diagnostic {
83    message: String,
84    severity: Severity,
85    error_code: Option<CompositeSchemasErrorCode>,
86}
87
88impl Diagnostic {
89    /// The warning or error message.
90    pub fn message(&self) -> &str {
91        &self.message
92    }
93
94    /// See [Severity].
95    pub fn severity(&self) -> Severity {
96        self.severity
97    }
98
99    /// The composite schemas error code
100    pub(crate) fn composite_shemas_error_code(&self) -> Option<CompositeSchemasErrorCode> {
101        self.error_code
102    }
103}
104
105/// The severity of a [Diagnostic].
106#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
107pub enum Severity {
108    /// A fatal error.
109    Error,
110    /// A warning to be displayed to the user.
111    Warning,
112}
113
114impl Severity {
115    /// Returns `true` if the severity is [`Error`].
116    ///
117    /// [`Error`]: Severity::Error
118    #[must_use]
119    pub fn is_error(&self) -> bool {
120        matches!(self, Self::Error)
121    }
122
123    /// Returns `true` if the severity is [`Warning`].
124    ///
125    /// [`Warning`]: Severity::Warning
126    #[must_use]
127    pub fn is_warning(&self) -> bool {
128        matches!(self, Self::Warning)
129    }
130}
131
132#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
133pub(crate) enum CompositeSchemasErrorCode {
134    /// https://graphql.github.io/composite-schemas-spec/draft/#sec-Query-Root-Type-Inaccessible
135    QueryRootTypeInaccessible,
136    /// https://graphql.github.io/composite-schemas-spec/draft/#sec-Lookup-Returns-Non-Nullable-Type
137    LookupReturnsNonNullableType,
138}
139
140impl CompositeSchemasErrorCode {
141    fn severity(&self) -> Severity {
142        match self {
143            CompositeSchemasErrorCode::QueryRootTypeInaccessible => Severity::Error,
144            CompositeSchemasErrorCode::LookupReturnsNonNullableType => Severity::Warning,
145        }
146    }
147}