grafbase_local_server/
errors.rs

1use axum::http::StatusCode;
2use axum::response::IntoResponse;
3use axum::response::Response;
4use axum::Json;
5use hyper::Error as HyperError;
6use notify::Error as NotifyError;
7use serde_json::json;
8use sqlx::Error as SqlxError;
9use std::io::Error as IoError;
10use std::path::PathBuf;
11use thiserror::Error;
12use tokio::task::JoinError;
13
14use crate::custom_resolvers::JavaScriptPackageManager;
15
16#[derive(Error, Debug)]
17pub enum ServerError {
18    /// returned if the current directory path cannot be read
19    #[error("could not create path '{0}' for the embedded server files")]
20    CreateDir(PathBuf),
21
22    /// returned if any of the embedded worker files cannot be written to disk
23    #[error("could not write an embedded server file: {0}")]
24    WriteFile(String),
25
26    /// returned if the version of the existing worker files cannot be read
27    #[error("could not read the previously extracted embedded file versions")]
28    ReadVersion,
29
30    /// returned if a connection to the sqlite database could not be made
31    #[error("could not connect to the sqlite database: {0}")]
32    ConnectToDatabase(SqlxError),
33
34    /// returned if an sqlite database file cannot be created
35    #[error("could not create an sqlite database file: {0}")]
36    CreateDatabase(SqlxError),
37
38    /// returned if an sqlite query returns an error
39    #[error("could not query the sqlite database: {0}")]
40    QueryDatabase(SqlxError),
41
42    /// returned if sqlx returns an unknown error
43    #[error("encountered an unknown sqlite error: {0}")]
44    UnknownSqliteError(SqlxError),
45
46    /// returned if the sqlite bridge cannot be started
47    #[error("the bridge api encountered an error: {0}")]
48    BridgeApi(#[from] HyperError),
49
50    /// returned if the miniflare command returns an error
51    #[error("miniflare encountered an error: {0}")]
52    MiniflareCommandError(IoError),
53
54    /// returned if the miniflare command exits unsuccessfully
55    #[error("miniflare encountered an error\ncause:\n{0}")]
56    MiniflareError(String),
57
58    /// returned if the schema parser command returns an error
59    #[error(transparent)]
60    SchemaParserError(IoError),
61
62    /// returned if reading the parser result fails
63    #[error(transparent)]
64    SchemaParserResultRead(IoError),
65
66    /// returned if the schema parser result is invalid JSON
67    #[error("schema parser result is malformed JSON:\n{0}")]
68    SchemaParserResultJson(serde_json::Error),
69
70    /// returned if writing the schema registry fails
71    #[error(transparent)]
72    SchemaRegistryWrite(IoError),
73
74    /// returned if `tempfile::NamedTempFile::new()` fails.
75    #[error("could not create a temporary file for the parser result: {0}")]
76    CreateTemporaryFile(IoError),
77
78    /// returned if a write to a resolver artifact file fails
79    #[error("could not create an output artifact file during a resolver build")]
80    CreateResolverArtifactFile(IoError),
81
82    /// returned if the schema parser command exits unsuccessfully
83    #[error("could not extract the resolver wrapper worker contents")]
84    ExtractResolverWrapperWorkerContents(String),
85
86    /// returned if the schema parser command exits unsuccessfully
87    #[error("could not parse grafbase/schema.graphql\n{0}")]
88    ParseSchema(String),
89
90    #[error("could not find a resolver referenced in the schema under the path {0}.{{js,ts}}")]
91    ResolverDoesNotExist(PathBuf),
92
93    /// returned if any of the npm commands ran during resolver build exits unsuccessfully
94    #[error("{0} encountered an error: {1}")]
95    ResolverPackageManagerCommandError(JavaScriptPackageManager, IoError),
96
97    /// returned if any of the npm commands ran during resolver build exits unsuccessfully
98    #[error("{0} failed with output:\n{1}")]
99    ResolverPackageManagerError(JavaScriptPackageManager, String),
100
101    /// returned if any of the npm commands ran during resolver build exits unsuccessfully
102    #[error("resolver {0} failed to build:\n{1}")]
103    ResolverBuild(String, String),
104
105    /// returned if the user project path is not valid utf-8
106    #[error("non utf-8 path used for project")]
107    ProjectPath,
108
109    /// returned if the user cache path is not valid utf-8
110    #[error("$HOME/.grafbase is a non utf8 path")]
111    CachePath,
112
113    /// returned if the `.grafbase` directory cannot be created
114    #[error("could not create a project cache directory")]
115    CreateCacheDir,
116
117    /// returned if the `.grafbase/database` directory cannot be created
118    #[error("could not create a project database directory\ncaused by: {0}")]
119    CreateDatabaseDir(IoError),
120
121    /// returned if the `.grafbase/database` directory cannot be read
122    #[error("could not read the project database directory\ncaused by: {0}")]
123    ReadDatabaseDir(IoError),
124
125    /// returned if an available port cannot be found for the bridge server
126    #[error("could not find an available port for the bridge server")]
127    AvailablePort,
128
129    /// returned if a spawned task panics
130    #[error(transparent)]
131    SpawnedTaskPanic(#[from] JoinError),
132
133    /// returned if node is not in the user $PATH
134    #[error("Node.js does not seem to be installed")]
135    NodeInPath,
136
137    /// returned if the installed version of node is unsupported
138    #[error("Node.js version {0} is unsupported")]
139    OutdatedNode(String, String),
140
141    /// returned if the installed version of node could not be retreived
142    #[error("Could not retrive the installed version of Node.js")]
143    CheckNodeVersion,
144
145    /// returned if a file watcher could not be initialized or was stopped due to an error
146    #[error("A file watcher encountered an error\ncaused by: {0}")]
147    FileWatcher(#[from] NotifyError),
148}
149
150impl From<SqlxError> for ServerError {
151    fn from(error: SqlxError) -> Self {
152        match error {
153            SqlxError::RowNotFound
154            | SqlxError::TypeNotFound { .. }
155            | SqlxError::ColumnNotFound(_)
156            | SqlxError::ColumnDecode { .. }
157            | SqlxError::ColumnIndexOutOfBounds { .. }
158            | SqlxError::Io(_)
159            | SqlxError::Decode(_)
160            | SqlxError::Database(_) => Self::QueryDatabase(error),
161
162            SqlxError::Configuration(_)
163            | SqlxError::Tls(_)
164            | SqlxError::PoolTimedOut
165            | SqlxError::Protocol(_)
166            | SqlxError::PoolClosed
167            | SqlxError::WorkerCrashed => Self::ConnectToDatabase(error),
168
169            SqlxError::Migrate(_) => Self::CreateDatabase(error),
170
171            _ => Self::UnknownSqliteError(error),
172        }
173    }
174}
175
176impl IntoResponse for ServerError {
177    fn into_response(self) -> Response {
178        let body = Json(json!({
179            "error": self.to_string(),
180        }));
181
182        (StatusCode::INTERNAL_SERVER_ERROR, body).into_response()
183    }
184}