From edcd182d1ac769fd38d0aab0d3db5051c6a8ba1b Mon Sep 17 00:00:00 2001 From: Elijah Date: Tue, 2 Jun 2026 03:00:20 +0000 Subject: [PATCH 01/12] feat!: Add support for rolling back migrations --- Cargo.lock | 2 + cot/Cargo.toml | 2 +- cot/src/cli.rs | 149 +++++++++++++++++++++++++++++++- cot/src/db/migrations.rs | 109 ++++++++++++++++++++++- cot/src/db/migrations/sorter.rs | 14 ++- cot/src/utils/graph.rs | 4 + 6 files changed, 270 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ee5f69c17..cfd9d78ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1675,6 +1675,8 @@ dependencies = [ "chrono", "chrono-tz", "cot", + "tracing", + "tracing-subscriber", ] [[package]] diff --git a/cot/Cargo.toml b/cot/Cargo.toml index 75e9548d8..bc0067e35 100644 --- a/cot/Cargo.toml +++ b/cot/Cargo.toml @@ -24,7 +24,7 @@ blake3.workspace = true bytes.workspace = true chrono = { workspace = true, features = ["alloc", "serde", "clock"] } chrono-tz.workspace = true -clap.workspace = true +clap = {workspace = true, features = ["string"] } cot_core.workspace = true cot_macros.workspace = true deadpool-redis = { workspace = true, features = ["tokio-comp", "rt_tokio_1"], optional = true } diff --git a/cot/src/cli.rs b/cot/src/cli.rs index 97652a2e1..5601b252d 100644 --- a/cot/src/cli.rs +++ b/cot/src/cli.rs @@ -4,18 +4,21 @@ use std::collections::HashMap; use std::path::PathBuf; use std::str::FromStr; +use crate::{Bootstrapper, Error, Result}; use async_trait::async_trait; pub use clap; use clap::{Arg, ArgMatches, Command, value_parser}; +use cot::db::migrations::{MigrationEngine, SyncDynMigration}; +use cot::project::BootstrappedProject; use derive_more::Debug; -use crate::{Bootstrapper, Error, Result}; - const CONFIG_PARAM: &str = "config"; const COLLECT_STATIC_SUBCOMMAND: &str = "collect-static"; const CHECK_SUBCOMMAND: &str = "check"; const LISTEN_PARAM: &str = "listen"; const COLLECT_STATIC_DIR_PARAM: &str = "dir"; +const MIGRATION_GROUP_SUBCOMMAND: &str = "migration"; +const MIGRATION_ROLLBACK_SUBCOMMAND: &str = "rollback"; /// A central point for configuring the default Command Line Interface (CLI) for /// Cot-powered projects. @@ -91,6 +94,12 @@ impl Cli { cli.add_task(Check); cli.add_task(CollectStatic); + let mut migration_group = + CliTaskGroup::new("migration").about("Database migration commands"); + migration_group.add_task(MigrationRollback); + + cli.add_task(migration_group); + cli } @@ -389,6 +398,142 @@ impl CliTask for Check { } } +/// A group of related sub-tasks under a single parent subcommand. +/// +/// Usage: +/// ``` +/// let mut migration = CliTaskGroup::new("migration") +/// .about("Database migration commands"); +/// migration.add_task(MigrationRollback); +/// migration.add_task(MigrationSquash); +/// cli.add_task(migration); +/// ``` +pub struct CliTaskGroup { + name: String, + about: String, + tasks: HashMap>, +} + +impl CliTaskGroup { + pub fn new(name: impl Into) -> Self { + Self { + name: name.into(), + about: String::new(), + tasks: HashMap::new(), + } + } + + pub fn about(mut self, about: impl Into) -> Self { + self.about = about.into(); + self + } + + pub fn add_task(&mut self, task: C) { + let subcommand = task.subcommand(); + let name = subcommand.get_name().to_owned(); + + assert!( + !self.tasks.contains_key(&name), + "Task with name {name} already exists in group '{}'", + self.name + ); + + self.tasks.insert(name, Box::new(task)); + } +} + +#[async_trait(?Send)] +impl CliTask for CliTaskGroup { + fn subcommand(&self) -> Command { + let name = self.name.clone(); + let mut cmd = Command::new(name); + + if !self.about.is_empty() { + cmd = cmd.about(self.about.clone()); + } + + cmd = cmd.subcommand_required(true).arg_required_else_help(true); + for task in self.tasks.values() { + cmd = cmd.subcommand(task.subcommand()); + } + cmd + } + + async fn execute( + &mut self, + matches: &ArgMatches, + bootstrapper: Bootstrapper, + ) -> Result<()> { + let (sub_name, sub_matches) = matches + .subcommand() + .expect("subcommand_required(true) ensures one is present"); + + self.tasks + .get_mut(sub_name) + .expect("clap only matches registered subcommands") + .execute(sub_matches, bootstrapper) + .await + } +} + +struct MigrationRollback; + +#[async_trait(?Send)] +impl CliTask for MigrationRollback { + fn subcommand(&self) -> Command { + Command::new(MIGRATION_ROLLBACK_SUBCOMMAND) + .about("Rollback migrations up to the specified migration file") + .arg( + Arg::new("file") + .help("The migration filename to roll back to (e.g. 0001_create_users)") + .value_name("FILE") + .required(true), + ) + } + + async fn execute( + &mut self, + matches: &ArgMatches, + bootstrapper: Bootstrapper, + ) -> Result<()> { + let file = matches + .get_one::("file") + .expect("required argument"); + + let bootstrapper = bootstrapper + .with_apps() + .with_database() + .await? + .with_cache() + .await? + .boot() + .await?; + + // migrations are currently tied to crates, so we use the crate name as the app name. + // TODO: cli command should take an explicit crate name as arg when workspaces are supported. + let crate_name = bootstrapper.project().cli_metadata().name; + + let BootstrappedProject { + mut context, + mut handler, + mut error_handler, + } = bootstrapper.finish(); + + #[cfg(feature = "db")] + { + let mut migrations: Vec> = Vec::new(); + for app in context.apps() { + migrations.extend(app.migrations()); + } + let migration_engine = MigrationEngine::new(migrations)?; + migration_engine + .rollback(context.database(), file, crate_name) + .await?; + } + Ok(()) + } +} + /// A macro to generate a [`CliMetadata`] struct from the Cargo manifest. #[macro_export] macro_rules! metadata { diff --git a/cot/src/db/migrations.rs b/cot/src/db/migrations.rs index 51dc19761..c81c8fc5d 100644 --- a/cot/src/db/migrations.rs +++ b/cot/src/db/migrations.rs @@ -2,12 +2,13 @@ mod sorter; +pub use cot_macros::migration_op; +use sea_query::{ColumnDef, StringLen}; +use std::collections::{HashMap, HashSet, VecDeque}; use std::fmt; use std::fmt::{Debug, Formatter}; use std::future::Future; - -pub use cot_macros::migration_op; -use sea_query::{ColumnDef, StringLen}; +use std::path::{Path, PathBuf}; use thiserror::Error; use tracing::{Level, info}; @@ -215,6 +216,92 @@ impl MigrationEngine { Ok(()) } + pub async fn rollback(&self, database: &Database, file: &str, app_name: &str) -> Result<()> { + let rollback_plan = self.rollback_plan(file, app_name)?; + + for migration in rollback_plan { + if !Self::is_migration_applied(database, migration).await? { + continue; + } + + let span = tracing::span!( + Level::TRACE, + "rollback_migration", + app_name = migration.app_name(), + migration_name = migration.name() + ); + let _enter = span.enter(); + + info!( + "Rolling back migration {} for app {}", + migration.name(), + migration.app_name() + ); + + for operation in migration.operations().iter().rev() { + operation.backwards(database).await?; + } + + Self::unapply_migration(database, migration).await?; + } + + Ok(()) + } + + fn rollback_plan<'a>( + &'a self, + file: &str, + app_name: &str, + ) -> Result> { + let target_index = self + .migrations + .iter() + .position(|migration| { + migration.app_name() == app_name + && resolve_migration_file_name(migration.name()).contains(&file) + }) + .ok_or_else(|| { + MigrationEngineError::Custom(format!( + "Migration with file name {file} not found for app {app_name}" + )) + })?; + + let mut rollback_indices = HashSet::new(); + // seed all possible migration files in the same app that are dependent on the target migration + rollback_indices.extend( + self.migrations + .iter() + .enumerate() + .filter(|(index, migration)| { + *index > target_index && migration.app_name() == app_name + }) + .map(|(index, _)| index), + ); + + let graph = MigrationSorter::generate_graph(&self.migrations).map_err(|e| { + MigrationEngineError::Custom(format!("Failed to generate migration graph: {}", e)) + })?; + let mut queue = rollback_indices.iter().copied().collect::>(); + // go through every migration dependent on the target migration and add any dependencies to the queue. + // This is also useful in cases where a migration is depended on by migrations in other apps. In this case, + // we need to make sure that we also roll back the migrations in other apps. + while let Some(index) = queue.pop_front() { + for &dependent_index in graph.get_edges(index) { + if rollback_indices.insert(dependent_index) { + queue.push_back(dependent_index); + } + } + } + + Ok(self + .migrations + .iter() + .enumerate() + .rev() + .filter_map(|(index, migration)| rollback_indices.contains(&index).then_some(migration)) + .collect()) + } + async fn is_migration_applied( database: &Database, migration: &MigrationWrapper, @@ -241,6 +328,22 @@ impl MigrationEngine { database.insert(&mut applied_migration).await?; Ok(()) } + + async fn unapply_migration(database: &Database, migration: &MigrationWrapper) -> Result<()> { + query!(AppliedMigration, $app == migration.app_name() && $name == migration.name()) + .delete(database) + .await?; + Ok(()) + } +} + +fn resolve_migration_file_name(file_name: &str) -> Vec<&str> { + let mut names = vec![file_name]; + let migration_number = file_name.split('_').nth(1); + if let Some(migration_number) = migration_number { + names.push(migration_number); + } + names } /// A migration operation that can be run forwards or backwards. diff --git a/cot/src/db/migrations/sorter.rs b/cot/src/db/migrations/sorter.rs index e2d5c7e66..46faf1f96 100644 --- a/cot/src/db/migrations/sorter.rs +++ b/cot/src/db/migrations/sorter.rs @@ -61,11 +61,11 @@ impl<'a, T: DynMigration> MigrationSorter<'a, T> { Ok(()) } - fn toposort(&mut self) -> Result<()> { - let lookup = Self::create_lookup_table(self.migrations)?; - let mut graph = Graph::new(self.migrations.len()); + pub(super) fn generate_graph(migrations: &[T]) -> Result { + let lookup = Self::create_lookup_table(migrations)?; + let mut graph = Graph::new(migrations.len()); - for (index, migration) in self.migrations.iter().enumerate() { + for (index, migration) in migrations.iter().enumerate() { for dependency in migration.dependencies() { let dependency_index = lookup .get(&MigrationLookup::from(dependency)) @@ -74,6 +74,12 @@ impl<'a, T: DynMigration> MigrationSorter<'a, T> { } } + Ok(graph) + } + + fn toposort(&mut self) -> Result<()> { + let mut graph = Self::generate_graph(self.migrations)?; + let mut sorted_indices = graph.toposort()?; apply_permutation(self.migrations, &mut sorted_indices); diff --git a/cot/src/utils/graph.rs b/cot/src/utils/graph.rs index 741f98400..bf6053efc 100644 --- a/cot/src/utils/graph.rs +++ b/cot/src/utils/graph.rs @@ -40,6 +40,10 @@ impl Graph { self.vertex_edges[from].push(to); } + pub(crate) fn get_edges(&self, from: usize) -> &[usize] { + &self.vertex_edges[from] + } + #[must_use] pub(crate) fn vertex_num(&self) -> usize { self.vertex_edges.len() From d069460be4986f8c4d851b1bef781be9f4a663c4 Mon Sep 17 00:00:00 2001 From: Elijah Date: Thu, 4 Jun 2026 23:00:12 +0000 Subject: [PATCH 02/12] add unit tests --- cot/src/cli.rs | 251 +++++++++++++++++++++++++++++++++++++-- cot/src/db/migrations.rs | 226 ++++++++++++++++++++++++++++++++++- 2 files changed, 459 insertions(+), 18 deletions(-) diff --git a/cot/src/cli.rs b/cot/src/cli.rs index 5601b252d..6a4cec02c 100644 --- a/cot/src/cli.rs +++ b/cot/src/cli.rs @@ -95,7 +95,7 @@ impl Cli { cli.add_task(CollectStatic); let mut migration_group = - CliTaskGroup::new("migration").about("Database migration commands"); + CliTaskGroup::new(MIGRATION_GROUP_SUBCOMMAND).about("Database migration commands"); migration_group.add_task(MigrationRollback); cli.add_task(migration_group); @@ -400,13 +400,42 @@ impl CliTask for Check { /// A group of related sub-tasks under a single parent subcommand. /// -/// Usage: +/// # Examples +/// /// ``` -/// let mut migration = CliTaskGroup::new("migration") -/// .about("Database migration commands"); -/// migration.add_task(MigrationRollback); -/// migration.add_task(MigrationSquash); -/// cli.add_task(migration); +/// use async_trait::async_trait; +/// use clap::{ArgMatches, Command}; +/// use cot::cli::{Cli, CliTask}; +/// use cot::project::WithConfig; +/// use cot::{Bootstrapper, Project}; +/// +/// struct Frobnicate; +/// +/// #[async_trait(?Send)] +/// impl CliTask for Frobnicate { +/// fn subcommand(&self) -> Command { +/// Command::new("frobnicate") +/// } +/// +/// async fn execute( +/// &mut self, +/// _matches: &ArgMatches, +/// _bootstrapper: Bootstrapper, +/// ) -> cot::Result<()> { +/// println!("Frobnicating..."); +/// +/// Ok(()) +/// } +/// } +/// +/// struct MyProject; +/// impl Project for MyProject { +/// fn register_tasks(&self, cli: &mut Cli) { +/// group_command = CliTaskGroup::new("foo").about("Foo related commands"); +/// group_command.add_task(Frobnicate) +/// cli.add_task(group_command); +/// } +/// } /// ``` pub struct CliTaskGroup { name: String, @@ -466,11 +495,11 @@ impl CliTask for CliTaskGroup { ) -> Result<()> { let (sub_name, sub_matches) = matches .subcommand() - .expect("subcommand_required(true) ensures one is present"); + .expect("subcommand should be present since subcommand_required is true"); self.tasks .get_mut(sub_name) - .expect("clap only matches registered subcommands") + .expect("command should be registered") .execute(sub_matches, bootstrapper) .await } @@ -514,9 +543,9 @@ impl CliTask for MigrationRollback { let crate_name = bootstrapper.project().cli_metadata().name; let BootstrappedProject { - mut context, - mut handler, - mut error_handler, + context, + handler: _, + error_handler: _, } = bootstrapper.finish(); #[cfg(feature = "db")] @@ -559,11 +588,114 @@ mod tests { use tempfile::tempdir; use super::*; + #[cfg(feature = "db")] + use crate::config::DatabaseConfig; use crate::config::ProjectConfig; + #[cfg(feature = "db")] + use crate::db::migrations::{ + Field, Migration, MigrationDependency, Operation, wrap_migrations, + }; + #[cfg(feature = "db")] + use crate::db::{Auto, Database, DatabaseField, Identifier, query}; use crate::project::RegisterAppsContext; use crate::static_files::StaticFile; use crate::{App, AppBuilder}; + #[cfg(feature = "db")] + struct CliRollbackInitial; + + #[cfg(feature = "db")] + impl Migration for CliRollbackInitial { + const APP_NAME: &'static str = "cli_rollback_app"; + const MIGRATION_NAME: &'static str = "m_0001_initial"; + const DEPENDENCIES: &'static [MigrationDependency] = &[]; + const OPERATIONS: &'static [Operation] = &[Operation::create_model() + .table_name(Identifier::new("cli_rollback_app__initial")) + .fields(&[ + Field::new(Identifier::new("id"), ::TYPE) + .primary_key() + .auto(), + ]) + .build()]; + } + + #[cfg(feature = "db")] + struct CliRollbackSecond; + + #[cfg(feature = "db")] + impl Migration for CliRollbackSecond { + const APP_NAME: &'static str = "cli_rollback_app"; + const MIGRATION_NAME: &'static str = "m_0002_second"; + const DEPENDENCIES: &'static [MigrationDependency] = &[MigrationDependency::migration( + "cli_rollback_app", + "m_0001_initial", + )]; + const OPERATIONS: &'static [Operation] = &[Operation::create_model() + .table_name(Identifier::new("cli_rollback_app__second")) + .fields(&[ + Field::new(Identifier::new("id"), ::TYPE) + .primary_key() + .auto(), + ]) + .build()]; + } + + #[cfg(feature = "db")] + struct CliRollbackApp; + + #[cfg(feature = "db")] + impl App for CliRollbackApp { + fn name(&self) -> &'static str { + "cli_rollback_app" + } + + fn migrations(&self) -> Vec> { + #[expect(trivial_casts)] + wrap_migrations(&[ + &CliRollbackInitial as &SyncDynMigration, + &CliRollbackSecond as &SyncDynMigration, + ]) + } + } + + #[cfg(feature = "db")] + struct CliRollbackProject; + + #[cfg(feature = "db")] + impl crate::Project for CliRollbackProject { + fn cli_metadata(&self) -> CliMetadata { + CliMetadata { + name: "cli_rollback_app", + version: "0.0.0", + authors: "", + description: "", + } + } + + fn register_apps(&self, apps: &mut AppBuilder, _context: &RegisterAppsContext) { + apps.register(CliRollbackApp); + } + } + + #[cfg(feature = "db")] + #[derive(::std::fmt::Debug)] + #[crate::db::model(table_name = "cot__migrations", model_type = "internal")] + struct CliAppliedMigration { + #[model(primary_key)] + id: Auto, + app: String, + name: String, + applied: chrono::DateTime, + } + + #[cfg(feature = "db")] + async fn cli_migration_applied(database: &Database, app: &str, name: &str) -> bool { + query!(CliAppliedMigration, $app == app && $name == name) + .exists(database) + .await + .unwrap() + } + #[test] fn cli_new() { let cli = Cli::new(); @@ -621,6 +753,61 @@ mod tests { ); } + #[test] + fn cli_new_includes_migration_rollback_group() { + let cli = Cli::new(); + + let migration_group = cli + .command + .get_subcommands() + .find(|command| command.get_name() == MIGRATION_GROUP_SUBCOMMAND) + .expect("migration group is registered"); + + assert!( + migration_group + .get_subcommands() + .any(|command| command.get_name() == MIGRATION_ROLLBACK_SUBCOMMAND) + ); + } + + #[cot::test] + async fn cli_task_group_dispatches_nested_task() { + use std::sync::atomic::{AtomicBool, Ordering}; + + struct NestedTask; + #[async_trait(?Send)] + impl CliTask for NestedTask { + fn subcommand(&self) -> Command { + Command::new("nested") + } + + async fn execute( + &mut self, + _matches: &ArgMatches, + _bootstrapper: Bootstrapper, + ) -> Result<()> { + TASK_CALLED.store(true, Ordering::SeqCst); + Ok(()) + } + } + + struct TestProject; + impl crate::Project for TestProject {} + + static TASK_CALLED: AtomicBool = AtomicBool::new(false); + TASK_CALLED.store(false, Ordering::SeqCst); + + + let mut group = CliTaskGroup::new("group"); + group.add_task(NestedTask); + let matches = group.subcommand().get_matches_from(["group", "nested"]); + let bootstrapper = Bootstrapper::new(TestProject).with_config(ProjectConfig::default()); + + group.execute(&matches, bootstrapper).await.unwrap(); + + assert!(TASK_CALLED.load(Ordering::SeqCst)); + } + #[test] fn run_server_subcommand() { let matches = RunServer @@ -669,6 +856,46 @@ mod tests { assert!(temp_path.join("test.txt").exists()); } + #[cot::test] + #[cfg_attr( + miri, + ignore = "unsupported operation: can't call foreign function `sqlite3_open_v2`" + )] + #[cfg(feature = "db")] + async fn migration_rollback_execute_rolls_back_project_app() { + let temp_dir = tempdir().unwrap(); + let db_path = temp_dir.path().join("rollback.sqlite"); + std::fs::File::create(&db_path).unwrap(); + let db_url = format!("sqlite://{}", db_path.display()); + + let setup_database = Database::new(db_url.clone()).await.unwrap(); + let setup_engine = MigrationEngine::new(CliRollbackApp.migrations()).unwrap(); + setup_engine.run(&setup_database).await.unwrap(); + assert!(cli_migration_applied(&setup_database, "cli_rollback_app", "m_0001_initial").await); + assert!(cli_migration_applied(&setup_database, "cli_rollback_app", "m_0002_second").await); + drop(setup_engine); + drop(setup_database); + + let config = ProjectConfig::builder() + .database(DatabaseConfig::builder().url(db_url.clone()).build()) + .build(); + let mut rollback = MigrationRollback; + let matches = MigrationRollback + .subcommand() + .get_matches_from(["rollback", "0001"]); + let bootstrapper = Bootstrapper::new(CliRollbackProject).with_config(config); + + rollback.execute(&matches, bootstrapper).await.unwrap(); + + let verify_database = Database::new(db_url).await.unwrap(); + assert!( + cli_migration_applied(&verify_database, "cli_rollback_app", "m_0001_initial").await + ); + assert!( + !cli_migration_applied(&verify_database, "cli_rollback_app", "m_0002_second").await + ); + } + #[cot::test] async fn check_execute() { let config = r#"secret_key = "123abc""#; diff --git a/cot/src/db/migrations.rs b/cot/src/db/migrations.rs index c81c8fc5d..8216fc301 100644 --- a/cot/src/db/migrations.rs +++ b/cot/src/db/migrations.rs @@ -4,11 +4,10 @@ mod sorter; pub use cot_macros::migration_op; use sea_query::{ColumnDef, StringLen}; -use std::collections::{HashMap, HashSet, VecDeque}; +use std::collections::{HashSet, VecDeque}; use std::fmt; use std::fmt::{Debug, Formatter}; use std::future::Future; -use std::path::{Path, PathBuf}; use thiserror::Error; use tracing::{Level, info}; @@ -216,6 +215,8 @@ impl MigrationEngine { Ok(()) } + /// Rollback necessary migrations up until the specified migration in an app. + /// pub async fn rollback(&self, database: &Database, file: &str, app_name: &str) -> Result<()> { let rollback_plan = self.rollback_plan(file, app_name)?; @@ -267,7 +268,8 @@ impl MigrationEngine { })?; let mut rollback_indices = HashSet::new(); - // seed all possible migration files in the same app that are dependent on the target migration + // Seed later migrations in the same app, then include migrations from + // other apps only when they depend on that seed set. rollback_indices.extend( self.migrations .iter() @@ -282,9 +284,6 @@ impl MigrationEngine { MigrationEngineError::Custom(format!("Failed to generate migration graph: {}", e)) })?; let mut queue = rollback_indices.iter().copied().collect::>(); - // go through every migration dependent on the target migration and add any dependencies to the queue. - // This is also useful in cases where a migration is depended on by migrations in other apps. In this case, - // we need to make sure that we also roll back the migrations in other apps. while let Some(index) = queue.pop_front() { for &dependent_index in graph.get_edges(index) { if rollback_indices.insert(dependent_index) { @@ -2130,6 +2129,7 @@ mod tests { use super::*; use crate::db::{ColumnType, DatabaseField, Identifier}; + use crate::{App, auth::db::DatabaseUserApp, session::db::SessionApp}; struct TestMigration; @@ -2157,6 +2157,110 @@ mod tests { const OPERATIONS: &'static [Operation] = &[]; } + struct RollbackApp1Initial; + + impl Migration for RollbackApp1Initial { + const APP_NAME: &'static str = "rollback_app1"; + const MIGRATION_NAME: &'static str = "m_0001_initial"; + const DEPENDENCIES: &'static [MigrationDependency] = &[]; + const OPERATIONS: &'static [Operation] = &[Operation::create_model() + .table_name(Identifier::new("rollback_single__first")) + .fields(&[ + Field::new(Identifier::new("id"), ::TYPE) + .primary_key() + .auto(), + ]) + .build()]; + } + + struct RollbackApp10002; + + impl Migration for RollbackApp10002 { + const APP_NAME: &'static str = "rollback_app1"; + const MIGRATION_NAME: &'static str = "m_0002_second"; + const DEPENDENCIES: &'static [MigrationDependency] = &[MigrationDependency::migration( + "rollback_app1", + "m_0001_initial", + )]; + const OPERATIONS: &'static [Operation] = &[Operation::create_model() + .table_name(Identifier::new("rollback_app1__second")) + .fields(&[ + Field::new(Identifier::new("id"), ::TYPE) + .primary_key() + .auto(), + ]) + .build()]; + } + + struct RollbackApp1003; + + impl Migration for RollbackApp1003 { + const APP_NAME: &'static str = "rollback_app1"; + const MIGRATION_NAME: &'static str = "m_0003_third"; + const DEPENDENCIES: &'static [MigrationDependency] = &[MigrationDependency::migration( + "rollback_app1", + "m_0002_second", + )]; + const OPERATIONS: &'static [Operation] = &[Operation::create_model() + .table_name(Identifier::new("rollback_single__third")) + .fields(&[ + Field::new(Identifier::new("id"), ::TYPE) + .primary_key() + .auto(), + ]) + .build()]; + } + + struct RollbackApp2Initial; + + impl Migration for RollbackApp2Initial { + const APP_NAME: &'static str = "rollback_app2"; + const MIGRATION_NAME: &'static str = "m_0001_initial"; + const DEPENDENCIES: &'static [MigrationDependency] = &[]; + const OPERATIONS: &'static [Operation] = &[Operation::create_model() + .table_name(Identifier::new("rollback_app2__foo")) + .fields(&[ + Field::new(Identifier::new("id"), ::TYPE) + .primary_key() + .auto(), + ]) + .build()]; + } + + struct RollbackDependentInitial; + + impl Migration for RollbackDependentInitial { + const APP_NAME: &'static str = "rollback_dependent"; + const MIGRATION_NAME: &'static str = "m_0001_initial"; + const DEPENDENCIES: &'static [MigrationDependency] = &[MigrationDependency::migration( + "rollback_app1", + "m_0002_second", + )]; + const OPERATIONS: &'static [Operation] = &[Operation::create_model() + .table_name(Identifier::new("rollback_dependent__bar")) + .fields(&[ + Field::new(Identifier::new("id"), ::TYPE) + .primary_key() + .auto(), + ]) + .build()]; + } + + async fn assert_migration_applied(database: Database, app: &str, name: &str, expected: bool) { + let applied = query!(AppliedMigration, $app == app && $name == name) + .exists(&database) + .await + .unwrap(); + + assert_eq!(applied, expected, "{app}::{name}"); + } + + #[cot_macros::dbtest] + async fn test_migration_rollback_no_deps(test_db: &mut TestDatabase) { + let engine = MigrationEngine::new([RollbackApp1Initial]).unwrap(); + engine.run(&test_db.database()).await.unwrap(); + } + #[cot_macros::dbtest] async fn test_migration_engine_run(test_db: &mut TestDatabase) { let engine = MigrationEngine::new([TestMigration]).unwrap(); @@ -2180,6 +2284,116 @@ mod tests { assert!(result.is_ok()); } + #[cot_macros::dbtest] + async fn test_migration_engine_rollback_single_app(test_db: &mut TestDatabase) { + #[expect(trivial_casts)] + let engine = MigrationEngine::new([ + &RollbackApp1Initial as &SyncDynMigration, + &RollbackApp10002 as &SyncDynMigration, + &RollbackApp1003 as &SyncDynMigration, + ]) + .unwrap(); + + engine.run(&test_db.database()).await.unwrap(); + // migrations should be applied + + assert_migration_applied(test_db.database(), "rollback_app1", "m_0001_initial", true).await; + assert_migration_applied(test_db.database(), "rollback_app1", "m_0002_second", true).await; + assert_migration_applied(test_db.database(), "rollback_app1", "m_0003_third", true).await; + + // rollback everything except the initial migration + engine + .rollback(&test_db.database(), "0001", "rollback_app1") + .await + .unwrap(); + + // the initial migration should stay applied + assert_migration_applied(test_db.database(), "rollback_app1", "m_0001_initial", true).await; + // everything else should be unapplied + assert_migration_applied(test_db.database(), "rollback_app1", "m_0002_second", false).await; + assert_migration_applied(test_db.database(), "rollback_app1", "m_0003_third", false).await; + } + + #[cot_macros::dbtest] + async fn test_migration_engine_rollback_preserves_unrelated_apps(test_db: &mut TestDatabase) { + let mut migrations = DatabaseUserApp::new().migrations(); + // combine migrations from multiple apps/crates + #[expect(trivial_casts)] + migrations.extend(wrap_migrations(&[ + &RollbackApp1Initial as &SyncDynMigration, + &RollbackApp10002 as &SyncDynMigration, + &RollbackApp2Initial as &SyncDynMigration, + ])); + migrations.extend(SessionApp::new().migrations()); + let engine = MigrationEngine::new(migrations).unwrap(); + + engine.run(&test_db.database()).await.unwrap(); + // migrations should be applied across all apps + assert_migration_applied(test_db.database(), "cot", "m_0001_initial", true).await; + assert_migration_applied(test_db.database(), "cot_session", "m_0001_initial", true).await; + assert_migration_applied(test_db.database(), "rollback_app1", "m_0001_initial", true).await; + assert_migration_applied(test_db.database(), "rollback_app1", "m_0002_second", true).await; + assert_migration_applied(test_db.database(), "rollback_app2", "m_0001_initial", true).await; + + // rollback every migration in the rollback_mixed app except the initial + engine + .rollback(&test_db.database(), "0001", "rollback_app1") + .await + .unwrap(); + + // the initial migration should stay applied + assert_migration_applied(test_db.database(), "rollback_app1", "m_0001_initial", true).await; + // everything else in the rollback_mixed app should be unapplied + assert_migration_applied(test_db.database(), "rollback_app1", "m_0002_second", false).await; + // migrations from other apps should remain unaffected + assert_migration_applied(test_db.database(), "cot", "m_0001_initial", true).await; + assert_migration_applied(test_db.database(), "cot_session", "m_0001_initial", true).await; + assert_migration_applied(test_db.database(), "rollback_app2", "m_0001_initial", true).await; + } + + #[cot_macros::dbtest] + async fn test_migration_engine_rollback_includes_dependent_apps(test_db: &mut TestDatabase) { + #[expect(trivial_casts)] + let engine = MigrationEngine::new([ + &RollbackApp1Initial as &SyncDynMigration, + &RollbackApp10002 as &SyncDynMigration, + &RollbackDependentInitial as &SyncDynMigration, + &RollbackApp2Initial as &SyncDynMigration, + ]) + .unwrap(); + + engine.run(&test_db.database()).await.unwrap(); + assert_migration_applied(test_db.database(), "rollback_app1", "m_0001_initial", true).await; + assert_migration_applied(test_db.database(), "rollback_app1", "m_0002_second", true).await; + assert_migration_applied( + test_db.database(), + "rollback_dependent", + "m_0001_initial", + true, + ) + .await; + assert_migration_applied(test_db.database(), "rollback_app2", "m_0001_initial", true).await; + + // rollback everything except the initial migration in the source/independent app + engine + .rollback(&test_db.database(), "0001", "rollback_app1") + .await + .unwrap(); + + assert_migration_applied(test_db.database(), "rollback_app1", "m_0001_initial", true).await; + assert_migration_applied(test_db.database(), "rollback_app1", "m_0002_second", false).await; + // the sink/dependent app should also be unapplied/rolled back + assert_migration_applied( + test_db.database(), + "rollback_dependent", + "m_0001_initial", + false, + ) + .await; + // migrations from non-dependent apps should remain unaffected + assert_migration_applied(test_db.database(), "rollback_app2", "m_0001_initial", true).await; + } + #[test] fn test_operation_create_model() { const OPERATION_CREATE_MODEL_FIELDS: &[Field; 2] = &[ From 965f4829416d2f8f6b50bfe51c294f74b2e74397 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 4 Jun 2026 23:00:29 +0000 Subject: [PATCH 03/12] chore(pre-commit.ci): auto fixes from pre-commit hooks --- cot/src/cli.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/cot/src/cli.rs b/cot/src/cli.rs index 6a4cec02c..22ffb7f83 100644 --- a/cot/src/cli.rs +++ b/cot/src/cli.rs @@ -797,7 +797,6 @@ mod tests { static TASK_CALLED: AtomicBool = AtomicBool::new(false); TASK_CALLED.store(false, Ordering::SeqCst); - let mut group = CliTaskGroup::new("group"); group.add_task(NestedTask); let matches = group.subcommand().get_matches_from(["group", "nested"]); From 851faff7a0155b1c4d4fc6cef67351a9de222e2b Mon Sep 17 00:00:00 2001 From: Elijah Date: Thu, 4 Jun 2026 23:34:47 +0000 Subject: [PATCH 04/12] clippy fix --- cot/src/cli.rs | 63 ++++++++++++++++++++++++++++++++++++++++ cot/src/db/migrations.rs | 17 +++++++---- 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/cot/src/cli.rs b/cot/src/cli.rs index 22ffb7f83..472892e5d 100644 --- a/cot/src/cli.rs +++ b/cot/src/cli.rs @@ -437,13 +437,24 @@ impl CliTask for Check { /// } /// } /// ``` +#[derive(Debug)] pub struct CliTaskGroup { name: String, about: String, + #[debug("..")] tasks: HashMap>, } impl CliTaskGroup { + /// Create a subcommand group. + /// + /// # Examples + /// + /// ``` + /// use cot::cli::CliTaskGroup; + /// + /// let group = CliTaskGroup::new("command"); + /// ``` pub fn new(name: impl Into) -> Self { Self { name: name.into(), @@ -452,11 +463,63 @@ impl CliTaskGroup { } } + /// Sets the description of the group, which is displayed in the help message + /// for the group's subcommands. + /// + /// # Example + /// ``` + /// let group = CliTaskGroup::new("command") + /// .about("This is a description for the command group"); + /// ``` + #[must_use] pub fn about(mut self, about: impl Into) -> Self { self.about = about.into(); self } + /// Adds a new task to the subcommand group. + /// + /// # Panics + /// + /// Panics if a task with the same name has already been registered. + /// + /// # Examples + /// + /// ``` + /// use async_trait::async_trait; + /// use clap::{ArgMatches, Command}; + /// use cot::cli::{Cli, CliTask}; + /// use cot::project::WithConfig; + /// use cot::{Bootstrapper, Project}; + /// + /// struct Frobnicate; + /// + /// #[async_trait(?Send)] + /// impl CliTask for Frobnicate { + /// fn subcommand(&self) -> Command { + /// Command::new("frobnicate") + /// } + /// + /// async fn execute( + /// &mut self, + /// _matches: &ArgMatches, + /// _bootstrapper: Bootstrapper, + /// ) -> cot::Result<()> { + /// println!("Frobnicating..."); + /// + /// Ok(()) + /// } + /// } + /// + /// struct MyProject; + /// impl Project for MyProject { + /// fn register_tasks(&self, cli: &mut Cli) { + /// group_command = CliTaskGroup::new("foo").about("Foo related commands"); + /// group_command.add_task(Frobnicate) + /// cli.add_task(group_command); + /// } + /// } + /// ``` pub fn add_task(&mut self, task: C) { let subcommand = task.subcommand(); let name = subcommand.get_name().to_owned(); diff --git a/cot/src/db/migrations.rs b/cot/src/db/migrations.rs index 8216fc301..d0c367c0b 100644 --- a/cot/src/db/migrations.rs +++ b/cot/src/db/migrations.rs @@ -215,9 +215,16 @@ impl MigrationEngine { Ok(()) } - /// Rollback necessary migrations up until the specified migration in an app. + /// Roll back necessary migrations up until the specified migration in an app. /// + /// # Errors + /// + /// Returns an error if there is an error while interacting with the database or if + /// there is an error while generating the migration graph or if there is an error + /// while unapplying a migration. pub async fn rollback(&self, database: &Database, file: &str, app_name: &str) -> Result<()> { + info!("Rolling back migrations"); + let rollback_plan = self.rollback_plan(file, app_name)?; for migration in rollback_plan { @@ -243,7 +250,7 @@ impl MigrationEngine { operation.backwards(database).await?; } - Self::unapply_migration(database, migration).await?; + Self::mark_migration_unapplied(database, migration).await?; } Ok(()) @@ -281,7 +288,7 @@ impl MigrationEngine { ); let graph = MigrationSorter::generate_graph(&self.migrations).map_err(|e| { - MigrationEngineError::Custom(format!("Failed to generate migration graph: {}", e)) + MigrationEngineError::Custom(format!("Failed to generate migration graph: {e}")) })?; let mut queue = rollback_indices.iter().copied().collect::>(); while let Some(index) = queue.pop_front() { @@ -328,7 +335,7 @@ impl MigrationEngine { Ok(()) } - async fn unapply_migration(database: &Database, migration: &MigrationWrapper) -> Result<()> { + async fn mark_migration_unapplied(database: &Database, migration: &MigrationWrapper) -> Result<()> { query!(AppliedMigration, $app == migration.app_name() && $name == migration.name()) .delete(database) .await?; @@ -2335,7 +2342,7 @@ mod tests { assert_migration_applied(test_db.database(), "rollback_app1", "m_0002_second", true).await; assert_migration_applied(test_db.database(), "rollback_app2", "m_0001_initial", true).await; - // rollback every migration in the rollback_mixed app except the initial + // rollback every migration in the rollback_app1 app except the initial engine .rollback(&test_db.database(), "0001", "rollback_app1") .await From 921ef7bb30af91c6042d4294c3394587fd66d05d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 4 Jun 2026 23:35:01 +0000 Subject: [PATCH 05/12] chore(pre-commit.ci): auto fixes from pre-commit hooks --- cot/src/db/migrations.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cot/src/db/migrations.rs b/cot/src/db/migrations.rs index d0c367c0b..1d3c69e79 100644 --- a/cot/src/db/migrations.rs +++ b/cot/src/db/migrations.rs @@ -335,7 +335,10 @@ impl MigrationEngine { Ok(()) } - async fn mark_migration_unapplied(database: &Database, migration: &MigrationWrapper) -> Result<()> { + async fn mark_migration_unapplied( + database: &Database, + migration: &MigrationWrapper, + ) -> Result<()> { query!(AppliedMigration, $app == migration.app_name() && $name == migration.name()) .delete(database) .await?; From 45ed53170d60036fcd89ee5c4db7942d8f8c3648 Mon Sep 17 00:00:00 2001 From: Elijah Date: Fri, 5 Jun 2026 00:00:33 +0000 Subject: [PATCH 06/12] fix doc tests --- cot/src/cli.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/cot/src/cli.rs b/cot/src/cli.rs index 472892e5d..558b8ee94 100644 --- a/cot/src/cli.rs +++ b/cot/src/cli.rs @@ -405,7 +405,7 @@ impl CliTask for Check { /// ``` /// use async_trait::async_trait; /// use clap::{ArgMatches, Command}; -/// use cot::cli::{Cli, CliTask}; +/// use cot::cli::{Cli, CliTask, CliTaskGroup}; /// use cot::project::WithConfig; /// use cot::{Bootstrapper, Project}; /// @@ -431,8 +431,8 @@ impl CliTask for Check { /// struct MyProject; /// impl Project for MyProject { /// fn register_tasks(&self, cli: &mut Cli) { -/// group_command = CliTaskGroup::new("foo").about("Foo related commands"); -/// group_command.add_task(Frobnicate) +/// let mut group_command = CliTaskGroup::new("foo").about("Foo related commands"); +/// group_command.add_task(Frobnicate); /// cli.add_task(group_command); /// } /// } @@ -468,6 +468,8 @@ impl CliTaskGroup { /// /// # Example /// ``` + /// use cot::cli::CliTaskGroup; + /// /// let group = CliTaskGroup::new("command") /// .about("This is a description for the command group"); /// ``` @@ -488,7 +490,7 @@ impl CliTaskGroup { /// ``` /// use async_trait::async_trait; /// use clap::{ArgMatches, Command}; - /// use cot::cli::{Cli, CliTask}; + /// use cot::cli::{Cli, CliTask, CliTaskGroup}; /// use cot::project::WithConfig; /// use cot::{Bootstrapper, Project}; /// @@ -514,8 +516,8 @@ impl CliTaskGroup { /// struct MyProject; /// impl Project for MyProject { /// fn register_tasks(&self, cli: &mut Cli) { - /// group_command = CliTaskGroup::new("foo").about("Foo related commands"); - /// group_command.add_task(Frobnicate) + /// let mut group_command = CliTaskGroup::new("foo").about("Foo related commands"); + /// group_command.add_task(Frobnicate); /// cli.add_task(group_command); /// } /// } From 92564de1d8a1b07a01ac4821bb0639258019a334 Mon Sep 17 00:00:00 2001 From: Elijah Date: Mon, 8 Jun 2026 16:55:50 +0000 Subject: [PATCH 07/12] update lock --- Cargo.lock | 585 ++++++++++++++++++++++++----------------------------- 1 file changed, 265 insertions(+), 320 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cfd9d78ab..23c45e646 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -213,14 +213,14 @@ checksum = "7db09fde9143e7ac4513358fb32ee32847125b63b18ea715afd487956da715da" dependencies = [ "rustc-hash", "unicode-ident", - "winnow 1.0.1", + "winnow 1.0.3", ] [[package]] name = "assert_cmd" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39bae1d3fa576f7c6519514180a72559268dd7d1fe104070956cb687bc6673bd" +checksum = "2aa3a22042e45de04255c7bf3626e239f450200fd0493c1e382263544b20aea6" dependencies = [ "anstyle", "bstr", @@ -332,9 +332,9 @@ dependencies = [ [[package]] name = "async-signal" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" dependencies = [ "async-io", "async-lock", @@ -431,9 +431,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "axum" @@ -444,7 +444,7 @@ dependencies = [ "axum-core", "bytes", "futures-util", - "http 1.4.0", + "http 1.4.2", "http-body", "http-body-util", "hyper", @@ -471,7 +471,7 @@ checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", - "http 1.4.0", + "http 1.4.2", "http-body", "http-body-util", "mime", @@ -510,9 +510,9 @@ checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" dependencies = [ "serde_core", ] @@ -575,9 +575,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "byteorder" @@ -609,20 +609,14 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.58" +version = "1.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ "find-msvc-tools", "shlex", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfg-if" version = "1.0.4" @@ -637,14 +631,14 @@ checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "rand_core 0.10.0", + "rand_core 0.10.1", ] [[package]] name = "chrono" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", "js-sys", @@ -726,9 +720,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.6.3" +version = "4.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "660c0520455b1013b9bcb0393d5f643d7e4454fb69c915b8d6d2aa0e9a45acc3" +checksum = "e0a7a9bfdb35811f9e59832f0f05975114d2251b415fb534108e6f34060fd772" dependencies = [ "clap", ] @@ -902,7 +896,7 @@ dependencies = [ "futures-util", "grass_compiler", "hex", - "http 1.4.0", + "http 1.4.2", "http-body-util", "humantime", "idna", @@ -930,7 +924,7 @@ dependencies = [ "thiserror 2.0.18", "time", "tokio", - "toml 1.1.0+spec-1.1.0", + "toml 1.1.2+spec-1.1.0", "tower", "tower-livereload", "tower-sessions", @@ -1002,7 +996,7 @@ dependencies = [ "futures", "futures-core", "futures-util", - "http 1.4.0", + "http 1.4.2", "http-body", "http-body-util", "indexmap", @@ -1065,9 +1059,9 @@ dependencies = [ [[package]] name = "crc-catalog" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" [[package]] name = "criterion" @@ -1355,9 +1349,9 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", @@ -1402,9 +1396,9 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" dependencies = [ "serde", ] @@ -1588,7 +1582,7 @@ checksum = "7737298823a6f9ca743e372e8cb03658d55354fbab843424f575706ba9563046" dependencies = [ "base64", "cookie 0.18.1", - "http 1.4.0", + "http 1.4.2", "http-body-util", "hyper", "hyper-tls", @@ -1605,9 +1599,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "find-msvc-tools" @@ -1675,15 +1669,16 @@ dependencies = [ "chrono", "chrono-tz", "cot", - "tracing", - "tracing-subscriber", ] [[package]] name = "fragile" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dd6caf6059519a65843af8fe2a3ae298b14b80179855aeb4adc2c1934ee619" +checksum = "8878864ba14bb86e818a412bfd6f18f9eabd4ec0f008a28e8f7eb61db532fcf9" +dependencies = [ + "futures-core", +] [[package]] name = "futures" @@ -1839,7 +1834,7 @@ dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", - "rand_core 0.10.0", + "rand_core 0.10.1", "wasip2", "wasip3", ] @@ -1915,9 +1910,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "hashlink" @@ -1986,9 +1981,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", "itoa", @@ -2001,7 +1996,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.4.0", + "http 1.4.2", ] [[package]] @@ -2012,7 +2007,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.4.0", + "http 1.4.2", "http-body", "pin-project-lite", ] @@ -2037,21 +2032,20 @@ checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" [[package]] name = "hyper" -version = "1.8.1" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", - "http 1.4.0", + "http 1.4.2", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -2083,7 +2077,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.4.0", + "http 1.4.2", "http-body", "hyper", "ipnet", @@ -2134,28 +2128,29 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", - "yoke 0.8.1", + "utf8_iter", + "yoke 0.8.3", "zerofrom", - "zerovec 0.11.5", + "zerovec 0.11.6", ] [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", - "litemap 0.8.1", - "tinystr 0.8.2", - "writeable 0.6.2", - "zerovec 0.11.5", + "litemap 0.8.2", + "tinystr 0.8.3", + "writeable 0.6.3", + "zerovec 0.11.6", ] [[package]] @@ -2172,43 +2167,43 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ - "icu_collections 2.1.1", + "icu_collections 2.2.0", "icu_normalizer_data", "icu_properties", - "icu_provider 2.1.1", + "icu_provider 2.2.0", "smallvec", - "zerovec 0.11.5", + "zerovec 0.11.6", ] [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ - "icu_collections 2.1.1", + "icu_collections 2.2.0", "icu_locale_core", "icu_properties_data", - "icu_provider 2.1.1", + "icu_provider 2.2.0", "zerotrie", - "zerovec 0.11.5", + "zerovec 0.11.6", ] [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" @@ -2229,17 +2224,17 @@ dependencies = [ [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", - "writeable 0.6.2", - "yoke 0.8.1", + "writeable 0.6.3", + "yoke 0.8.3", "zerofrom", "zerotrie", - "zerovec 0.11.5", + "zerovec 0.11.6", ] [[package]] @@ -2300,9 +2295,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", @@ -2315,7 +2310,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.0", + "hashbrown 0.17.1", "serde", "serde_core", ] @@ -2362,16 +2357,6 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" -[[package]] -name = "iri-string" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -2395,27 +2380,32 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jni" -version = "0.21.1" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" dependencies = [ - "cesu8", "cfg-if", "combine", - "jni-sys 0.3.1", + "jni-macros", + "jni-sys", "log", - "thiserror 1.0.69", + "simd_cesu8", + "thiserror 2.0.18", "walkdir", - "windows-sys 0.45.0", + "windows-link", ] [[package]] -name = "jni-sys" -version = "0.3.1" +name = "jni-macros" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" dependencies = [ - "jni-sys 0.4.1", + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn", ] [[package]] @@ -2439,9 +2429,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.92" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4c90f45aa2e6eacbe8645f77fdea542ac97a494bcd117a67df9ff4d611f995" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" dependencies = [ "cfg-if", "futures-util", @@ -2484,9 +2474,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "lettre" -version = "0.11.21" +version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dabda5859ee7c06b995b9d1165aa52c39110e079ef609db97178d86aeb051fa7" +checksum = "0da65617f6cb926332d039cb578aad56178da86e128db6a1b09f4c94fa5b3349" dependencies = [ "async-std", "async-trait", @@ -2512,9 +2502,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.183" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libm" @@ -2524,14 +2514,14 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.15" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ "bitflags", "libc", "plain", - "redox_syscall 0.7.3", + "redox_syscall 0.8.1", ] [[package]] @@ -2559,9 +2549,9 @@ checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "lock_api" @@ -2574,9 +2564,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" dependencies = [ "value-bag", ] @@ -2608,9 +2598,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "mime" @@ -2639,9 +2629,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", "wasi", @@ -2683,7 +2673,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http 1.4.0", + "http 1.4.2", "httparse", "memchr", "mime", @@ -2737,16 +2727,16 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand 0.8.5", + "rand 0.8.6", "smallvec", "zeroize", ] [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" @@ -2817,15 +2807,14 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "openssl" -version = "0.10.76" +version = "0.10.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" dependencies = [ "bitflags", "cfg-if", "foreign-types", "libc", - "once_cell", "openssl-macros", "openssl-sys", ] @@ -2849,9 +2838,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.112" +version = "0.9.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" dependencies = [ "cc", "libc", @@ -2973,7 +2962,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared 0.11.3", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -3053,9 +3042,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "plain" @@ -3107,11 +3096,11 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ - "zerovec 0.11.5", + "zerovec 0.11.6", ] [[package]] @@ -3213,9 +3202,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -3224,9 +3213,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -3240,7 +3229,7 @@ checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ "chacha20", "getrandom 0.4.2", - "rand_core 0.10.0", + "rand_core 0.10.1", ] [[package]] @@ -3283,15 +3272,15 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "rayon" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -3309,9 +3298,9 @@ dependencies = [ [[package]] name = "redis" -version = "1.2.0" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f44e94c96d8870a387d88ce3de3fdd608cbfc0705f03cb343cdde91509d3e49a" +checksum = "a12e6b5f4d8ef33944e833e2b1859ad478deab6e431d7337b30ee2efe21f7543" dependencies = [ "arcstr", "async-lock", @@ -3341,9 +3330,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.7.3" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +checksum = "5b44b894f2a6e36457d665d1e08c3866add6ed5e70050c1b4ba8a8ddedb02ce7" dependencies = [ "bitflags", ] @@ -3399,14 +3388,14 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ "base64", "bytes", "futures-core", - "http 1.4.0", + "http 1.4.2", "http-body", "http-body-util", "hyper", @@ -3504,9 +3493,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.37" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ "log", "once_cell", @@ -3519,9 +3508,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -3531,18 +3520,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ "zeroize", ] [[package]] name = "rustls-platform-verifier" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ "core-foundation", "core-foundation-sys", @@ -3684,9 +3673,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" @@ -3742,9 +3731,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -3766,9 +3755,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876ac351060d4f882bb1032b6369eb0aef79ad9df1ea8bc404874d8cc3d0cd98" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] @@ -3818,9 +3807,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "signal-hook-registry" @@ -3842,6 +3831,22 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "similar" version = "2.7.0" @@ -3850,9 +3855,9 @@ checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "siphasher" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" [[package]] name = "slab" @@ -3871,9 +3876,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", "windows-sys 0.61.2", @@ -4014,7 +4019,7 @@ dependencies = [ "memchr", "once_cell", "percent-encoding", - "rand 0.8.5", + "rand 0.8.6", "rsa", "serde", "sha1", @@ -4053,7 +4058,7 @@ dependencies = [ "md-5", "memchr", "once_cell", - "rand 0.8.5", + "rand 0.8.6", "serde", "serde_json", "sha2", @@ -4295,12 +4300,12 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", - "zerovec 0.11.5", + "zerovec 0.11.6", ] [[package]] @@ -4330,9 +4335,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.1" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", @@ -4416,17 +4421,17 @@ dependencies = [ [[package]] name = "toml" -version = "1.1.0+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8195ca05e4eb728f4ba94f3e3291661320af739c4e43779cbdfae82ab239fcc" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" dependencies = [ "indexmap", "serde_core", "serde_spanned", - "toml_datetime 1.1.0+spec-1.1.0", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", "toml_writer", - "winnow 1.0.1", + "winnow 1.0.3", ] [[package]] @@ -4440,39 +4445,39 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.1.0+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.25.8+spec-1.1.0" +version = "0.25.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16bff38f1d86c47f9ff0647e6838d7bb362522bdf44006c7068c2b1e606f1f3c" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap", - "toml_datetime 1.1.0+spec-1.1.0", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.1", + "winnow 1.0.3", ] [[package]] name = "toml_parser" -version = "1.1.0+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.1", + "winnow 1.0.3", ] [[package]] name = "toml_writer" -version = "1.1.0+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d282ade6016312faf3e41e57ebbba0c073e4056dab1232ab1cb624199648f8ed" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "tower" @@ -4498,7 +4503,7 @@ dependencies = [ "axum-core", "cookie 0.18.1", "futures-util", - "http 1.4.0", + "http 1.4.2", "parking_lot", "pin-project-lite", "tower-layer", @@ -4507,20 +4512,20 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "bitflags", "bytes", "futures-util", - "http 1.4.0", + "http 1.4.2", "http-body", - "iri-string", "pin-project-lite", "tower", "tower-layer", "tower-service", + "url", ] [[package]] @@ -4536,7 +4541,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7df210bd982165dee3c20e31deed79a3585ee6b255c070d9c73ae015cc40d2f3" dependencies = [ "bytes", - "http 1.4.0", + "http 1.4.2", "http-body", "pin-project-lite", "tokio", @@ -4556,7 +4561,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "518dca34b74a17cadfcee06e616a09d2bd0c3984eff1769e1e76d58df978fc78" dependencies = [ "async-trait", - "http 1.4.0", + "http 1.4.2", "time", "tokio", "tower-cookies", @@ -4576,9 +4581,9 @@ dependencies = [ "async-trait", "base64", "futures", - "http 1.4.0", + "http 1.4.2", "parking_lot", - "rand 0.9.2", + "rand 0.9.4", "serde", "serde_json", "thiserror 2.0.18", @@ -4701,14 +4706,14 @@ dependencies = [ "serde_json", "target-triple", "termcolor", - "toml 1.1.0+spec-1.1.0", + "toml 1.1.2+spec-1.1.0", ] [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicase" @@ -4745,9 +4750,9 @@ checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" [[package]] name = "unicode-segmentation" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-xid" @@ -4846,11 +4851,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -4859,7 +4864,7 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] @@ -4870,9 +4875,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.115" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6523d69017b7633e396a89c5efab138161ed5aafcbc8d3e5c5a42ae38f50495a" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" dependencies = [ "cfg-if", "once_cell", @@ -4883,9 +4888,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.65" +version = "0.4.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d1faf851e778dfa54db7cd438b70758eba9755cb47403f3496edd7c8fc212f0" +checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" dependencies = [ "js-sys", "wasm-bindgen", @@ -4893,9 +4898,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.115" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3a6c758eb2f701ed3d052ff5737f5bfe6614326ea7f3bbac7156192dc32e67" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4903,9 +4908,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.115" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "921de2737904886b52bcbb237301552d05969a6f9c40d261eb0533c8b055fedf" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" dependencies = [ "bumpalo", "proc-macro2", @@ -4916,9 +4921,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.115" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a93e946af942b58934c604527337bad9ae33ba1d5c6900bbb41c2c07c2364a93" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" dependencies = [ "unicode-ident", ] @@ -4959,9 +4964,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.92" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84cde8507f4d7cfcb1185b8cb5890c494ffea65edbe1ba82cfd63661c805ed94" +checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" dependencies = [ "js-sys", "wasm-bindgen", @@ -4989,9 +4994,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" dependencies = [ "rustls-pki-types", ] @@ -5096,15 +5101,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -5132,21 +5128,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.48.5" @@ -5178,12 +5159,6 @@ dependencies = [ "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -5196,12 +5171,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -5214,12 +5183,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -5238,12 +5201,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -5256,12 +5213,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -5274,12 +5225,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -5292,12 +5237,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -5318,9 +5257,9 @@ checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" [[package]] name = "winnow" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" dependencies = [ "memchr", ] @@ -5334,6 +5273,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" @@ -5421,9 +5366,9 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "xxhash-rust" @@ -5445,12 +5390,12 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ "stable_deref_trait", - "yoke-derive 0.8.1", + "yoke-derive 0.8.2", "zerofrom", ] @@ -5468,9 +5413,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -5480,18 +5425,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" dependencies = [ "proc-macro2", "quote", @@ -5500,18 +5445,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -5527,12 +5472,12 @@ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", - "yoke 0.8.1", + "yoke 0.8.3", "zerofrom", ] @@ -5549,13 +5494,13 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ - "yoke 0.8.1", + "yoke 0.8.3", "zerofrom", - "zerovec-derive 0.11.2", + "zerovec-derive 0.11.3", ] [[package]] @@ -5571,9 +5516,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", From 787f31af2be963805e6f652f7bf65ae0b11b203f Mon Sep 17 00:00:00 2001 From: Elijah Date: Tue, 9 Jun 2026 18:12:45 +0000 Subject: [PATCH 08/12] remove unwanted test --- cot/src/cli.rs | 40 ---------------------------------------- 1 file changed, 40 deletions(-) diff --git a/cot/src/cli.rs b/cot/src/cli.rs index 558b8ee94..105ce43b6 100644 --- a/cot/src/cli.rs +++ b/cot/src/cli.rs @@ -920,46 +920,6 @@ mod tests { assert!(temp_path.join("test.txt").exists()); } - #[cot::test] - #[cfg_attr( - miri, - ignore = "unsupported operation: can't call foreign function `sqlite3_open_v2`" - )] - #[cfg(feature = "db")] - async fn migration_rollback_execute_rolls_back_project_app() { - let temp_dir = tempdir().unwrap(); - let db_path = temp_dir.path().join("rollback.sqlite"); - std::fs::File::create(&db_path).unwrap(); - let db_url = format!("sqlite://{}", db_path.display()); - - let setup_database = Database::new(db_url.clone()).await.unwrap(); - let setup_engine = MigrationEngine::new(CliRollbackApp.migrations()).unwrap(); - setup_engine.run(&setup_database).await.unwrap(); - assert!(cli_migration_applied(&setup_database, "cli_rollback_app", "m_0001_initial").await); - assert!(cli_migration_applied(&setup_database, "cli_rollback_app", "m_0002_second").await); - drop(setup_engine); - drop(setup_database); - - let config = ProjectConfig::builder() - .database(DatabaseConfig::builder().url(db_url.clone()).build()) - .build(); - let mut rollback = MigrationRollback; - let matches = MigrationRollback - .subcommand() - .get_matches_from(["rollback", "0001"]); - let bootstrapper = Bootstrapper::new(CliRollbackProject).with_config(config); - - rollback.execute(&matches, bootstrapper).await.unwrap(); - - let verify_database = Database::new(db_url).await.unwrap(); - assert!( - cli_migration_applied(&verify_database, "cli_rollback_app", "m_0001_initial").await - ); - assert!( - !cli_migration_applied(&verify_database, "cli_rollback_app", "m_0002_second").await - ); - } - #[cot::test] async fn check_execute() { let config = r#"secret_key = "123abc""#; From d5445b9f974e590e30818865e29423bcd0299e35 Mon Sep 17 00:00:00 2001 From: Elijah Date: Tue, 9 Jun 2026 18:47:26 +0000 Subject: [PATCH 09/12] fix linting --- Cargo.lock | 46 +++++++-------- cot/src/cli.rs | 119 +++------------------------------------ cot/src/db/migrations.rs | 21 ++++--- 3 files changed, 41 insertions(+), 145 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3eb78ef6b..8f74654a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1494,7 +1494,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1729,9 +1729,12 @@ dependencies = [ [[package]] name = "fragile" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dd6caf6059519a65843af8fe2a3ae298b14b80179855aeb4adc2c1934ee619" +checksum = "8878864ba14bb86e818a412bfd6f18f9eabd4ec0f008a28e8f7eb61db532fcf9" +dependencies = [ + "futures-core", +] [[package]] name = "futures" @@ -2410,16 +2413,6 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" -[[package]] -name = "iri-string" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -2888,15 +2881,14 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "openssl" -version = "0.10.76" +version = "0.10.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" dependencies = [ "bitflags", "cfg-if", "foreign-types", "libc", - "once_cell", "openssl-macros", "openssl-sys", ] @@ -2920,9 +2912,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.112" +version = "0.9.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" dependencies = [ "cc", "libc", @@ -3399,9 +3391,9 @@ checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" [[package]] name = "rayon" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -3609,7 +3601,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -3666,7 +3658,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4302,7 +4294,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -4633,20 +4625,20 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "bitflags", "bytes", "futures-util", "http 1.4.1", "http-body", - "iri-string", "pin-project-lite", "tower", "tower-layer", "tower-service", + "url", ] [[package]] @@ -5160,7 +5152,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/cot/src/cli.rs b/cot/src/cli.rs index 105ce43b6..27ab7ad8c 100644 --- a/cot/src/cli.rs +++ b/cot/src/cli.rs @@ -4,14 +4,16 @@ use std::collections::HashMap; use std::path::PathBuf; use std::str::FromStr; -use crate::{Bootstrapper, Error, Result}; use async_trait::async_trait; pub use clap; use clap::{Arg, ArgMatches, Command, value_parser}; +#[cfg(feature = "db")] use cot::db::migrations::{MigrationEngine, SyncDynMigration}; use cot::project::BootstrappedProject; use derive_more::Debug; +use crate::{Bootstrapper, Error, Result}; + const CONFIG_PARAM: &str = "config"; const COLLECT_STATIC_SUBCOMMAND: &str = "collect-static"; const CHECK_SUBCOMMAND: &str = "check"; @@ -463,15 +465,14 @@ impl CliTaskGroup { } } - /// Sets the description of the group, which is displayed in the help message - /// for the group's subcommands. + /// Sets the description of the group, which is displayed in the help + /// message for the group's subcommands. /// /// # Example /// ``` /// use cot::cli::CliTaskGroup; /// - /// let group = CliTaskGroup::new("command") - /// .about("This is a description for the command group"); + /// let group = CliTaskGroup::new("command").about("This is a description for the command group"); /// ``` #[must_use] pub fn about(mut self, about: impl Into) -> Self { @@ -603,8 +604,9 @@ impl CliTask for MigrationRollback { .boot() .await?; - // migrations are currently tied to crates, so we use the crate name as the app name. - // TODO: cli command should take an explicit crate name as arg when workspaces are supported. + // migrations are currently tied to crates, so we use the crate name as the app + // name. TODO: cli command should take an explicit crate name as arg + // when workspaces are supported. let crate_name = bootstrapper.project().cli_metadata().name; let BootstrappedProject { @@ -653,114 +655,11 @@ mod tests { use tempfile::tempdir; use super::*; - #[cfg(feature = "db")] - use crate::config::DatabaseConfig; use crate::config::ProjectConfig; - #[cfg(feature = "db")] - use crate::db::migrations::{ - Field, Migration, MigrationDependency, Operation, wrap_migrations, - }; - #[cfg(feature = "db")] - use crate::db::{Auto, Database, DatabaseField, Identifier, query}; use crate::project::RegisterAppsContext; use crate::static_files::StaticFile; use crate::{App, AppBuilder}; - #[cfg(feature = "db")] - struct CliRollbackInitial; - - #[cfg(feature = "db")] - impl Migration for CliRollbackInitial { - const APP_NAME: &'static str = "cli_rollback_app"; - const MIGRATION_NAME: &'static str = "m_0001_initial"; - const DEPENDENCIES: &'static [MigrationDependency] = &[]; - const OPERATIONS: &'static [Operation] = &[Operation::create_model() - .table_name(Identifier::new("cli_rollback_app__initial")) - .fields(&[ - Field::new(Identifier::new("id"), ::TYPE) - .primary_key() - .auto(), - ]) - .build()]; - } - - #[cfg(feature = "db")] - struct CliRollbackSecond; - - #[cfg(feature = "db")] - impl Migration for CliRollbackSecond { - const APP_NAME: &'static str = "cli_rollback_app"; - const MIGRATION_NAME: &'static str = "m_0002_second"; - const DEPENDENCIES: &'static [MigrationDependency] = &[MigrationDependency::migration( - "cli_rollback_app", - "m_0001_initial", - )]; - const OPERATIONS: &'static [Operation] = &[Operation::create_model() - .table_name(Identifier::new("cli_rollback_app__second")) - .fields(&[ - Field::new(Identifier::new("id"), ::TYPE) - .primary_key() - .auto(), - ]) - .build()]; - } - - #[cfg(feature = "db")] - struct CliRollbackApp; - - #[cfg(feature = "db")] - impl App for CliRollbackApp { - fn name(&self) -> &'static str { - "cli_rollback_app" - } - - fn migrations(&self) -> Vec> { - #[expect(trivial_casts)] - wrap_migrations(&[ - &CliRollbackInitial as &SyncDynMigration, - &CliRollbackSecond as &SyncDynMigration, - ]) - } - } - - #[cfg(feature = "db")] - struct CliRollbackProject; - - #[cfg(feature = "db")] - impl crate::Project for CliRollbackProject { - fn cli_metadata(&self) -> CliMetadata { - CliMetadata { - name: "cli_rollback_app", - version: "0.0.0", - authors: "", - description: "", - } - } - - fn register_apps(&self, apps: &mut AppBuilder, _context: &RegisterAppsContext) { - apps.register(CliRollbackApp); - } - } - - #[cfg(feature = "db")] - #[derive(::std::fmt::Debug)] - #[crate::db::model(table_name = "cot__migrations", model_type = "internal")] - struct CliAppliedMigration { - #[model(primary_key)] - id: Auto, - app: String, - name: String, - applied: chrono::DateTime, - } - - #[cfg(feature = "db")] - async fn cli_migration_applied(database: &Database, app: &str, name: &str) -> bool { - query!(CliAppliedMigration, $app == app && $name == name) - .exists(database) - .await - .unwrap() - } - #[test] fn cli_new() { let cli = Cli::new(); diff --git a/cot/src/db/migrations.rs b/cot/src/db/migrations.rs index 1d3c69e79..18e894dda 100644 --- a/cot/src/db/migrations.rs +++ b/cot/src/db/migrations.rs @@ -2,12 +2,13 @@ mod sorter; -pub use cot_macros::migration_op; -use sea_query::{ColumnDef, StringLen}; use std::collections::{HashSet, VecDeque}; use std::fmt; use std::fmt::{Debug, Formatter}; use std::future::Future; + +pub use cot_macros::migration_op; +use sea_query::{ColumnDef, StringLen}; use thiserror::Error; use tracing::{Level, info}; @@ -215,13 +216,14 @@ impl MigrationEngine { Ok(()) } - /// Roll back necessary migrations up until the specified migration in an app. + /// Roll back necessary migrations up until the specified migration in an + /// app. /// /// # Errors /// - /// Returns an error if there is an error while interacting with the database or if - /// there is an error while generating the migration graph or if there is an error - /// while unapplying a migration. + /// Returns an error if there is an error while interacting with the + /// database or if there is an error while generating the migration + /// graph or if there is an error while unapplying a migration. pub async fn rollback(&self, database: &Database, file: &str, app_name: &str) -> Result<()> { info!("Rolling back migrations"); @@ -2138,8 +2140,10 @@ mod tests { use sea_query::ColumnSpec; use super::*; + use crate::App; + use crate::auth::db::DatabaseUserApp; use crate::db::{ColumnType, DatabaseField, Identifier}; - use crate::{App, auth::db::DatabaseUserApp, session::db::SessionApp}; + use crate::session::db::SessionApp; struct TestMigration; @@ -2384,7 +2388,8 @@ mod tests { .await; assert_migration_applied(test_db.database(), "rollback_app2", "m_0001_initial", true).await; - // rollback everything except the initial migration in the source/independent app + // rollback everything except the initial migration in the source/independent + // app engine .rollback(&test_db.database(), "0001", "rollback_app1") .await From d16c593e10c8f9e9cbe75606843efb20a4dc34cd Mon Sep 17 00:00:00 2001 From: Elijah Date: Tue, 9 Jun 2026 19:59:22 +0000 Subject: [PATCH 10/12] shorten test name so mysql runs --- cot/src/db/migrations.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cot/src/db/migrations.rs b/cot/src/db/migrations.rs index 18e894dda..381dabe57 100644 --- a/cot/src/db/migrations.rs +++ b/cot/src/db/migrations.rs @@ -2329,7 +2329,7 @@ mod tests { } #[cot_macros::dbtest] - async fn test_migration_engine_rollback_preserves_unrelated_apps(test_db: &mut TestDatabase) { + async fn test_migration_rollback_unrelated_apps(test_db: &mut TestDatabase) { let mut migrations = DatabaseUserApp::new().migrations(); // combine migrations from multiple apps/crates #[expect(trivial_casts)] From aa5472c6322dc78bb71dabe4d32b3a5946fd5a9d Mon Sep 17 00:00:00 2001 From: Elijah Date: Tue, 9 Jun 2026 20:46:02 +0000 Subject: [PATCH 11/12] add some more comments and docs --- cot/src/cli.rs | 7 ++++--- cot/src/db/migrations.rs | 17 ++++++++++++----- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/cot/src/cli.rs b/cot/src/cli.rs index 27ab7ad8c..ef174bf39 100644 --- a/cot/src/cli.rs +++ b/cot/src/cli.rs @@ -580,7 +580,7 @@ impl CliTask for MigrationRollback { .about("Rollback migrations up to the specified migration file") .arg( Arg::new("file") - .help("The migration filename to roll back to (e.g. 0001_create_users)") + .help("The migration filename to roll back to (e.g. 0001_initial or 0001)") .value_name("FILE") .required(true), ) @@ -605,8 +605,9 @@ impl CliTask for MigrationRollback { .await?; // migrations are currently tied to crates, so we use the crate name as the app - // name. TODO: cli command should take an explicit crate name as arg - // when workspaces are supported. + // name. + // TODO: cli command should take an explicit crate name as arg when workspaces + // are supported. let crate_name = bootstrapper.project().cli_metadata().name; let BootstrappedProject { diff --git a/cot/src/db/migrations.rs b/cot/src/db/migrations.rs index 381dabe57..8002d9963 100644 --- a/cot/src/db/migrations.rs +++ b/cot/src/db/migrations.rs @@ -260,7 +260,7 @@ impl MigrationEngine { fn rollback_plan<'a>( &'a self, - file: &str, + file_name: &str, app_name: &str, ) -> Result> { let target_index = self @@ -268,11 +268,11 @@ impl MigrationEngine { .iter() .position(|migration| { migration.app_name() == app_name - && resolve_migration_file_name(migration.name()).contains(&file) + && resolve_migration_file_names(migration.name()).contains(&file_name) }) .ok_or_else(|| { MigrationEngineError::Custom(format!( - "Migration with file name {file} not found for app {app_name}" + "Migration with file name {file_name} not found for app {app_name}" )) })?; @@ -296,6 +296,8 @@ impl MigrationEngine { while let Some(index) = queue.pop_front() { for &dependent_index in graph.get_edges(index) { if rollback_indices.insert(dependent_index) { + // we found a migration that depends on the one we're rolling back, so let's + // add it to the queue which we will later traverse its dependents as well. queue.push_back(dependent_index); } } @@ -348,7 +350,12 @@ impl MigrationEngine { } } -fn resolve_migration_file_name(file_name: &str) -> Vec<&str> { +/// Resolves the possible migration names that can be used to refer to a +/// migration file. For example, for a migration file named `m_0001_initial`, +/// this function will return both `m_0001_initial` and `0001`. This allows +/// users to refer to migrations using either the full file name or just the +/// migration number when rolling back migrations. +fn resolve_migration_file_names(file_name: &str) -> Vec<&str> { let mut names = vec![file_name]; let migration_number = file_name.split('_').nth(1); if let Some(migration_number) = migration_number { @@ -2357,7 +2364,7 @@ mod tests { // the initial migration should stay applied assert_migration_applied(test_db.database(), "rollback_app1", "m_0001_initial", true).await; - // everything else in the rollback_mixed app should be unapplied + // everything else in the rollback_app1 app should be unapplied assert_migration_applied(test_db.database(), "rollback_app1", "m_0002_second", false).await; // migrations from other apps should remain unaffected assert_migration_applied(test_db.database(), "cot", "m_0001_initial", true).await; From eeb4a6248ccc06863785491cb9a573629fad7c71 Mon Sep 17 00:00:00 2001 From: Elijah Date: Mon, 15 Jun 2026 21:12:55 +0000 Subject: [PATCH 12/12] refactor function name --- cot/src/db/migrations.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cot/src/db/migrations.rs b/cot/src/db/migrations.rs index 8002d9963..de71c6563 100644 --- a/cot/src/db/migrations.rs +++ b/cot/src/db/migrations.rs @@ -268,7 +268,7 @@ impl MigrationEngine { .iter() .position(|migration| { migration.app_name() == app_name - && resolve_migration_file_names(migration.name()).contains(&file_name) + && expand_migration_file_name(migration.name()).contains(&file_name) }) .ok_or_else(|| { MigrationEngineError::Custom(format!( @@ -355,7 +355,7 @@ impl MigrationEngine { /// this function will return both `m_0001_initial` and `0001`. This allows /// users to refer to migrations using either the full file name or just the /// migration number when rolling back migrations. -fn resolve_migration_file_names(file_name: &str) -> Vec<&str> { +fn expand_migration_file_name(file_name: &str) -> Vec<&str> { let mut names = vec![file_name]; let migration_number = file_name.split('_').nth(1); if let Some(migration_number) = migration_number {