Use serde_rusqlite to deserialize run row in get_bootstrap
https://claude.ai/code/session_01GfccpUReesMY5Rb4FhajhT
diff --git a/Cargo.lock b/Cargo.lock
index a1e26dc..d1bab60 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2557,6 +2557,7 @@ dependencies = [
"sentry",
"serde",
"serde_json",
+ "serde_rusqlite",
"serde_yaml_ng",
"shell-words",
"tempfile",
@@ -3113,6 +3114,16 @@ dependencies = [
"serde_core",
]
+[[package]]
+name = "serde_rusqlite"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee252eeacd2dc6d0dcbc7b3e5a17f4c1bae19e811e843a9c7698f849b1611d67"
+dependencies = [
+ "rusqlite",
+ "serde_core",
+]
+
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
diff --git a/quire-server/Cargo.toml b/quire-server/Cargo.toml
index 267b5ae..84925d5 100644
--- a/quire-server/Cargo.toml
+++ b/quire-server/Cargo.toml
@@ -38,6 +38,7 @@ clap_complete = "*"
rand = "*"
rusqlite = { version = "*", features = ["bundled"] }
rusqlite_migration = "*"
+serde_rusqlite = "*"
sentry = { workspace = true }
serde_yaml_ng = "*"
shell-words = "*"
diff --git a/quire-server/src/quire/web/api.rs b/quire-server/src/quire/web/api.rs
index 9d3e8c9..a1da2f0 100644
--- a/quire-server/src/quire/web/api.rs
+++ b/quire-server/src/quire/web/api.rs
@@ -7,6 +7,8 @@
use std::collections::HashMap;
use std::path::PathBuf;
+use serde::Deserialize;
+
use axum::extract::{FromRequestParts, Path as AxumPath, State};
use axum::http::StatusCode;
use axum::middleware::Next;
@@ -63,6 +65,16 @@ impl From<rusqlite::Error> for ApiError {
}
}
+impl From<serde_rusqlite::Error> for ApiError {
+ fn from(e: serde_rusqlite::Error) -> Self {
+ ApiError::Db(rusqlite::Error::FromSqlConversionFailure(
+ 0,
+ rusqlite::types::Type::Text,
+ Box::new(e),
+ ))
+ }
+}
+
impl IntoResponse for ApiError {
fn into_response(self) -> Response {
match self {
@@ -151,39 +163,35 @@ async fn get_bootstrap(
tokio::task::spawn_blocking(move || -> std::result::Result<Bootstrap, ApiError> {
let db = crate::db::open(&quire.db_path())?;
- let (sha, ref_name, pushed_at_ms, git_dir_opt, sentry_trace_id, state): (
- String,
- String,
- i64,
- Option<String>,
- Option<String>,
- String,
- ) = db.query_row(
- "SELECT sha, ref_name, pushed_at_ms, git_dir, sentry_trace_id, state
+ #[derive(Deserialize)]
+ struct RunRow {
+ sha: String,
+ ref_name: String,
+ pushed_at_ms: i64,
+ git_dir: Option<String>,
+ sentry_trace_id: Option<String>,
+ state: String,
+ }
+
+ let row: RunRow = db
+ .prepare(
+ "SELECT sha, ref_name, pushed_at_ms, git_dir, sentry_trace_id, state
FROM runs WHERE id = ?1",
- rusqlite::params![run_id],
- |row| {
- Ok((
- row.get(0)?,
- row.get(1)?,
- row.get(2)?,
- row.get(3)?,
- row.get(4)?,
- row.get(5)?,
- ))
- },
- )?;
+ )?
+ .query_and_then(rusqlite::params![run_id], serde_rusqlite::from_row)?
+ .next()
+ .ok_or(rusqlite::Error::QueryReturnedNoRows)??;
- if state != "pending" {
+ if row.state != "pending" {
return Err(ApiError::Gone);
}
- let git_dir: PathBuf = git_dir_opt.map(PathBuf::from).ok_or(ApiError::NotFound)?;
+ let git_dir: PathBuf = row.git_dir.map(PathBuf::from).ok_or(ApiError::NotFound)?;
let meta = RunMeta {
- sha,
- r#ref: ref_name,
- pushed_at: jiff::Timestamp::from_millisecond(pushed_at_ms)
+ sha: row.sha,
+ r#ref: row.ref_name,
+ pushed_at: jiff::Timestamp::from_millisecond(row.pushed_at_ms)
.expect("db stores valid timestamps"),
};
@@ -196,7 +204,7 @@ async fn get_bootstrap(
Ok(Bootstrap {
meta,
git_dir,
- sentry_trace_id,
+ sentry_trace_id: row.sentry_trace_id,
})
})
.await