From d5030f96ba29a29859bd2ada60ccb75681d66ebd Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Thu, 7 Aug 2025 21:56:14 -0400 Subject: [PATCH] enums, migration change --- .../20250708000000_create_tasks_table.sql | 14 +- .../core/events/basic_sendable_event_trait.rs | 7 +- ...anvas_background_removal_complete_event.rs | 3 +- .../generation_complete_event.rs | 3 +- .../generation_enqueue_failure_event.rs | 3 +- .../generation_enqueue_success_event.rs | 3 +- .../generation_failed_event.rs | 3 +- .../enums/src/common/generation_provider.rs | 2 +- .../public/enums/src/common/model_type.rs | 2 +- crates/schema/public/enums/src/tauri/mod.rs | 3 +- .../schema/public/enums/src/tauri/ux/mod.rs | 2 + .../src/tauri/ux/tauri_command_caller.rs | 109 ++++++++++++ .../enums/src/tauri/ux/tauri_event_name.rs | 168 ++++++++++++++++++ 13 files changed, 304 insertions(+), 18 deletions(-) create mode 100644 crates/schema/public/enums/src/tauri/ux/mod.rs create mode 100644 crates/schema/public/enums/src/tauri/ux/tauri_command_caller.rs create mode 100644 crates/schema/public/enums/src/tauri/ux/tauri_event_name.rs diff --git a/_sql/artcraft_migrations/20250708000000_create_tasks_table.sql b/_sql/artcraft_migrations/20250708000000_create_tasks_table.sql index 59a32c38bd..923811688e 100644 --- a/_sql/artcraft_migrations/20250708000000_create_tasks_table.sql +++ b/_sql/artcraft_migrations/20250708000000_create_tasks_table.sql @@ -10,29 +10,29 @@ CREATE TABLE tasks ( -- TaskStatus enum -- This is how the job system manages job states. - -- pending, started, complete_success, dead, etc. + -- e.g. pending, started, complete_success, dead, etc. task_status TEXT NOT NULL, -- TaskType enum - -- image_generation, video_generation, etc. + -- e.g. image_generation, video_generation, etc. task_type TEXT NOT NULL, -- TaskModelType enum - -- flux_1_dev, veo_2, etc. + -- e.g. flux_1_dev, veo_2, etc. model_type TEXT, -- GenerationProvider enum -- Together with `provider_job_id`, this creates a foreign key lookup to the job. - -- 'artcraft', 'fal', 'sora', etc. + -- e.g. 'artcraft', 'fal', 'sora', etc. provider TEXT, -- The primary key for the job in the provider's system. provider_job_id TEXT, -- OPTIONAL. - -- Tell the job system which event(s) to send to the frontend - -- upon task completion. Comma-separated list of event names. - frontend_subscriber_event_names TEXT, + -- Tell the job system which caller initiated the task. + -- e.g. 'canvas' + frontend_caller TEXT, -- OPTIONAL. -- An arbitrary opaque identifier that frontend can set. diff --git a/crates/desktop/artcraft/src/core/events/basic_sendable_event_trait.rs b/crates/desktop/artcraft/src/core/events/basic_sendable_event_trait.rs index c4e4f00636..012c43e6f2 100644 --- a/crates/desktop/artcraft/src/core/events/basic_sendable_event_trait.rs +++ b/crates/desktop/artcraft/src/core/events/basic_sendable_event_trait.rs @@ -1,4 +1,5 @@ use crate::core::events::sendable_event_error::SendableEventError; +use enums::tauri::ux::tauri_event_name::TauriEventName; use log::info; use serde::Serialize; use std::fmt::Debug; @@ -29,7 +30,7 @@ struct BasicEventWrapper { pub trait BasicSendableEvent : Clone + Debug + Serialize { /// This is the name of the event that the frontend subscribes to. - const FRONTEND_EVENT_NAME: &'static str; + const FRONTEND_EVENT_NAME: TauriEventName; /// This is the status of the event: Success, Failure, etc. const EVENT_STATUS: BasicEventStatus; @@ -41,8 +42,8 @@ pub trait BasicSendableEvent : Clone + Debug + Serialize { status: Self::EVENT_STATUS, data: self.clone(), }; - info!("Sending event to frontend: {} ; payload = {:?}", Self::FRONTEND_EVENT_NAME, wrapped); - let result = app.emit(Self::FRONTEND_EVENT_NAME, wrapped); + info!("Sending event to frontend: {} ; payload = {:?}", Self::FRONTEND_EVENT_NAME.to_str(), wrapped); + let result = app.emit(Self::FRONTEND_EVENT_NAME.to_str(), wrapped); if let Err(err) = result { return Err(SendableEventError::from(err)); } diff --git a/crates/desktop/artcraft/src/core/events/functional_events/canvas_background_removal_complete_event.rs b/crates/desktop/artcraft/src/core/events/functional_events/canvas_background_removal_complete_event.rs index c9d25cd3aa..cf81281452 100644 --- a/crates/desktop/artcraft/src/core/events/functional_events/canvas_background_removal_complete_event.rs +++ b/crates/desktop/artcraft/src/core/events/functional_events/canvas_background_removal_complete_event.rs @@ -1,6 +1,7 @@ use crate::core::events::basic_sendable_event_trait::{BasicEventStatus, BasicSendableEvent}; use serde_derive::Serialize; use url::Url; +use enums::tauri::ux::tauri_event_name::TauriEventName; use tokens::tokens::media_files::MediaFileToken; #[derive(Clone, Debug, Serialize)] @@ -11,6 +12,6 @@ pub struct CanvasBackgroundRemovalCompleteEvent { } impl BasicSendableEvent for CanvasBackgroundRemovalCompleteEvent { - const FRONTEND_EVENT_NAME: &'static str = "canvas-background-removal-complete-event"; + const FRONTEND_EVENT_NAME: TauriEventName = TauriEventName::CanvasBgRemovedEvent; const EVENT_STATUS: BasicEventStatus = BasicEventStatus::Success; } diff --git a/crates/desktop/artcraft/src/core/events/generation_events/generation_complete_event.rs b/crates/desktop/artcraft/src/core/events/generation_events/generation_complete_event.rs index ff5fa40628..e1c225dc41 100644 --- a/crates/desktop/artcraft/src/core/events/generation_events/generation_complete_event.rs +++ b/crates/desktop/artcraft/src/core/events/generation_events/generation_complete_event.rs @@ -1,5 +1,6 @@ use crate::core::events::basic_sendable_event_trait::{BasicEventStatus, BasicSendableEvent}; use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider}; +use enums::tauri::ux::tauri_event_name::TauriEventName; use serde_derive::Serialize; #[derive(Clone, Debug, Serialize)] @@ -11,6 +12,6 @@ pub struct GenerationCompleteEvent { } impl BasicSendableEvent for GenerationCompleteEvent { - const FRONTEND_EVENT_NAME: &'static str = "generation-complete-event"; + const FRONTEND_EVENT_NAME: TauriEventName = TauriEventName::GenerationCompleteEvent; const EVENT_STATUS: BasicEventStatus = BasicEventStatus::Success; } diff --git a/crates/desktop/artcraft/src/core/events/generation_events/generation_enqueue_failure_event.rs b/crates/desktop/artcraft/src/core/events/generation_events/generation_enqueue_failure_event.rs index 8930bc5ca2..c55620f983 100644 --- a/crates/desktop/artcraft/src/core/events/generation_events/generation_enqueue_failure_event.rs +++ b/crates/desktop/artcraft/src/core/events/generation_events/generation_enqueue_failure_event.rs @@ -1,5 +1,6 @@ use crate::core::events::basic_sendable_event_trait::{BasicEventStatus, BasicSendableEvent}; use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider}; +use enums::tauri::ux::tauri_event_name::TauriEventName; use serde_derive::Serialize; #[derive(Clone, Debug, Serialize)] @@ -14,7 +15,7 @@ pub struct GenerationEnqueueFailureEvent { } impl BasicSendableEvent for GenerationEnqueueFailureEvent { - const FRONTEND_EVENT_NAME: &'static str = "generation-enqueue-failure-event"; + const FRONTEND_EVENT_NAME: TauriEventName = TauriEventName::GenerationEnqueueFailureEvent; const EVENT_STATUS: BasicEventStatus = BasicEventStatus::Failure; } diff --git a/crates/desktop/artcraft/src/core/events/generation_events/generation_enqueue_success_event.rs b/crates/desktop/artcraft/src/core/events/generation_events/generation_enqueue_success_event.rs index 2d219f663a..7737d3477a 100644 --- a/crates/desktop/artcraft/src/core/events/generation_events/generation_enqueue_success_event.rs +++ b/crates/desktop/artcraft/src/core/events/generation_events/generation_enqueue_success_event.rs @@ -1,5 +1,6 @@ use crate::core::events::basic_sendable_event_trait::{BasicEventStatus, BasicSendableEvent}; use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider}; +use enums::tauri::ux::tauri_event_name::TauriEventName; use serde_derive::Serialize; #[derive(Clone, Debug, Serialize)] @@ -11,6 +12,6 @@ pub struct GenerationEnqueueSuccessEvent { } impl BasicSendableEvent for GenerationEnqueueSuccessEvent { - const FRONTEND_EVENT_NAME: &'static str = "generation-enqueue-success-event"; + const FRONTEND_EVENT_NAME: TauriEventName = TauriEventName::GenerationEnqueueSuccessEvent; const EVENT_STATUS: BasicEventStatus = BasicEventStatus::Success; } diff --git a/crates/desktop/artcraft/src/core/events/generation_events/generation_failed_event.rs b/crates/desktop/artcraft/src/core/events/generation_events/generation_failed_event.rs index 6ea7893432..f4d580fc43 100644 --- a/crates/desktop/artcraft/src/core/events/generation_events/generation_failed_event.rs +++ b/crates/desktop/artcraft/src/core/events/generation_events/generation_failed_event.rs @@ -1,5 +1,6 @@ use crate::core::events::basic_sendable_event_trait::{BasicEventStatus, BasicSendableEvent}; use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider}; +use enums::tauri::ux::tauri_event_name::TauriEventName; use serde_derive::Serialize; #[derive(Clone, Debug, Serialize)] @@ -14,6 +15,6 @@ pub struct GenerationFailedEvent { } impl BasicSendableEvent for GenerationFailedEvent { - const FRONTEND_EVENT_NAME: &'static str = "generation-failed-event"; + const FRONTEND_EVENT_NAME: TauriEventName = TauriEventName::GenerationFailedEvent; const EVENT_STATUS: BasicEventStatus = BasicEventStatus::Failure; } diff --git a/crates/schema/public/enums/src/common/generation_provider.rs b/crates/schema/public/enums/src/common/generation_provider.rs index dfbe54d729..cb134ec96e 100644 --- a/crates/schema/public/enums/src/common/generation_provider.rs +++ b/crates/schema/public/enums/src/common/generation_provider.rs @@ -37,7 +37,7 @@ impl GenerationProvider { "artcraft" => Ok(Self::Artcraft), "fal" => Ok(Self::Fal), "sora" => Ok(Self::Sora), - _ => Err(format!("invalid task_state: {:?}", job_status)), + _ => Err(format!("invalid generation_provider: {:?}", job_status)), } } diff --git a/crates/schema/public/enums/src/common/model_type.rs b/crates/schema/public/enums/src/common/model_type.rs index b134bd03b5..8f73c0bf61 100644 --- a/crates/schema/public/enums/src/common/model_type.rs +++ b/crates/schema/public/enums/src/common/model_type.rs @@ -108,7 +108,7 @@ impl ModelType { "hunyuan_3d_2p0" => Ok(Self::Hunyuan3d2_0), "hunyuan_3d_2p1" => Ok(Self::Hunyuan3d2_1), - _ => Err(format!("invalid task_state: {:?}", job_status)), + _ => Err(format!("invalid model_type: {:?}", job_status)), } } diff --git a/crates/schema/public/enums/src/tauri/mod.rs b/crates/schema/public/enums/src/tauri/mod.rs index 8c20e3c5c5..b6956616d1 100644 --- a/crates/schema/public/enums/src/tauri/mod.rs +++ b/crates/schema/public/enums/src/tauri/mod.rs @@ -1 +1,2 @@ -pub mod tasks; \ No newline at end of file +pub mod tasks; +pub mod ux; \ No newline at end of file diff --git a/crates/schema/public/enums/src/tauri/ux/mod.rs b/crates/schema/public/enums/src/tauri/ux/mod.rs new file mode 100644 index 0000000000..40215426d6 --- /dev/null +++ b/crates/schema/public/enums/src/tauri/ux/mod.rs @@ -0,0 +1,2 @@ +pub mod tauri_command_caller; +pub mod tauri_event_name; diff --git a/crates/schema/public/enums/src/tauri/ux/tauri_command_caller.rs b/crates/schema/public/enums/src/tauri/ux/tauri_command_caller.rs new file mode 100644 index 0000000000..f0edff034a --- /dev/null +++ b/crates/schema/public/enums/src/tauri/ux/tauri_command_caller.rs @@ -0,0 +1,109 @@ +use std::collections::BTreeSet; + +#[cfg(test)] +use strum::EnumCount; +#[cfg(test)] +use strum::EnumIter; +use utoipa::ToSchema; + +/// Defines the names of the Tauri-sent events that the frontend subscribes to. +/// These event names are also stored in the database, so keep them short-ish. +#[cfg_attr(test, derive(EnumIter, EnumCount))] +#[derive(Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd, Serialize, Deserialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum TauriCommandCaller { + /// The 2D canvas + Canvas, +} + +impl_enum_display_and_debug_using_to_str!(TauriCommandCaller); +impl_mysql_enum_coders!(TauriCommandCaller); +impl_mysql_from_row!(TauriCommandCaller); + +// NB: We can derive `sqlx::Type` instead of using `impl_mysql_enum_coders` + +impl TauriCommandCaller { + pub fn to_str(&self) -> &'static str { + match self { + Self::Canvas => "canvas", + } + } + + pub fn from_str(job_status: &str) -> Result { + match job_status { + "canvas" => Ok(Self::Canvas), + _ => Err(format!("invalid tauri_command_caller: {:?}", job_status)), + } + } + + pub fn all_variants() -> BTreeSet { + // NB: BTreeSet is sorted + // NB: BTreeSet::from() isn't const, but not worth using LazyStatic, etc. + BTreeSet::from([ + Self::Canvas, + ]) + } +} + +#[cfg(test)] +mod tests { + use crate::test_helpers::assert_serialization; + use crate::tauri::ux::tauri_command_caller::TauriCommandCaller; + + mod explicit_checks { + use super::*; + + #[test] + fn test_serialization() { + assert_serialization(TauriCommandCaller::Canvas, "canvas"); + } + + #[test] + fn to_str() { + assert_eq!(TauriCommandCaller::Canvas.to_str(), "canvas"); + } + + #[test] + fn from_str() { + assert_eq!(TauriCommandCaller::from_str("canvas").unwrap(), TauriCommandCaller::Canvas); + } + + #[test] + fn all_variants() { + let mut variants = TauriCommandCaller::all_variants(); + assert_eq!(variants.len(), 1); + assert_eq!(variants.pop_first(), Some(TauriCommandCaller::Canvas)); + assert_eq!(variants.pop_first(), None); + } + } + + mod mechanical_checks { + use super::*; + use strum::IntoEnumIterator; + + #[test] + fn variant_length() { + assert_eq!(TauriCommandCaller::all_variants().len(), TauriCommandCaller::iter().len()); + } + + #[test] + fn round_trip() { + for variant in TauriCommandCaller::all_variants() { + // Test to_str(), from_str(), Display, and Debug. + assert_eq!(variant, TauriCommandCaller::from_str(variant.to_str()).unwrap()); + assert_eq!(variant, TauriCommandCaller::from_str(&format!("{}", variant)).unwrap()); + assert_eq!(variant, TauriCommandCaller::from_str(&format!("{:?}", variant)).unwrap()); + } + } + + //#[test] + //fn serialized_length_ok_for_database() { + // const MAX_LENGTH : usize = 16; + // for variant in TauriCommandCaller::all_variants() { + // let serialized = variant.to_str(); + // assert!(serialized.len() > 0, "variant {:?} is too short", variant); + // assert!(serialized.len() <= MAX_LENGTH, "variant {:?} is too long", variant); + // } + //} + } +} diff --git a/crates/schema/public/enums/src/tauri/ux/tauri_event_name.rs b/crates/schema/public/enums/src/tauri/ux/tauri_event_name.rs new file mode 100644 index 0000000000..68eb5dbcc3 --- /dev/null +++ b/crates/schema/public/enums/src/tauri/ux/tauri_event_name.rs @@ -0,0 +1,168 @@ +use std::collections::BTreeSet; + +#[cfg(test)] +use strum::EnumCount; +#[cfg(test)] +use strum::EnumIter; +use utoipa::ToSchema; + +/// Defines the names of the Tauri-sent events that the frontend subscribes to. +/// These event names are also stored in the database, so keep them short-ish. +/// +/// NB: Events should end in "_event" so they're easy to grep for in Javascript. +#[cfg_attr(test, derive(EnumIter, EnumCount))] +#[derive(Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd, Serialize, Deserialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum TauriEventName { + // TODO: Get rid of kebab case. + /// General purpose event: + /// Generation enqueued + #[serde(rename = "generation-enqueue-success-event")] + GenerationEnqueueSuccessEvent, + + // TODO: Get rid of kebab case. + /// General purpose event: + /// Generation failed to enqueue + #[serde(rename = "generation-enqueue-failure-event")] + GenerationEnqueueFailureEvent, + + // TODO: Get rid of kebab case. + /// General purpose event: + /// Generation completed successfully + #[serde(rename = "generation-complete-event")] + GenerationCompleteEvent, + + // TODO: Get rid of kebab case. + /// General purpose event: + /// Generation failed + #[serde(rename = "generation-failed-event")] + GenerationFailedEvent, + + /// Special event: + /// Background removal complete + #[serde(rename = "canvas_bg_removed_event")] + CanvasBgRemovedEvent, +} + +impl_enum_display_and_debug_using_to_str!(TauriEventName); +impl_mysql_enum_coders!(TauriEventName); +impl_mysql_from_row!(TauriEventName); + +// NB: We can derive `sqlx::Type` instead of using `impl_mysql_enum_coders` + +impl TauriEventName { + pub fn to_str(&self) -> &'static str { + match self { + Self::GenerationEnqueueSuccessEvent => "generation-enqueue-success-event", + Self::GenerationEnqueueFailureEvent => "generation-enqueue-failure-event", + Self::GenerationCompleteEvent => "generation-complete-event", + Self::GenerationFailedEvent => "generation-failed-event", + Self::CanvasBgRemovedEvent => "canvas_bg_removed_event", + } + } + + pub fn from_str(job_status: &str) -> Result { + match job_status { + "generation-enqueue-success-event" => Ok(Self::GenerationEnqueueSuccessEvent), + "generation-enqueue-failure-event" => Ok(Self::GenerationEnqueueFailureEvent), + "generation-complete-event" => Ok(Self::GenerationCompleteEvent), + "generation-failed-event" => Ok(Self::GenerationFailedEvent), + "canvas_bg_removed_event" => Ok(Self::CanvasBgRemovedEvent), + _ => Err(format!("invalid tauri_event_name: {:?}", job_status)), + } + } + + pub fn all_variants() -> BTreeSet { + // NB: BTreeSet is sorted + // NB: BTreeSet::from() isn't const, but not worth using LazyStatic, etc. + BTreeSet::from([ + Self::GenerationEnqueueSuccessEvent, + Self::GenerationEnqueueFailureEvent, + Self::GenerationCompleteEvent, + Self::GenerationFailedEvent, + Self::CanvasBgRemovedEvent, + ]) + } +} + +#[cfg(test)] +mod tests { + use crate::tauri::ux::tauri_event_name::TauriEventName; + use crate::test_helpers::assert_serialization; + + mod explicit_checks { + use super::*; + + #[test] + fn test_serialization() { + assert_serialization(TauriEventName::GenerationEnqueueSuccessEvent, "generation-enqueue-success-event"); + assert_serialization(TauriEventName::GenerationEnqueueFailureEvent, "generation-enqueue-failure-event"); + assert_serialization(TauriEventName::GenerationCompleteEvent, "generation-complete-event"); + assert_serialization(TauriEventName::GenerationFailedEvent, "generation-failed-event"); + assert_serialization(TauriEventName::CanvasBgRemovedEvent, "canvas_bg_removed_event"); + } + + #[test] + fn to_str() { + assert_eq!(TauriEventName::GenerationEnqueueSuccessEvent.to_str(), "generation-enqueue-success-event"); + assert_eq!(TauriEventName::GenerationEnqueueFailureEvent.to_str(), "generation-enqueue-failure-event"); + assert_eq!(TauriEventName::GenerationCompleteEvent.to_str(), "generation-complete-event"); + assert_eq!(TauriEventName::GenerationFailedEvent.to_str(), "generation-failed-event"); + assert_eq!(TauriEventName::CanvasBgRemovedEvent.to_str(), "canvas_bg_removed_event"); + + + + } + + #[test] + fn from_str() { + assert_eq!(TauriEventName::from_str("generation-enqueue-success-event").unwrap(), TauriEventName::GenerationEnqueueSuccessEvent); + assert_eq!(TauriEventName::from_str("generation-enqueue-failure-event").unwrap(), TauriEventName::GenerationEnqueueFailureEvent); + assert_eq!(TauriEventName::from_str("generation-complete-event").unwrap(), TauriEventName::GenerationCompleteEvent); + assert_eq!(TauriEventName::from_str("generation-failed-event").unwrap(), TauriEventName::GenerationFailedEvent); + assert_eq!(TauriEventName::from_str("canvas_bg_removed_event").unwrap(), TauriEventName::CanvasBgRemovedEvent); + } + + #[test] + fn all_variants() { + let mut variants = TauriEventName::all_variants(); + assert_eq!(variants.len(), 5); + assert_eq!(variants.pop_first(), Some(TauriEventName::GenerationEnqueueSuccessEvent)); + assert_eq!(variants.pop_first(), Some(TauriEventName::GenerationEnqueueFailureEvent)); + assert_eq!(variants.pop_first(), Some(TauriEventName::GenerationCompleteEvent)); + assert_eq!(variants.pop_first(), Some(TauriEventName::GenerationFailedEvent)); + assert_eq!(variants.pop_first(), Some(TauriEventName::CanvasBgRemovedEvent)); + assert_eq!(variants.pop_first(), None); + } + } + + mod mechanical_checks { + use super::*; + use strum::IntoEnumIterator; + + #[test] + fn variant_length() { + assert_eq!(TauriEventName::all_variants().len(), TauriEventName::iter().len()); + } + + #[test] + fn round_trip() { + for variant in TauriEventName::all_variants() { + // Test to_str(), from_str(), Display, and Debug. + assert_eq!(variant, TauriEventName::from_str(variant.to_str()).unwrap()); + assert_eq!(variant, TauriEventName::from_str(&format!("{}", variant)).unwrap()); + assert_eq!(variant, TauriEventName::from_str(&format!("{:?}", variant)).unwrap()); + } + } + + //#[test] + //fn serialized_length_ok_for_database() { + // const MAX_LENGTH : usize = 16; + // for variant in TauriEventName::all_variants() { + // let serialized = variant.to_str(); + // assert!(serialized.len() > 0, "variant {:?} is too short", variant); + // assert!(serialized.len() <= MAX_LENGTH, "variant {:?} is too long", variant); + // } + //} + } +}