1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
//! This module provides error handling for WarpGrapher.
use serde_yaml;
use std::error;
use std::fmt::{Display, Formatter, Result};
use std::sync::mpsc::RecvError;

/// Categories of Warpgrapher errors.
#[derive(Debug)]
pub enum ErrorKind {
    /// Returned when the server attempts to listen on an address/port
    /// combination that is already bound on the system.
    AddrInUse(std::io::Error),

    /// Returned when the server attempts to listen on an address not
    /// assigned to any of the system's interfaces.
    AddrNotAvailable(std::io::Error),

    /// Returned when `WarpgrapherClient` receives an HTTP response which
    /// contains a body that is not valid JSON. All GraphQL responses
    /// including errors are expected to be in the form of valid JSON.
    ClientReceivedInvalidJson,

    /// Returned when `WarpgrapherClient` is unable to submit a request to
    /// the server (network error or server error).
    ClientRequestFailed(String),

    /// Returned when `WarpgrapherClient` receives a valid JSON response
    /// that does not contain the expected 'data' or 'errors' objects.
    ClientRequestUnexpectedPayload(serde_json::Value),

    /// Returned when a custom endpoint defines an inline custom input type
    /// with a name that conflicts with a GraphQL scalar
    ConfigEndpointInputTypeScalarNameError(String, String),

    /// Returned when a custom endpoint defines an inline custom output type
    /// with a name that conflicts with a GraphQL scalar
    ConfigEndpointOutputTypeScalarNameError(String, String),

    /// Returned when a warpgrapher type is defined with a name that conflicts with
    /// a GraphQL scalar
    ConfigTypeScalarNameError(String, String),

    /// Returned when a `WarpgrapherConfig` struct attempts to be initialized
    /// from a config file that cannot be found on disk.  
    ConfigNotFound(std::io::Error),

    /// Returned when a `WarpgrapherConfig` fails to deserialize because the
    /// provided data does not match the expected config spec
    ConfigDeserializationError(serde_yaml::Error),

    /// Returned when attempting to compose configs with different versions
    ConfigVersionMismatchError(String, i32),

    /// Returned when two warpgrapher types are defined with the same name
    ConfigTypeDuplicateError(String, String),

    /// Returned when two warpgrapher endpoints are defined with the same name
    ConfigEndpointDuplicateError(String, String),

    /// Returned when a warpgrapher endpoint defines for an input or output a
    /// type that does not exist
    ConfigEndpointMissingTypeError(String, String),

    /// Returned when `WarpgrapherServer` fails to build a pool for the cypher
    /// connection manager.
    CouldNotBuildCypherPool(r2d2::Error),

    /// Returned when the internal resolver logic cannot infer the correct warpgrapher type
    /// that corresponds to data queried from the database.
    /// Note: This error should never be thrown. This is a critical error. If you see it,
    /// please report it to the warpgrapher team.
    CouldNotInferType,

    /// Returned when an environment variable cannot be found
    EnvironmentVariableNotFound(String),

    /// Returned when there is a mismatch in the expected internal representation of a
    /// warpgrapher type
    /// Note: This error should never be thrown. This is a critical error. If you see it,
    /// please report it to the warpgrapher team.
    InputTypeMismatch(String),

    /// Returned when trying to perform on operation on a type that cannot support it.
    /// For example, this would be returned when trying to load a relationship from
    /// an input, as input types don't have relationships. This is a critical internal
    /// error. If you see it, please report it to the warpgrapher team.
    InvalidType(String),

    /// Returned when received GraphQL input contains an invalid property
    /// Note: This error should never be thrown. This is a critical error. If you see it,
    /// please report it to the warpgrapher team.
    InvalidProperty(String),

    /// Returned when there is a mismatch in the expected internal representation of a
    /// warpgrapher type
    /// Note: This error should never be thrown. This is a critical error. If you see it,
    /// please report it to the warpgrapher team.
    InvalidPropertyType(String),

    /// Returned during config validation if the config defines a Type or Rel property with the name 'ID'.
    /// ID is a reserved prop used by the Warpgrapher internals.
    InvalidPropNameID(String),

    /// Returned when attempts to serialize/deserialize a struct to/from JSON fails
    JsonError(serde_json::error::Error),

    /// Returned when a resolver's input is missing an expected argument. Given
    /// GraphQL's type system
    /// Note: This error should never be thrown. This is a critical error. If you see it,
    /// please report it to the warpgrapher team.
    MissingArgument(String),

    /// Returned when warpgrapher missing a property expected for that node or rel type.
    /// This could occur if a node or relationship is created with a direct cypher query
    /// in a custom resolver or external to Warpgrapher altogether, without creating an
    /// 'id' property with a UUID. This could also occur if a schema change makes a
    /// previously optional and nullable property mandatory and non-nullable.
    MissingProperty(String, Option<String>),

    /// Returned when the cursor points outside of the bounds of the data returned
    /// Note: This error should never be thrown. This is a critical error. If you see it,
    /// please report it to the warpgrapher team.
    MissingResultSet,

    /// Returned when there is a mismatch between the data returned from the database
    /// and what the internal representation of a warpgrapher type expects
    MissingResultElement(String),

    /// Returned at start time when warpgrapher is dynamically generating a GraphQL schema
    /// from the config but there is a mismatch in the schema.
    /// Note: This error should never be thrown. This is a critical error. If you see it,
    /// please report it to the warpgrapher team.
    MissingSchemaElement(String),

    /// Returned when a field (prop or rel) of a node has been determined to be a DynamicScalar
    /// type and will attempt to execute a custom resolver for that field. This error is
    /// returned if the resolver is not defined for that DynamicScalar type field.
    /// Note: This error should never be thrown. This is a critical error. If you see it,
    /// please report it to the warpgrapher team.
    FieldMissingResolverError(String, String),

    /// Returned when there is a failure executing a neo4j query and the expected results
    /// from the database are not returned.
    GraphQueryError(rusted_cypher::error::GraphError),

    /// Returned when the output of a GraphQL execution is not a valid JSON.
    /// Note: This error should never be thrown. This is a critical error. If you see it,
    /// please report it to the warpgrapher team.
    GraphQLOutputError(String),

    /// Returned when a resolver attempt to infer relationships between queried data via
    /// a regex match fails
    RegexError,

    /// Returned when a custom endpoint is defined or a resolver is
    /// defined for a field, but the corresponding resolver is not provided.
    ResolverNotFound(String, String),

    /// Returned when a `WarpgrapherServer` tries to shutdown but the server is not
    /// running.
    ServerNotRunning,

    /// Returned when an error is encountered while trying to shutdown a `WarpgrapherServer`
    /// that is supposed to be running.
    ServerShutdownFailed,

    /// Returned when a `WarpgrapherServer` that is already running tries to start.
    ServerAlreadyRunning,

    /// Returned when a `WarpgrapherServer` fails to start.
    ServerStartupFailed(RecvError),

    /// Returned when a custom input validator is defined, but the corresponding
    /// validator is not provided.
    ValidatorNotFound(String, String),

    /// This error is returned by a custom input validator when the validation fails.
    /// This error is converted into a FieldError and returned to the client.
    ValidationError(String),
}

/// Error type for Warpgrapher operations.
///
/// Many errors originate in underlying libraries, but the
/// [`ErrorKind`] wraps these as necessary into
///  Warpgrapher errors.
///
/// [`ErrorKind`]: ./enum.ErrorKind.html
#[derive(Debug)]
pub struct Error {
    pub kind: ErrorKind,
    source: Option<Box<dyn error::Error + Send + Sync>>,
}

impl Error {
    /// Creates a new Warpgrapher error from an [`ErrorKind`] and,
    /// optionally, an arbitrary source error.
    ///
    /// [`ErrorKind`]: ./enum.ErrorKind.html
    ///
    /// # Examples
    ///
    /// ```rust
    /// use warpgrapher::{Error, ErrorKind};
    ///
    /// let e1 = Error::new(ErrorKind::ServerAlreadyRunning, None);
    ///
    /// let s = std::io::Error::new(std::io::ErrorKind::Other, "Oh no!");
    /// let e2 = Error::new(ErrorKind::ServerShutdownFailed, Some(Box::new(s)));
    /// ```
    pub fn new(kind: ErrorKind, source: Option<Box<dyn error::Error + Send + Sync>>) -> Error {
        Error { kind, source }
    }
}

impl Display for Error {
    fn fmt(&self, fmt: &mut Formatter) -> Result {
        write!(
            fmt,
            "ErrorKind: {:#?}, source: {:#?}",
            self.kind, self.source
        )
    }
}

impl error::Error for Error {}

#[cfg(test)]
mod tests {
    use super::{Error, ErrorKind};

    /// Passes if a new error with no wrapped source error is created
    #[test]
    fn new_error() {
        let e = Error::new(ErrorKind::ServerAlreadyRunning, None);

        assert!(std::error::Error::source(&e).is_none());
    }

    /// Passes if an error prints a display string correctly
    #[test]
    fn display_fmt() {
        let s = std::io::Error::new(std::io::ErrorKind::Other, "oh no!");
        let e = Error::new(ErrorKind::ServerShutdownFailed, Some(Box::new(s)));

        assert!(&format!("{}", e).starts_with("ErrorKind: ServerShutdownFailed, source: Some"));
    }
}