Use serde_rusqlite to deserialize run row in get_bootstrap
https://claude.ai/code/session_01GfccpUReesMY5Rb4FhajhT
change
commit 416b33d89c5fb587db8694d1a740b3fc13232827
author Claude <noreply@anthropic.com>
date
parent b4cad32f
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