Skip to content
This repository was archived by the owner on Oct 18, 2023. It is now read-only.

Commit f9a70c9

Browse files
bors[bot]honzasp
andauthored
Merge #314
314: Add error codes to Hrana errors r=MarinPostma a=honzasp Error codes are short machine-readable error descriptions. Co-authored-by: Jan Špaček <[email protected]>
2 parents edad968 + f8b070f commit f9a70c9

File tree

9 files changed

+103
-3
lines changed

9 files changed

+103
-3
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/HRANA_SPEC.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,13 +178,14 @@ other hand, the client should always receive messages, to avoid deadlock.
178178
```typescript
179179
type Error = {
180180
"message": string,
181+
"code"?: string | null,
181182
}
182183
```
183184
184185
When a server refuses to accept a client `hello` or fails to process a
185186
`request`, it responds with a message that describes the error. The `message`
186-
field contains an English human-readable description of the error. The protocol
187-
will be extended with machine-readable error codes in the future.
187+
field contains an English human-readable description of the error. The `code`
188+
contains a machine-readable error code.
188189
189190
If either peer detects that the protocol has been violated, it should close the
190191
WebSocket with an appropriate WebSocket close code and reason. Some examples of

sqld/src/auth.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,24 @@ pub fn parse_jwt_key(data: &str) -> Result<jsonwebtoken::DecodingKey> {
174174
}
175175
}
176176

177+
impl AuthError {
178+
pub fn code(&self) -> &'static str {
179+
match self {
180+
Self::HttpAuthHeaderMissing => "AUTH_HTTP_HEADER_MISSING",
181+
Self::HttpAuthHeaderInvalid => "AUTH_HTTP_HEADER_INVALID",
182+
Self::HttpAuthHeaderUnsupportedScheme => "AUTH_HTTP_HEADER_UNSUPPORTED_SCHEME",
183+
Self::BasicNotAllowed => "AUTH_BASIC_NOT_ALLOWED",
184+
Self::BasicRejected => "AUTH_BASIC_REJECTED",
185+
Self::JwtMissing => "AUTH_JWT_MISSING",
186+
Self::JwtNotAllowed => "AUTH_JWT_NOT_ALLOWED",
187+
Self::JwtInvalid => "AUTH_JWT_INVALID",
188+
Self::JwtExpired => "AUTH_JWT_EXPIRED",
189+
Self::JwtImmature => "AUTH_JWT_IMMATURE",
190+
Self::Other => "AUTH_FAILED",
191+
}
192+
}
193+
}
194+
177195
#[cfg(test)]
178196
mod tests {
179197
use super::*;

sqld/src/batch/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,11 @@ fn eval_cond(ctx: &Ctx, cond: &proto::BatchCond) -> Result<bool> {
9191
.try_fold(false, |x, cond| eval_cond(ctx, cond).map(|y| x | y))?,
9292
})
9393
}
94+
95+
impl BatchError {
96+
pub fn code(&self) -> &'static str {
97+
match self {
98+
Self::CondBadStep => "BATCH_COND_BAD_STEP",
99+
}
100+
}
101+
}

sqld/src/batch/stmt.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,53 @@ fn stmt_error_from_sqld_error(sqld_error: SqldError) -> Result<StmtError, SqldEr
153153
pub fn proto_error_from_stmt_error(error: &StmtError) -> hrana::proto::Error {
154154
hrana::proto::Error {
155155
message: error.to_string(),
156+
code: error.code().into(),
157+
}
158+
}
159+
160+
impl StmtError {
161+
pub fn code(&self) -> &'static str {
162+
match self {
163+
Self::SqlParse { .. } => "SQL_PARSE_ERROR",
164+
Self::SqlNoStmt => "SQL_NO_STATEMENT",
165+
Self::SqlManyStmts => "SQL_MANY_STATEMENTS",
166+
Self::ArgsInvalid { .. } => "ARGS_INVALID",
167+
Self::ArgsBothPositionalAndNamed => "ARGS_BOTH_POSITIONAL_AND_NAMED",
168+
Self::TransactionTimeout => "TRANSACTION_TIMEOUT",
169+
Self::TransactionBusy => "TRANSACTION_BUSY",
170+
Self::SqliteError { source, .. } => sqlite_error_code(source.code),
171+
Self::SqlInputError { .. } => "SQL_INPUT_ERROR",
172+
}
173+
}
174+
}
175+
176+
fn sqlite_error_code(code: rusqlite::ffi::ErrorCode) -> &'static str {
177+
match code {
178+
rusqlite::ErrorCode::InternalMalfunction => "SQLITE_INTERNAL",
179+
rusqlite::ErrorCode::PermissionDenied => "SQLITE_PERM",
180+
rusqlite::ErrorCode::OperationAborted => "SQLITE_ABORT",
181+
rusqlite::ErrorCode::DatabaseBusy => "SQLITE_BUSY",
182+
rusqlite::ErrorCode::DatabaseLocked => "SQLITE_LOCKED",
183+
rusqlite::ErrorCode::OutOfMemory => "SQLITE_NOMEM",
184+
rusqlite::ErrorCode::ReadOnly => "SQLITE_READONLY",
185+
rusqlite::ErrorCode::OperationInterrupted => "SQLITE_INTERRUPT",
186+
rusqlite::ErrorCode::SystemIoFailure => "SQLITE_IOERR",
187+
rusqlite::ErrorCode::DatabaseCorrupt => "SQLITE_CORRUPT",
188+
rusqlite::ErrorCode::NotFound => "SQLITE_NOTFOUND",
189+
rusqlite::ErrorCode::DiskFull => "SQLITE_FULL",
190+
rusqlite::ErrorCode::CannotOpen => "SQLITE_CANTOPEN",
191+
rusqlite::ErrorCode::FileLockingProtocolFailed => "SQLITE_PROTOCOL",
192+
rusqlite::ErrorCode::SchemaChanged => "SQLITE_SCHEMA",
193+
rusqlite::ErrorCode::TooBig => "SQLITE_TOOBIG",
194+
rusqlite::ErrorCode::ConstraintViolation => "SQLITE_CONSTRAINT",
195+
rusqlite::ErrorCode::TypeMismatch => "SQLITE_MISMATCH",
196+
rusqlite::ErrorCode::ApiMisuse => "SQLITE_MISUSE",
197+
rusqlite::ErrorCode::NoLargeFileSupport => "SQLITE_NOLFS",
198+
rusqlite::ErrorCode::AuthorizationForStatementDenied => "SQLITE_AUTH",
199+
rusqlite::ErrorCode::ParameterOutOfRange => "SQLITE_RANGE",
200+
rusqlite::ErrorCode::NotADatabase => "SQLITE_NOTADB",
201+
rusqlite::ErrorCode::Unknown => "SQLITE_UNKNOWN",
202+
_ => "SQLITE_UNKNOWN",
156203
}
157204
}
158205

sqld/src/hrana/conn.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ fn downcast_error(err: anyhow::Error) -> Result<proto::Error> {
232232
match err.downcast_ref::<session::ResponseError>() {
233233
Some(error) => Ok(proto::Error {
234234
message: error.to_string(),
235+
code: error.code().into(),
235236
}),
236237
None => Err(err),
237238
}

sqld/src/hrana/proto.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,5 @@ pub struct BatchResp {
7979
#[derive(Serialize, Debug)]
8080
pub struct Error {
8181
pub message: String,
82+
pub code: String,
8283
}

sqld/src/hrana/session.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,3 +184,16 @@ async fn stream_respond<F>(
184184
};
185185
let _: Result<_, _> = stream_hnd.job_tx.send(job).await;
186186
}
187+
188+
impl ResponseError {
189+
pub fn code(&self) -> &'static str {
190+
match self {
191+
Self::Auth { source } => source.code(),
192+
Self::StreamNotFound { .. } => "STREAM_NOT_FOUND",
193+
Self::StreamExists { .. } => "STREAM_EXISTS",
194+
Self::StreamNotOpen { .. } => "STREAM_NOT_OPEN",
195+
Self::Batch(err) => err.code(),
196+
Self::Stmt(err) => err.code(),
197+
}
198+
}
199+
}

sqld/src/http/hrana_over_http.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ fn error_response(err: ResponseError) -> hyper::Response<hyper::Body> {
140140
status,
141141
&hrana::proto::Error {
142142
message: err.to_string(),
143+
code: err.code().into(),
143144
},
144145
)
145146
}
@@ -155,3 +156,13 @@ fn json_response<T: Serialize>(
155156
.body(hyper::Body::from(body))
156157
.unwrap()
157158
}
159+
160+
impl ResponseError {
161+
pub fn code(&self) -> &'static str {
162+
match self {
163+
Self::BadRequestBody { .. } => "HTTP_BAD_REQUEST_BODY",
164+
Self::Stmt(err) => err.code(),
165+
Self::Batch(err) => err.code(),
166+
}
167+
}
168+
}

0 commit comments

Comments
 (0)