From 02a574b24e9b89b98a18d390c101e7104d2c868c Mon Sep 17 00:00:00 2001 From: Sienna Meridian Satterwhite Date: Tue, 7 Apr 2026 18:44:21 +0100 Subject: [PATCH] style: apply cargo fmt workspace-wide Pure formatting pass from `cargo fmt --all`. No logic changes. Separating this out so the 1.9 release feature commits that follow show only their intentional edits. --- wfe-buildkit/src/lib.rs | 2 +- wfe-buildkit/src/step.rs | 50 +- wfe-buildkit/tests/integration_test.rs | 42 +- wfe-containerd/src/config.rs | 6 +- wfe-containerd/src/step.rs | 260 +++++----- wfe-containerd/tests/integration.rs | 18 +- wfe-core/src/builder/step_builder.rs | 43 +- wfe-core/src/builder/workflow_builder.rs | 11 +- wfe-core/src/executor/condition.rs | 33 +- wfe-core/src/executor/error_handler.rs | 14 +- wfe-core/src/executor/result_processor.rs | 15 +- wfe-core/src/executor/step_registry.rs | 3 +- wfe-core/src/executor/workflow_executor.rs | 472 ++++++++++++------ wfe-core/src/models/condition.rs | 24 +- wfe-core/src/models/event.rs | 6 +- wfe-core/src/models/execution_result.rs | 14 +- wfe-core/src/models/lifecycle.rs | 19 +- wfe-core/src/models/mod.rs | 6 +- wfe-core/src/models/schema.rs | 10 +- wfe-core/src/primitives/foreach_step.rs | 5 +- wfe-core/src/primitives/if_step.rs | 10 +- wfe-core/src/primitives/mod.rs | 2 +- wfe-core/src/primitives/poll_endpoint.rs | 4 +- wfe-core/src/primitives/recur.rs | 10 +- wfe-core/src/primitives/saga_container.rs | 5 +- wfe-core/src/primitives/schedule.rs | 10 +- wfe-core/src/primitives/sequence.rs | 5 +- wfe-core/src/primitives/while_step.rs | 10 +- .../src/test_support/in_memory_lifecycle.rs | 2 +- wfe-core/src/test_support/in_memory_lock.rs | 2 +- wfe-core/src/test_support/in_memory_queue.rs | 2 +- wfe-core/src/test_support/queue_suite.rs | 39 +- wfe-core/src/traits/middleware.rs | 4 +- wfe-core/src/traits/mod.rs | 2 +- wfe-core/src/traits/step.rs | 2 +- wfe-deno/src/ops/builder.rs | 2 +- wfe-deno/src/ops/event.rs | 2 +- wfe-deno/src/ops/host.rs | 2 +- wfe-deno/src/ops/step.rs | 9 +- wfe-deno/src/ops/workflow.rs | 2 +- wfe-deno/tests/integration.rs | 6 +- wfe-kubernetes/src/logs.rs | 8 +- wfe-kubernetes/src/namespace.rs | 20 +- wfe-kubernetes/src/output.rs | 10 +- wfe-kubernetes/src/service_manifests.rs | 22 +- wfe-kubernetes/src/service_provider.rs | 9 +- wfe-kubernetes/src/step.rs | 48 +- wfe-kubernetes/tests/integration.rs | 41 +- wfe-opensearch/src/lib.rs | 2 +- wfe-opensearch/tests/search.rs | 9 +- wfe-rustlang/src/cargo/config.rs | 15 +- wfe-rustlang/src/cargo/step.rs | 252 +++++++--- wfe-rustlang/src/rustdoc/transformer.rs | 385 ++++++++++---- wfe-rustlang/src/rustup/config.rs | 21 +- wfe-rustlang/src/rustup/step.rs | 129 ++++- wfe-server-protos/build.rs | 6 +- wfe-server-protos/src/lib.rs | 3 +- wfe-server/src/auth.rs | 35 +- wfe-server/src/config.rs | 38 +- wfe-server/src/lifecycle_bus.rs | 22 +- wfe-server/src/log_search.rs | 62 ++- wfe-server/src/log_store.rs | 36 +- wfe-server/src/main.rs | 44 +- wfe-server/src/webhook.rs | 24 +- wfe-valkey/src/lifecycle.rs | 5 +- wfe-valkey/tests/lifecycle.rs | 24 +- wfe-valkey/tests/lock.rs | 4 +- wfe-valkey/tests/queue.rs | 4 +- wfe-yaml/src/executors/deno/module_loader.rs | 54 +- wfe-yaml/src/executors/deno/ops/http.rs | 2 +- wfe-yaml/src/executors/deno/ops/workflow.rs | 19 +- wfe-yaml/src/executors/deno/permissions.rs | 21 +- wfe-yaml/src/executors/deno/runtime.rs | 4 +- wfe-yaml/src/executors/deno/step.rs | 11 +- wfe-yaml/src/executors/shell.rs | 50 +- wfe-yaml/src/lib.rs | 62 +-- wfe-yaml/src/types.rs | 4 +- wfe-yaml/src/validation.rs | 14 +- wfe-yaml/tests/compiler.rs | 78 ++- wfe-yaml/tests/deno.rs | 42 +- wfe-yaml/tests/deno_e2e.rs | 36 +- wfe-yaml/tests/lib_tests.rs | 17 +- wfe-yaml/tests/rustlang_containerd.rs | 131 +++-- wfe-yaml/tests/schema.rs | 15 +- wfe-yaml/tests/shell.rs | 75 ++- wfe-yaml/tests/types.rs | 2 +- wfe-yaml/tests/validation.rs | 432 ++++++++++++---- wfe/examples/pizza.rs | 81 ++- wfe/examples/run_pipeline.rs | 18 +- wfe/src/host_builder.rs | 15 +- wfe/src/purger.rs | 2 +- wfe/tests/e2e_compensation.rs | 13 +- wfe/tests/e2e_delay.rs | 13 +- wfe/tests/e2e_error_handling.rs | 5 +- wfe/tests/e2e_events.rs | 11 +- wfe/tests/e2e_foreach.rs | 3 +- wfe/tests/e2e_linear.rs | 3 +- wfe/tests/e2e_parallel.rs | 9 +- wfe/tests/e2e_versioning.rs | 31 +- wfe/tests/e2e_while.rs | 3 +- wfe/tests/host_tests.rs | 29 +- wfe/tests/nested_workflow_tests.rs | 6 +- 102 files changed, 2467 insertions(+), 1307 deletions(-) diff --git a/wfe-buildkit/src/lib.rs b/wfe-buildkit/src/lib.rs index 2c3b902..336de52 100644 --- a/wfe-buildkit/src/lib.rs +++ b/wfe-buildkit/src/lib.rs @@ -2,4 +2,4 @@ pub mod config; pub mod step; pub use config::{BuildkitConfig, RegistryAuth, TlsConfig}; -pub use step::{build_output_data, parse_digest, BuildkitStep}; +pub use step::{BuildkitStep, build_output_data, parse_digest}; diff --git a/wfe-buildkit/src/step.rs b/wfe-buildkit/src/step.rs index 1f96566..f2d7f8f 100644 --- a/wfe-buildkit/src/step.rs +++ b/wfe-buildkit/src/step.rs @@ -9,9 +9,9 @@ use wfe_buildkit_protos::moby::buildkit::v1::control_client::ControlClient; use wfe_buildkit_protos::moby::buildkit::v1::{ CacheOptions, CacheOptionsEntry, Exporter, SolveRequest, StatusRequest, }; +use wfe_core::WfeError; use wfe_core::models::ExecutionResult; use wfe_core::traits::step::{StepBody, StepExecutionContext}; -use wfe_core::WfeError; use crate::config::BuildkitConfig; @@ -45,10 +45,7 @@ impl BuildkitStep { tracing::info!(addr = %addr, "connecting to BuildKit daemon"); let channel = if addr.starts_with("unix://") { - let socket_path = addr - .strip_prefix("unix://") - .unwrap() - .to_string(); + let socket_path = addr.strip_prefix("unix://").unwrap().to_string(); // Verify the socket exists before attempting connection. if !Path::new(&socket_path).exists() { @@ -60,9 +57,7 @@ impl BuildkitStep { // tonic requires a dummy URI for Unix sockets; the actual path // is provided via the connector. Endpoint::try_from("http://[::]:50051") - .map_err(|e| { - WfeError::StepExecution(format!("failed to create endpoint: {e}")) - })? + .map_err(|e| WfeError::StepExecution(format!("failed to create endpoint: {e}")))? .connect_with_connector(tower::service_fn(move |_: Uri| { let path = socket_path.clone(); async move { @@ -231,10 +226,7 @@ impl BuildkitStep { let context_name = "context"; let dockerfile_name = "dockerfile"; - frontend_attrs.insert( - "context".to_string(), - format!("local://{context_name}"), - ); + frontend_attrs.insert("context".to_string(), format!("local://{context_name}")); frontend_attrs.insert( format!("local-sessionid:{context_name}"), session_id.clone(), @@ -276,20 +268,18 @@ impl BuildkitStep { // The x-docker-expose-session-uuid header tells buildkitd which // session owns the local sources. The x-docker-expose-session-grpc-method // header lists the gRPC methods the session implements. - if let Ok(key) = - "x-docker-expose-session-uuid" - .parse::>() - && let Ok(val) = session_id - .parse::>() + if let Ok(key) = "x-docker-expose-session-uuid" + .parse::>() + && let Ok(val) = + session_id.parse::>() { metadata.insert(key, val); } // Advertise the filesync method so the daemon knows it can request // local file content from our session. - if let Ok(key) = - "x-docker-expose-session-grpc-method" - .parse::>() + if let Ok(key) = "x-docker-expose-session-grpc-method" + .parse::>() { if let Ok(val) = "/moby.filesync.v1.FileSync/DiffCopy" .parse::>() @@ -598,7 +588,8 @@ mod tests { #[test] fn parse_digest_with_digest_prefix() { - let output = "digest: sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef\n"; + let output = + "digest: sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef\n"; let digest = parse_digest(output); assert_eq!( digest, @@ -630,8 +621,7 @@ mod tests { #[test] fn parse_digest_wrong_prefix() { - let output = - "sha256:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"; + let output = "sha256:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"; assert_eq!(parse_digest(output), None); } @@ -651,7 +641,10 @@ mod tests { "#; assert_eq!( parse_digest(output), - Some("sha256:aabbccdd0011223344556677aabbccdd0011223344556677aabbccdd00112233".to_string()) + Some( + "sha256:aabbccdd0011223344556677aabbccdd0011223344556677aabbccdd00112233" + .to_string() + ) ); } @@ -659,9 +652,7 @@ mod tests { fn parse_digest_first_match_wins() { let hash1 = "a".repeat(64); let hash2 = "b".repeat(64); - let output = format!( - "exporting manifest sha256:{hash1}\ndigest: sha256:{hash2}" - ); + let output = format!("exporting manifest sha256:{hash1}\ndigest: sha256:{hash2}"); let digest = parse_digest(&output).unwrap(); assert_eq!(digest, format!("sha256:{hash1}")); } @@ -806,10 +797,7 @@ mod tests { exporters[0].attrs.get("name"), Some(&"myapp:latest,myapp:v1.0".to_string()) ); - assert_eq!( - exporters[0].attrs.get("push"), - Some(&"true".to_string()) - ); + assert_eq!(exporters[0].attrs.get("push"), Some(&"true".to_string())); } #[test] diff --git a/wfe-buildkit/tests/integration_test.rs b/wfe-buildkit/tests/integration_test.rs index 9f1bafa..fd09ef5 100644 --- a/wfe-buildkit/tests/integration_test.rs +++ b/wfe-buildkit/tests/integration_test.rs @@ -9,17 +9,16 @@ use std::collections::HashMap; use std::path::Path; -use wfe_buildkit::config::{BuildkitConfig, TlsConfig}; use wfe_buildkit::BuildkitStep; +use wfe_buildkit::config::{BuildkitConfig, TlsConfig}; use wfe_core::models::{ExecutionPointer, WorkflowInstance, WorkflowStep}; use wfe_core::traits::step::{StepBody, StepExecutionContext}; /// Get the BuildKit daemon address from the environment or use the default. fn buildkit_addr() -> String { - std::env::var("WFE_BUILDKIT_ADDR").unwrap_or_else(|_| { - "unix:///Users/sienna/.lima/wfe-test/sock/buildkitd.sock".to_string() - }) + std::env::var("WFE_BUILDKIT_ADDR") + .unwrap_or_else(|_| "unix:///Users/sienna/.lima/wfe-test/sock/buildkitd.sock".to_string()) } /// Check whether the BuildKit daemon socket is reachable. @@ -33,13 +32,7 @@ fn buildkitd_available() -> bool { } } -fn make_test_context( - step_name: &str, -) -> ( - WorkflowStep, - ExecutionPointer, - WorkflowInstance, -) { +fn make_test_context(step_name: &str) -> (WorkflowStep, ExecutionPointer, WorkflowInstance) { let mut step = WorkflowStep::new(0, "buildkit"); step.name = Some(step_name.to_string()); let pointer = ExecutionPointer::new(0); @@ -50,21 +43,14 @@ fn make_test_context( #[tokio::test] async fn build_simple_dockerfile_via_grpc() { if !buildkitd_available() { - eprintln!( - "SKIP: BuildKit daemon not available at {}", - buildkit_addr() - ); + eprintln!("SKIP: BuildKit daemon not available at {}", buildkit_addr()); return; } // Create a temp directory with a trivial Dockerfile. let tmp = tempfile::tempdir().unwrap(); let dockerfile = tmp.path().join("Dockerfile"); - std::fs::write( - &dockerfile, - "FROM alpine:latest\nRUN echo built\n", - ) - .unwrap(); + std::fs::write(&dockerfile, "FROM alpine:latest\nRUN echo built\n").unwrap(); let config = BuildkitConfig { dockerfile: "Dockerfile".to_string(), @@ -94,7 +80,7 @@ async fn build_simple_dockerfile_via_grpc() { workflow: &instance, cancellation_token: cancel, host_context: None, - log_sink: None, + log_sink: None, }; let result = step.run(&ctx).await.expect("build should succeed"); @@ -135,10 +121,7 @@ async fn build_simple_dockerfile_via_grpc() { #[tokio::test] async fn build_with_build_args() { if !buildkitd_available() { - eprintln!( - "SKIP: BuildKit daemon not available at {}", - buildkit_addr() - ); + eprintln!("SKIP: BuildKit daemon not available at {}", buildkit_addr()); return; } @@ -181,10 +164,13 @@ async fn build_with_build_args() { workflow: &instance, cancellation_token: cancel, host_context: None, - log_sink: None, + log_sink: None, }; - let result = step.run(&ctx).await.expect("build with args should succeed"); + let result = step + .run(&ctx) + .await + .expect("build with args should succeed"); assert!(result.proceed); let data = result.output_data.expect("should have output_data"); @@ -229,7 +215,7 @@ async fn connect_to_unavailable_daemon_returns_error() { workflow: &instance, cancellation_token: cancel, host_context: None, - log_sink: None, + log_sink: None, }; let err = step.run(&ctx).await; diff --git a/wfe-containerd/src/config.rs b/wfe-containerd/src/config.rs index 702c4a6..4554649 100644 --- a/wfe-containerd/src/config.rs +++ b/wfe-containerd/src/config.rs @@ -133,7 +133,11 @@ mod tests { assert_eq!(deserialized.tls.ca, Some("/ca.pem".to_string())); assert_eq!(deserialized.tls.cert, Some("/cert.pem".to_string())); assert_eq!(deserialized.tls.key, Some("/key.pem".to_string())); - assert!(deserialized.registry_auth.contains_key("registry.example.com")); + assert!( + deserialized + .registry_auth + .contains_key("registry.example.com") + ); assert_eq!(deserialized.timeout_ms, Some(30000)); } diff --git a/wfe-containerd/src/step.rs b/wfe-containerd/src/step.rs index 2d13ddc..ba34fe6 100644 --- a/wfe-containerd/src/step.rs +++ b/wfe-containerd/src/step.rs @@ -8,21 +8,20 @@ use wfe_core::models::ExecutionResult; use wfe_core::traits::step::{StepBody, StepExecutionContext}; use wfe_containerd_protos::containerd::services::containers::v1::{ - containers_client::ContainersClient, Container, CreateContainerRequest, - DeleteContainerRequest, container::Runtime, + Container, CreateContainerRequest, DeleteContainerRequest, container::Runtime, + containers_client::ContainersClient, }; use wfe_containerd_protos::containerd::services::content::v1::{ - content_client::ContentClient, ReadContentRequest, + ReadContentRequest, content_client::ContentClient, }; use wfe_containerd_protos::containerd::services::images::v1::{ - images_client::ImagesClient, GetImageRequest, + GetImageRequest, images_client::ImagesClient, }; use wfe_containerd_protos::containerd::services::snapshots::v1::{ - snapshots_client::SnapshotsClient, MountsRequest, PrepareSnapshotRequest, + MountsRequest, PrepareSnapshotRequest, snapshots_client::SnapshotsClient, }; use wfe_containerd_protos::containerd::services::tasks::v1::{ - tasks_client::TasksClient, CreateTaskRequest, DeleteTaskRequest, StartRequest, - WaitRequest, + CreateTaskRequest, DeleteTaskRequest, StartRequest, WaitRequest, tasks_client::TasksClient, }; use wfe_containerd_protos::containerd::services::version::v1::version_client::VersionClient; @@ -49,10 +48,7 @@ impl ContainerdStep { /// TCP/HTTP endpoints. pub(crate) async fn connect(addr: &str) -> Result { let channel = if addr.starts_with('/') || addr.starts_with("unix://") { - let socket_path = addr - .strip_prefix("unix://") - .unwrap_or(addr) - .to_string(); + let socket_path = addr.strip_prefix("unix://").unwrap_or(addr).to_string(); if !Path::new(&socket_path).exists() { return Err(WfeError::StepExecution(format!( @@ -61,9 +57,7 @@ impl ContainerdStep { } Endpoint::try_from("http://[::]:50051") - .map_err(|e| { - WfeError::StepExecution(format!("failed to create endpoint: {e}")) - })? + .map_err(|e| WfeError::StepExecution(format!("failed to create endpoint: {e}")))? .connect_with_connector(tower::service_fn(move |_: Uri| { let path = socket_path.clone(); async move { @@ -112,20 +106,14 @@ impl ContainerdStep { /// `ctr image pull` or `nerdctl pull`. /// /// TODO: implement full image pull via TransferService or content ingest. - async fn ensure_image( - channel: &Channel, - image: &str, - namespace: &str, - ) -> Result<(), WfeError> { + async fn ensure_image(channel: &Channel, image: &str, namespace: &str) -> Result<(), WfeError> { let mut client = ImagesClient::new(channel.clone()); let mut req = tonic::Request::new(GetImageRequest { name: image.to_string(), }); - req.metadata_mut().insert( - "containerd-namespace", - namespace.parse().unwrap(), - ); + req.metadata_mut() + .insert("containerd-namespace", namespace.parse().unwrap()); match client.get(req).await { Ok(_) => Ok(()), @@ -151,20 +139,24 @@ impl ContainerdStep { image: &str, namespace: &str, ) -> Result { - use sha2::{Sha256, Digest}; + use sha2::{Digest, Sha256}; // 1. Get the image record to find the manifest digest. let mut images_client = ImagesClient::new(channel.clone()); let req = Self::with_namespace( - GetImageRequest { name: image.to_string() }, + GetImageRequest { + name: image.to_string(), + }, namespace, ); - let image_resp = images_client.get(req).await.map_err(|e| { - WfeError::StepExecution(format!("failed to get image '{image}': {e}")) - })?; - let img = image_resp.into_inner().image.ok_or_else(|| { - WfeError::StepExecution(format!("image '{image}' has no record")) - })?; + let image_resp = images_client + .get(req) + .await + .map_err(|e| WfeError::StepExecution(format!("failed to get image '{image}': {e}")))?; + let img = image_resp + .into_inner() + .image + .ok_or_else(|| WfeError::StepExecution(format!("image '{image}' has no record")))?; let target = img.target.ok_or_else(|| { WfeError::StepExecution(format!("image '{image}' has no target descriptor")) })?; @@ -188,22 +180,26 @@ impl ContainerdStep { let manifests = manifest_json["manifests"].as_array().ok_or_else(|| { WfeError::StepExecution("image index has no manifests array".to_string()) })?; - let platform_manifest = manifests.iter().find(|m| { - m.get("platform") - .and_then(|p| p.get("architecture")) - .and_then(|a| a.as_str()) - == Some(oci_arch) - }).ok_or_else(|| { - WfeError::StepExecution(format!( - "no manifest for architecture '{oci_arch}' in image index" - )) - })?; + let platform_manifest = manifests + .iter() + .find(|m| { + m.get("platform") + .and_then(|p| p.get("architecture")) + .and_then(|a| a.as_str()) + == Some(oci_arch) + }) + .ok_or_else(|| { + WfeError::StepExecution(format!( + "no manifest for architecture '{oci_arch}' in image index" + )) + })?; let digest = platform_manifest["digest"].as_str().ok_or_else(|| { WfeError::StepExecution("platform manifest has no digest".to_string()) })?; let bytes = Self::read_content(channel, digest, namespace).await?; - serde_json::from_slice(&bytes) - .map_err(|e| WfeError::StepExecution(format!("failed to parse platform manifest: {e}")))? + serde_json::from_slice(&bytes).map_err(|e| { + WfeError::StepExecution(format!("failed to parse platform manifest: {e}")) + })? } else { manifest_json }; @@ -211,9 +207,7 @@ impl ContainerdStep { // 3. Get the config digest from the manifest. let config_digest = manifest_json["config"]["digest"] .as_str() - .ok_or_else(|| { - WfeError::StepExecution("manifest has no config.digest".to_string()) - })?; + .ok_or_else(|| WfeError::StepExecution("manifest has no config.digest".to_string()))?; // 4. Read the image config. let config_bytes = Self::read_content(channel, config_digest, namespace).await?; @@ -239,9 +233,9 @@ impl ContainerdStep { .to_string(); for diff_id in &diff_ids[1..] { - let diff = diff_id.as_str().ok_or_else(|| { - WfeError::StepExecution("diff_id is not a string".to_string()) - })?; + let diff = diff_id + .as_str() + .ok_or_else(|| WfeError::StepExecution("diff_id is not a string".to_string()))?; let mut hasher = Sha256::new(); hasher.update(format!("{chain_id} {diff}")); chain_id = format!("sha256:{:x}", hasher.finalize()); @@ -269,9 +263,11 @@ impl ContainerdStep { namespace, ); - let mut stream = client.read(req).await.map_err(|e| { - WfeError::StepExecution(format!("failed to read content {digest}: {e}")) - })?.into_inner(); + let mut stream = client + .read(req) + .await + .map_err(|e| WfeError::StepExecution(format!("failed to read content {digest}: {e}")))? + .into_inner(); let mut data = Vec::new(); while let Some(chunk) = stream.next().await { @@ -288,10 +284,7 @@ impl ContainerdStep { /// /// The spec is serialized as JSON and wrapped in a protobuf Any with /// the containerd OCI spec type URL. - pub(crate) fn build_oci_spec( - &self, - merged_env: &HashMap, - ) -> prost_types::Any { + pub(crate) fn build_oci_spec(&self, merged_env: &HashMap) -> prost_types::Any { // Build the args array for the process. let args: Vec = if let Some(ref run) = self.config.run { vec!["/bin/sh".to_string(), "-c".to_string(), run.clone()] @@ -302,10 +295,7 @@ impl ContainerdStep { }; // Build env in KEY=VALUE form. - let env: Vec = merged_env - .iter() - .map(|(k, v)| format!("{k}={v}")) - .collect(); + let env: Vec = merged_env.iter().map(|(k, v)| format!("{k}={v}")).collect(); // Build mounts. let mut mounts = vec![ @@ -360,10 +350,20 @@ impl ContainerdStep { // capability set so tools like apt-get work. Non-root gets nothing. let caps = if uid == 0 { serde_json::json!([ - "CAP_AUDIT_WRITE", "CAP_CHOWN", "CAP_DAC_OVERRIDE", - "CAP_FOWNER", "CAP_FSETID", "CAP_KILL", "CAP_MKNOD", - "CAP_NET_BIND_SERVICE", "CAP_NET_RAW", "CAP_SETFCAP", - "CAP_SETGID", "CAP_SETPCAP", "CAP_SETUID", "CAP_SYS_CHROOT", + "CAP_AUDIT_WRITE", + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_FOWNER", + "CAP_FSETID", + "CAP_KILL", + "CAP_MKNOD", + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW", + "CAP_SETFCAP", + "CAP_SETGID", + "CAP_SETPCAP", + "CAP_SETUID", + "CAP_SYS_CHROOT", ]) } else { serde_json::json!([]) @@ -405,10 +405,9 @@ impl ContainerdStep { /// Inject a `containerd-namespace` header into a tonic request. pub(crate) fn with_namespace(req: T, namespace: &str) -> tonic::Request { let mut request = tonic::Request::new(req); - request.metadata_mut().insert( - "containerd-namespace", - namespace.parse().unwrap(), - ); + request + .metadata_mut() + .insert("containerd-namespace", namespace.parse().unwrap()); request } @@ -492,8 +491,7 @@ impl ContainerdStep { match snapshots_client.mounts(mounts_req).await { Ok(resp) => resp.into_inner().mounts, Err(_) => { - let parent = - Self::resolve_image_chain_id(&channel, image, namespace).await?; + let parent = Self::resolve_image_chain_id(&channel, image, namespace).await?; let prepare_req = Self::with_namespace( PrepareSnapshotRequest { snapshotter: DEFAULT_SNAPSHOTTER.to_string(), @@ -531,9 +529,10 @@ impl ContainerdStep { }, namespace, ); - tasks_client.create(create_task_req).await.map_err(|e| { - WfeError::StepExecution(format!("failed to create service task: {e}")) - })?; + tasks_client + .create(create_task_req) + .await + .map_err(|e| WfeError::StepExecution(format!("failed to create service task: {e}")))?; let start_req = Self::with_namespace( StartRequest { @@ -542,9 +541,10 @@ impl ContainerdStep { }, namespace, ); - tasks_client.start(start_req).await.map_err(|e| { - WfeError::StepExecution(format!("failed to start service task: {e}")) - })?; + tasks_client + .start(start_req) + .await + .map_err(|e| WfeError::StepExecution(format!("failed to start service task: {e}")))?; tracing::info!(container_id = %container_id, image = %image, "service container started"); Ok(()) @@ -701,9 +701,10 @@ impl StepBody for ContainerdStep { namespace, ); - containers_client.create(create_req).await.map_err(|e| { - WfeError::StepExecution(format!("failed to create container: {e}")) - })?; + containers_client + .create(create_req) + .await + .map_err(|e| WfeError::StepExecution(format!("failed to create container: {e}")))?; // 6. Prepare snapshot with the image's layers as parent. let mut snapshots_client = SnapshotsClient::new(channel.clone()); @@ -723,7 +724,8 @@ impl StepBody for ContainerdStep { Err(_) => { // Resolve the image's chain ID to use as snapshot parent. let parent = if should_check { - Self::resolve_image_chain_id(&channel, &self.config.image, namespace).await? + Self::resolve_image_chain_id(&channel, &self.config.image, namespace) + .await? } else { String::new() }; @@ -741,9 +743,7 @@ impl StepBody for ContainerdStep { .prepare(prepare_req) .await .map_err(|e| { - WfeError::StepExecution(format!( - "failed to prepare snapshot: {e}" - )) + WfeError::StepExecution(format!("failed to prepare snapshot: {e}")) })? .into_inner() .mounts @@ -758,9 +758,8 @@ impl StepBody for ContainerdStep { .map(std::path::PathBuf::from) .unwrap_or_else(|_| std::env::temp_dir()); let tmp_dir = io_base.join(format!("wfe-io-{container_id}")); - std::fs::create_dir_all(&tmp_dir).map_err(|e| { - WfeError::StepExecution(format!("failed to create IO temp dir: {e}")) - })?; + std::fs::create_dir_all(&tmp_dir) + .map_err(|e| WfeError::StepExecution(format!("failed to create IO temp dir: {e}")))?; let stdout_path = tmp_dir.join("stdout"); let stderr_path = tmp_dir.join("stderr"); @@ -802,9 +801,10 @@ impl StepBody for ContainerdStep { namespace, ); - tasks_client.create(create_task_req).await.map_err(|e| { - WfeError::StepExecution(format!("failed to create task: {e}")) - })?; + tasks_client + .create(create_task_req) + .await + .map_err(|e| WfeError::StepExecution(format!("failed to create task: {e}")))?; // Start the task. let start_req = Self::with_namespace( @@ -815,9 +815,10 @@ impl StepBody for ContainerdStep { namespace, ); - tasks_client.start(start_req).await.map_err(|e| { - WfeError::StepExecution(format!("failed to start task: {e}")) - })?; + tasks_client + .start(start_req) + .await + .map_err(|e| WfeError::StepExecution(format!("failed to start task: {e}")))?; tracing::info!(container_id = %container_id, "task started"); @@ -836,12 +837,7 @@ impl StepBody for ContainerdStep { Ok(result) => result, Err(_) => { // Attempt cleanup before returning timeout error. - let _ = Self::cleanup( - &channel, - &container_id, - namespace, - ) - .await; + let _ = Self::cleanup(&channel, &container_id, namespace).await; let _ = std::fs::remove_dir_all(&tmp_dir); return Err(WfeError::StepExecution(format!( "container execution timed out after {timeout_ms}ms" @@ -887,8 +883,13 @@ impl StepBody for ContainerdStep { // 13. Parse outputs and build result. let parsed = Self::parse_outputs(&stdout_content); - let output_data = - Self::build_output_data(step_name, &stdout_content, &stderr_content, exit_code, &parsed); + let output_data = Self::build_output_data( + step_name, + &stdout_content, + &stderr_content, + exit_code, + &parsed, + ); Ok(ExecutionResult { proceed: true, @@ -927,9 +928,7 @@ impl ContainerdStep { containers_client .delete(del_container_req) .await - .map_err(|e| { - WfeError::StepExecution(format!("failed to delete container: {e}")) - })?; + .map_err(|e| WfeError::StepExecution(format!("failed to delete container: {e}")))?; Ok(()) } @@ -1013,10 +1012,7 @@ mod tests { let stdout = "##wfe[output url=https://example.com?a=1&b=2]\n"; let outputs = ContainerdStep::parse_outputs(stdout); assert_eq!(outputs.len(), 1); - assert_eq!( - outputs.get("url").unwrap(), - "https://example.com?a=1&b=2" - ); + assert_eq!(outputs.get("url").unwrap(), "https://example.com?a=1&b=2"); } #[test] @@ -1043,13 +1039,7 @@ mod tests { #[test] fn build_output_data_basic() { let parsed = HashMap::from([("result".to_string(), "success".to_string())]); - let data = ContainerdStep::build_output_data( - "my_step", - "hello world\n", - "", - 0, - &parsed, - ); + let data = ContainerdStep::build_output_data("my_step", "hello world\n", "", 0, &parsed); let obj = data.as_object().unwrap(); assert_eq!(obj.get("result").unwrap(), "success"); @@ -1060,13 +1050,7 @@ mod tests { #[test] fn build_output_data_no_parsed_outputs() { - let data = ContainerdStep::build_output_data( - "step1", - "out", - "err", - 1, - &HashMap::new(), - ); + let data = ContainerdStep::build_output_data("step1", "out", "err", 1, &HashMap::new()); let obj = data.as_object().unwrap(); assert_eq!(obj.len(), 3); // stdout, stderr, exit_code @@ -1150,7 +1134,11 @@ mod tests { fn build_oci_spec_with_command() { let mut config = minimal_config(); config.run = None; - config.command = Some(vec!["echo".to_string(), "hello".to_string(), "world".to_string()]); + config.command = Some(vec![ + "echo".to_string(), + "hello".to_string(), + "world".to_string(), + ]); let step = ContainerdStep::new(config); let spec = step.build_oci_spec(&HashMap::new()); @@ -1227,10 +1215,8 @@ mod tests { // 3 default + 2 user = 5 assert_eq!(mounts.len(), 5); - let bind_mounts: Vec<&serde_json::Value> = mounts - .iter() - .filter(|m| m["type"] == "bind") - .collect(); + let bind_mounts: Vec<&serde_json::Value> = + mounts.iter().filter(|m| m["type"] == "bind").collect(); assert_eq!(bind_mounts.len(), 2); let ro_mount = bind_mounts @@ -1274,10 +1260,9 @@ mod tests { #[tokio::test] async fn connect_to_missing_unix_socket_with_scheme_returns_error() { - let err = - ContainerdStep::connect("unix:///tmp/nonexistent-wfe-containerd-test.sock") - .await - .unwrap_err(); + let err = ContainerdStep::connect("unix:///tmp/nonexistent-wfe-containerd-test.sock") + .await + .unwrap_err(); let msg = format!("{err}"); assert!( msg.contains("socket not found"), @@ -1304,9 +1289,11 @@ mod tests { let config = minimal_config(); let step = ContainerdStep::new(config); assert_eq!(step.config.image, "alpine:3.18"); - assert_eq!(step.config.containerd_addr, "/run/containerd/containerd.sock"); + assert_eq!( + step.config.containerd_addr, + "/run/containerd/containerd.sock" + ); } - } /// Integration tests that require a live containerd daemon. @@ -1323,9 +1310,7 @@ mod e2e_tests { ) }); - let socket_path = addr - .strip_prefix("unix://") - .unwrap_or(addr.as_str()); + let socket_path = addr.strip_prefix("unix://").unwrap_or(addr.as_str()); if Path::new(socket_path).exists() { Some(addr) @@ -1350,6 +1335,9 @@ mod e2e_tests { assert!(!version.version.is_empty(), "version should not be empty"); assert!(!version.revision.is_empty(), "revision should not be empty"); - eprintln!("containerd version={} revision={}", version.version, version.revision); + eprintln!( + "containerd version={} revision={}", + version.version, version.revision + ); } } diff --git a/wfe-containerd/tests/integration.rs b/wfe-containerd/tests/integration.rs index fa91b0f..f7bd062 100644 --- a/wfe-containerd/tests/integration.rs +++ b/wfe-containerd/tests/integration.rs @@ -10,8 +10,8 @@ use std::collections::HashMap; use std::path::Path; -use wfe_containerd::config::{ContainerdConfig, TlsConfig}; use wfe_containerd::ContainerdStep; +use wfe_containerd::config::{ContainerdConfig, TlsConfig}; use wfe_core::models::{ExecutionPointer, WorkflowInstance, WorkflowStep}; use wfe_core::traits::step::{StepBody, StepExecutionContext}; @@ -75,7 +75,7 @@ fn make_context<'a>( workflow, cancellation_token: tokio_util::sync::CancellationToken::new(), host_context: None, - log_sink: None, + log_sink: None, } } @@ -204,8 +204,7 @@ async fn run_container_with_volume_mount() { return; }; - let shared_dir = std::env::var("WFE_IO_DIR") - .unwrap_or_else(|_| "/tmp/wfe-io".to_string()); + let shared_dir = std::env::var("WFE_IO_DIR").unwrap_or_else(|_| "/tmp/wfe-io".to_string()); let vol_dir = format!("{shared_dir}/test-vol"); std::fs::create_dir_all(&vol_dir).unwrap(); @@ -249,8 +248,7 @@ async fn run_debian_with_volume_and_network() { return; }; - let shared_dir = std::env::var("WFE_IO_DIR") - .unwrap_or_else(|_| "/tmp/wfe-io".to_string()); + let shared_dir = std::env::var("WFE_IO_DIR").unwrap_or_else(|_| "/tmp/wfe-io".to_string()); let cargo_dir = format!("{shared_dir}/test-cargo"); let rustup_dir = format!("{shared_dir}/test-rustup"); std::fs::create_dir_all(&cargo_dir).unwrap(); @@ -263,8 +261,12 @@ async fn run_debian_with_volume_and_network() { config.user = "0:0".to_string(); config.network = "host".to_string(); config.timeout_ms = Some(30_000); - config.env.insert("CARGO_HOME".to_string(), "/cargo".to_string()); - config.env.insert("RUSTUP_HOME".to_string(), "/rustup".to_string()); + config + .env + .insert("CARGO_HOME".to_string(), "/cargo".to_string()); + config + .env + .insert("RUSTUP_HOME".to_string(), "/rustup".to_string()); config.volumes = vec![ wfe_containerd::VolumeMountConfig { source: cargo_dir.clone(), diff --git a/wfe-core/src/builder/step_builder.rs b/wfe-core/src/builder/step_builder.rs index 01fc695..f494955 100644 --- a/wfe-core/src/builder/step_builder.rs +++ b/wfe-core/src/builder/step_builder.rs @@ -67,7 +67,10 @@ impl StepBuilder { } /// Chain an inline function step. - pub fn then_fn(mut self, f: impl Fn() -> ExecutionResult + Send + Sync + 'static) -> StepBuilder { + pub fn then_fn( + mut self, + f: impl Fn() -> ExecutionResult + Send + Sync + 'static, + ) -> StepBuilder { let next_id = self.builder.add_step(std::any::type_name::()); self.builder.wire_outcome(self.step_id, next_id, None); self.builder.last_step = Some(next_id); @@ -77,7 +80,9 @@ impl StepBuilder { /// Insert a WaitFor step. pub fn wait_for(mut self, event_name: &str, event_key: &str) -> StepBuilder { - let next_id = self.builder.add_step(std::any::type_name::()); + let next_id = self + .builder + .add_step(std::any::type_name::()); self.builder.wire_outcome(self.step_id, next_id, None); self.builder.last_step = Some(next_id); self.builder.steps[next_id].step_config = Some(serde_json::json!({ @@ -89,7 +94,9 @@ impl StepBuilder { /// Insert a Delay step. pub fn delay(mut self, duration: std::time::Duration) -> StepBuilder { - let next_id = self.builder.add_step(std::any::type_name::()); + let next_id = self + .builder + .add_step(std::any::type_name::()); self.builder.wire_outcome(self.step_id, next_id, None); self.builder.last_step = Some(next_id); self.builder.steps[next_id].step_config = Some(serde_json::json!({ @@ -104,7 +111,9 @@ impl StepBuilder { mut self, build_children: impl FnOnce(&mut WorkflowBuilder), ) -> StepBuilder { - let if_id = self.builder.add_step(std::any::type_name::()); + let if_id = self + .builder + .add_step(std::any::type_name::()); self.builder.wire_outcome(self.step_id, if_id, None); // Build children @@ -126,7 +135,9 @@ impl StepBuilder { mut self, build_children: impl FnOnce(&mut WorkflowBuilder), ) -> StepBuilder { - let while_id = self.builder.add_step(std::any::type_name::()); + let while_id = self + .builder + .add_step(std::any::type_name::()); self.builder.wire_outcome(self.step_id, while_id, None); let before_count = self.builder.steps.len(); @@ -146,7 +157,9 @@ impl StepBuilder { mut self, build_children: impl FnOnce(&mut WorkflowBuilder), ) -> StepBuilder { - let fe_id = self.builder.add_step(std::any::type_name::()); + let fe_id = self + .builder + .add_step(std::any::type_name::()); self.builder.wire_outcome(self.step_id, fe_id, None); let before_count = self.builder.steps.len(); @@ -162,11 +175,10 @@ impl StepBuilder { } /// Insert a Saga container step with child steps. - pub fn saga( - mut self, - build_children: impl FnOnce(&mut WorkflowBuilder), - ) -> StepBuilder { - let saga_id = self.builder.add_step(std::any::type_name::()); + pub fn saga(mut self, build_children: impl FnOnce(&mut WorkflowBuilder)) -> StepBuilder { + let saga_id = self.builder.add_step(std::any::type_name::< + primitives::saga_container::SagaContainerStep, + >()); self.builder.steps[saga_id].saga = true; self.builder.wire_outcome(self.step_id, saga_id, None); @@ -187,7 +199,9 @@ impl StepBuilder { mut self, build_branches: impl FnOnce(ParallelBuilder) -> ParallelBuilder, ) -> StepBuilder { - let seq_id = self.builder.add_step(std::any::type_name::()); + let seq_id = self + .builder + .add_step(std::any::type_name::()); self.builder.wire_outcome(self.step_id, seq_id, None); let pb = ParallelBuilder { @@ -213,10 +227,7 @@ impl StepBuilder { impl ParallelBuilder { /// Add a parallel branch. - pub fn branch( - mut self, - build_branch: impl FnOnce(&mut WorkflowBuilder), - ) -> Self { + pub fn branch(mut self, build_branch: impl FnOnce(&mut WorkflowBuilder)) -> Self { let before_count = self.builder.steps.len(); build_branch(&mut self.builder); let after_count = self.builder.steps.len(); diff --git a/wfe-core/src/builder/workflow_builder.rs b/wfe-core/src/builder/workflow_builder.rs index 5ad0628..5a2a96b 100644 --- a/wfe-core/src/builder/workflow_builder.rs +++ b/wfe-core/src/builder/workflow_builder.rs @@ -1,9 +1,7 @@ use std::collections::HashMap; use std::marker::PhantomData; -use crate::models::{ - ExecutionResult, StepOutcome, WorkflowDefinition, WorkflowStep, -}; +use crate::models::{ExecutionResult, StepOutcome, WorkflowDefinition, WorkflowStep}; use crate::traits::step::{StepBody, WorkflowData}; use super::inline_step::InlineStep; @@ -77,7 +75,12 @@ impl WorkflowBuilder { } /// Wire an outcome from `from_step` to `to_step`. - pub fn wire_outcome(&mut self, from_step: usize, to_step: usize, value: Option) { + pub fn wire_outcome( + &mut self, + from_step: usize, + to_step: usize, + value: Option, + ) { if let Some(step) = self.steps.get_mut(from_step) { step.outcomes.push(StepOutcome { next_step: to_step, diff --git a/wfe-core/src/executor/condition.rs b/wfe-core/src/executor/condition.rs index 449ce9f..898544e 100644 --- a/wfe-core/src/executor/condition.rs +++ b/wfe-core/src/executor/condition.rs @@ -1,5 +1,5 @@ -use crate::models::condition::{ComparisonOp, FieldComparison, StepCondition}; use crate::WfeError; +use crate::models::condition::{ComparisonOp, FieldComparison, StepCondition}; /// Evaluate a step condition against workflow data. /// @@ -29,10 +29,7 @@ impl From for EvalError { } } -fn evaluate_inner( - condition: &StepCondition, - data: &serde_json::Value, -) -> Result { +fn evaluate_inner(condition: &StepCondition, data: &serde_json::Value) -> Result { match condition { StepCondition::All(conditions) => { for c in conditions { @@ -582,22 +579,14 @@ mod tests { #[test] fn not_true_becomes_false() { let data = json!({"a": 1}); - let cond = StepCondition::Not(Box::new(comp( - ".a", - ComparisonOp::Equals, - Some(json!(1)), - ))); + let cond = StepCondition::Not(Box::new(comp(".a", ComparisonOp::Equals, Some(json!(1))))); assert!(!evaluate(&cond, &data).unwrap()); } #[test] fn not_false_becomes_true() { let data = json!({"a": 99}); - let cond = StepCondition::Not(Box::new(comp( - ".a", - ComparisonOp::Equals, - Some(json!(1)), - ))); + let cond = StepCondition::Not(Box::new(comp(".a", ComparisonOp::Equals, Some(json!(1))))); assert!(evaluate(&cond, &data).unwrap()); } @@ -639,11 +628,7 @@ mod tests { comp(".a", ComparisonOp::Equals, Some(json!(1))), comp(".a", ComparisonOp::Equals, Some(json!(99))), ]), - StepCondition::Not(Box::new(comp( - ".c", - ComparisonOp::Equals, - Some(json!(99)), - ))), + StepCondition::Not(Box::new(comp(".c", ComparisonOp::Equals, Some(json!(99))))), ]); assert!(evaluate(&cond, &data).unwrap()); } @@ -742,7 +727,13 @@ mod tests { let data = json!({"score": 3.14}); assert!(evaluate(&comp(".score", ComparisonOp::Gt, Some(json!(3.0))), &data).unwrap()); assert!(evaluate(&comp(".score", ComparisonOp::Lt, Some(json!(4.0))), &data).unwrap()); - assert!(!evaluate(&comp(".score", ComparisonOp::Equals, Some(json!(3.0))), &data).unwrap()); + assert!( + !evaluate( + &comp(".score", ComparisonOp::Equals, Some(json!(3.0))), + &data + ) + .unwrap() + ); } #[test] diff --git a/wfe-core/src/executor/error_handler.rs b/wfe-core/src/executor/error_handler.rs index 82827f4..d05e66e 100644 --- a/wfe-core/src/executor/error_handler.rs +++ b/wfe-core/src/executor/error_handler.rs @@ -29,7 +29,10 @@ pub fn handle_error( .unwrap_or_else(|| definition.default_error_behavior.clone()); match behavior { - ErrorBehavior::Retry { interval, max_retries } => { + ErrorBehavior::Retry { + interval, + max_retries, + } => { if max_retries > 0 && pointer.retry_count >= max_retries { // Exceeded max retries, suspend the workflow pointer.status = PointerStatus::Failed; @@ -44,9 +47,8 @@ pub fn handle_error( pointer.retry_count += 1; pointer.status = PointerStatus::Sleeping; pointer.active = true; - pointer.sleep_until = Some( - Utc::now() + chrono::Duration::milliseconds(interval.as_millis() as i64), - ); + pointer.sleep_until = + Some(Utc::now() + chrono::Duration::milliseconds(interval.as_millis() as i64)); } } ErrorBehavior::Suspend => { @@ -67,7 +69,9 @@ pub fn handle_error( && let Some(comp_step_id) = step.compensation_step_id { let mut comp_pointer = ExecutionPointer::new(comp_step_id); - comp_pointer.step_name = definition.steps.iter() + comp_pointer.step_name = definition + .steps + .iter() .find(|s| s.id == comp_step_id) .and_then(|s| s.name.clone()); comp_pointer.predecessor_id = Some(pointer.id.clone()); diff --git a/wfe-core/src/executor/result_processor.rs b/wfe-core/src/executor/result_processor.rs index 0d508ad..1d0383a 100644 --- a/wfe-core/src/executor/result_processor.rs +++ b/wfe-core/src/executor/result_processor.rs @@ -36,7 +36,9 @@ pub fn process_result( let next_step_id = find_next_step(step, &result.outcome_value); if let Some(next_id) = next_step_id { let mut next_pointer = ExecutionPointer::new(next_id); - next_pointer.step_name = definition.steps.iter() + next_pointer.step_name = definition + .steps + .iter() .find(|s| s.id == next_id) .and_then(|s| s.name.clone()); next_pointer.predecessor_id = Some(pointer.id.clone()); @@ -62,7 +64,9 @@ pub fn process_result( for value in branch_values { for &child_step_id in &child_step_ids { let mut child_pointer = ExecutionPointer::new(child_step_id); - child_pointer.step_name = definition.steps.iter() + child_pointer.step_name = definition + .steps + .iter() .find(|s| s.id == child_step_id) .and_then(|s| s.name.clone()); child_pointer.context_item = Some(value.clone()); @@ -79,9 +83,7 @@ pub fn process_result( pointer.event_name = result.event_name.clone(); pointer.event_key = result.event_key.clone(); - if let (Some(event_name), Some(event_key)) = - (&result.event_name, &result.event_key) - { + if let (Some(event_name), Some(event_key)) = (&result.event_name, &result.event_key) { let as_of = result.event_as_of.unwrap_or_else(Utc::now); let sub = EventSubscription::new( workflow_id, @@ -107,8 +109,7 @@ pub fn process_result( pointer.status = PointerStatus::Sleeping; pointer.active = true; pointer.sleep_until = Some( - Utc::now() - + chrono::Duration::milliseconds(poll_config.interval.as_millis() as i64), + Utc::now() + chrono::Duration::milliseconds(poll_config.interval.as_millis() as i64), ); pointer.persistence_data = result.persistence_data.clone(); } else if result.persistence_data.is_some() { diff --git a/wfe-core/src/executor/step_registry.rs b/wfe-core/src/executor/step_registry.rs index b6a8b76..ad5f5f9 100644 --- a/wfe-core/src/executor/step_registry.rs +++ b/wfe-core/src/executor/step_registry.rs @@ -17,7 +17,8 @@ impl StepRegistry { /// Register a step type using its full type name as the key. pub fn register(&mut self) { let key = std::any::type_name::().to_string(); - self.factories.insert(key, Box::new(|| Box::new(S::default()))); + self.factories + .insert(key, Box::new(|| Box::new(S::default()))); } /// Register a step factory with an explicit key and factory function. diff --git a/wfe-core/src/executor/workflow_executor.rs b/wfe-core/src/executor/workflow_executor.rs index b441267..f976187 100644 --- a/wfe-core/src/executor/workflow_executor.rs +++ b/wfe-core/src/executor/workflow_executor.rs @@ -119,12 +119,12 @@ impl WorkflowExecutor { host_context: Option<&dyn crate::traits::HostContext>, ) -> Result<()> { // 2. Load workflow instance. - let mut workflow = self - .persistence - .get_workflow_instance(workflow_id) - .await?; + let mut workflow = self.persistence.get_workflow_instance(workflow_id).await?; - tracing::Span::current().record("workflow.definition_id", workflow.workflow_definition_id.as_str()); + tracing::Span::current().record( + "workflow.definition_id", + workflow.workflow_definition_id.as_str(), + ); if workflow.status != WorkflowStatus::Runnable { debug!(workflow_id, status = ?workflow.status, "Workflow not runnable, skipping"); @@ -179,15 +179,15 @@ impl WorkflowExecutor { // Activate next step via outcomes (same as Complete). let next_step_id = step.outcomes.first().map(|o| o.next_step); if let Some(next_id) = next_step_id { - let mut next_pointer = - crate::models::ExecutionPointer::new(next_id); - next_pointer.step_name = definition.steps.iter() + let mut next_pointer = crate::models::ExecutionPointer::new(next_id); + next_pointer.step_name = definition + .steps + .iter() .find(|s| s.id == next_id) .and_then(|s| s.name.clone()); next_pointer.predecessor_id = Some(workflow.execution_pointers[idx].id.clone()); - next_pointer.scope = - workflow.execution_pointers[idx].scope.clone(); + next_pointer.scope = workflow.execution_pointers[idx].scope.clone(); workflow.execution_pointers.push(next_pointer); } @@ -208,12 +208,12 @@ impl WorkflowExecutor { ); // b. Resolve the step body. - let mut step_body = step_registry - .resolve(&step.step_type) - .ok_or_else(|| WfeError::StepExecution(format!( + let mut step_body = step_registry.resolve(&step.step_type).ok_or_else(|| { + WfeError::StepExecution(format!( "Step type not found in registry: {}", step.step_type - )))?; + )) + })?; // Mark pointer as running before building context. if workflow.execution_pointers[idx].start_time.is_none() { @@ -229,7 +229,8 @@ impl WorkflowExecutor { step_id, step_name: step.name.clone(), }, - )).await; + )) + .await; // c. Build StepExecutionContext (borrows workflow immutably). let cancellation_token = tokio_util::sync::CancellationToken::new(); @@ -277,19 +278,15 @@ impl WorkflowExecutor { step_id, step_name: step.name.clone(), }, - )).await; + )) + .await; // e. Process the ExecutionResult. // Extract workflow_id before mutable borrow. let wf_id = workflow.id.clone(); let process_result = { let pointer = &mut workflow.execution_pointers[idx]; - result_processor::process_result( - &result, - pointer, - definition, - &wf_id, - ) + result_processor::process_result(&result, pointer, definition, &wf_id) }; all_subscriptions.extend(process_result.subscriptions); @@ -320,7 +317,8 @@ impl WorkflowExecutor { crate::models::LifecycleEventType::Error { message: error_msg.clone(), }, - )).await; + )) + .await; let pointer_id = workflow.execution_pointers[idx].id.clone(); execution_errors.push(ExecutionError::new( @@ -331,11 +329,7 @@ impl WorkflowExecutor { let handler_result = { let pointer = &mut workflow.execution_pointers[idx]; - error_handler::handle_error( - &error_msg, - pointer, - definition, - ) + error_handler::handle_error(&error_msg, pointer, definition) }; // Apply workflow-level status changes from error handler. @@ -348,7 +342,8 @@ impl WorkflowExecutor { &workflow.workflow_definition_id, workflow.version, crate::models::LifecycleEventType::Terminated, - )).await; + )) + .await; } } @@ -382,7 +377,8 @@ impl WorkflowExecutor { &workflow.workflow_definition_id, workflow.version, crate::models::LifecycleEventType::Completed, - )).await; + )) + .await; // Publish completion event for SubWorkflow parents. let completion_event = Event::new( @@ -427,9 +423,7 @@ impl WorkflowExecutor { // Persist errors. if !execution_errors.is_empty() { - self.persistence - .persist_errors(&execution_errors) - .await?; + self.persistence.persist_errors(&execution_errors).await?; } // 8. Queue any follow-up work. @@ -512,10 +506,7 @@ mod tests { #[async_trait] impl StepBody for PassStep { - async fn run( - &mut self, - _ctx: &StepExecutionContext<'_>, - ) -> crate::Result { + async fn run(&mut self, _ctx: &StepExecutionContext<'_>) -> crate::Result { Ok(ExecutionResult::next()) } } @@ -525,10 +516,7 @@ mod tests { #[async_trait] impl StepBody for OutcomeStep { - async fn run( - &mut self, - _ctx: &StepExecutionContext<'_>, - ) -> crate::Result { + async fn run(&mut self, _ctx: &StepExecutionContext<'_>) -> crate::Result { Ok(ExecutionResult::outcome(serde_json::json!("yes"))) } } @@ -538,10 +526,7 @@ mod tests { #[async_trait] impl StepBody for PersistStep { - async fn run( - &mut self, - _ctx: &StepExecutionContext<'_>, - ) -> crate::Result { + async fn run(&mut self, _ctx: &StepExecutionContext<'_>) -> crate::Result { Ok(ExecutionResult::persist(serde_json::json!({"count": 1}))) } } @@ -551,10 +536,7 @@ mod tests { #[async_trait] impl StepBody for SleepStep { - async fn run( - &mut self, - _ctx: &StepExecutionContext<'_>, - ) -> crate::Result { + async fn run(&mut self, _ctx: &StepExecutionContext<'_>) -> crate::Result { Ok(ExecutionResult::sleep(Duration::from_secs(30), None)) } } @@ -564,10 +546,7 @@ mod tests { #[async_trait] impl StepBody for WaitEventStep { - async fn run( - &mut self, - _ctx: &StepExecutionContext<'_>, - ) -> crate::Result { + async fn run(&mut self, _ctx: &StepExecutionContext<'_>) -> crate::Result { Ok(ExecutionResult::wait_for_event( "order.completed", "order-123", @@ -581,10 +560,7 @@ mod tests { #[async_trait] impl StepBody for EventResumeStep { - async fn run( - &mut self, - ctx: &StepExecutionContext<'_>, - ) -> crate::Result { + async fn run(&mut self, ctx: &StepExecutionContext<'_>) -> crate::Result { if ctx.execution_pointer.event_published { Ok(ExecutionResult::next()) } else { @@ -602,10 +578,7 @@ mod tests { #[async_trait] impl StepBody for BranchStep { - async fn run( - &mut self, - _ctx: &StepExecutionContext<'_>, - ) -> crate::Result { + async fn run(&mut self, _ctx: &StepExecutionContext<'_>) -> crate::Result { Ok(ExecutionResult::branch( vec![ serde_json::json!(1), @@ -622,10 +595,7 @@ mod tests { #[async_trait] impl StepBody for FailStep { - async fn run( - &mut self, - _ctx: &StepExecutionContext<'_>, - ) -> crate::Result { + async fn run(&mut self, _ctx: &StepExecutionContext<'_>) -> crate::Result { Err(WfeError::StepExecution("step failed".into())) } } @@ -635,10 +605,7 @@ mod tests { #[async_trait] impl StepBody for CompensateStep { - async fn run( - &mut self, - _ctx: &StepExecutionContext<'_>, - ) -> crate::Result { + async fn run(&mut self, _ctx: &StepExecutionContext<'_>) -> crate::Result { Ok(ExecutionResult::next()) } } @@ -680,7 +647,8 @@ mod tests { registry.register::(); let mut def = WorkflowDefinition::new("test", 1); - def.steps.push(WorkflowStep::new(0, step_type::())); + def.steps + .push(WorkflowStep::new(0, step_type::())); let mut instance = WorkflowInstance::new("test", 1, serde_json::json!({})); let pointer = ExecutionPointer::new(0); @@ -688,11 +656,20 @@ mod tests { persistence.create_new_workflow(&instance).await.unwrap(); - executor.execute(&instance.id, &def, ®istry, None).await.unwrap(); + executor + .execute(&instance.id, &def, ®istry, None) + .await + .unwrap(); - let updated = persistence.get_workflow_instance(&instance.id).await.unwrap(); + let updated = persistence + .get_workflow_instance(&instance.id) + .await + .unwrap(); assert_eq!(updated.status, WorkflowStatus::Complete); - assert_eq!(updated.execution_pointers[0].status, PointerStatus::Complete); + assert_eq!( + updated.execution_pointers[0].status, + PointerStatus::Complete + ); assert!(updated.complete_time.is_some()); } @@ -712,27 +689,46 @@ mod tests { value: None, }); def.steps.push(step0); - def.steps.push(WorkflowStep::new(1, step_type::())); + def.steps + .push(WorkflowStep::new(1, step_type::())); let mut instance = WorkflowInstance::new("test", 1, serde_json::json!({})); instance.execution_pointers.push(ExecutionPointer::new(0)); persistence.create_new_workflow(&instance).await.unwrap(); // First execution: step 0 completes, step 1 pointer created. - executor.execute(&instance.id, &def, ®istry, None).await.unwrap(); + executor + .execute(&instance.id, &def, ®istry, None) + .await + .unwrap(); - let updated = persistence.get_workflow_instance(&instance.id).await.unwrap(); + let updated = persistence + .get_workflow_instance(&instance.id) + .await + .unwrap(); assert_eq!(updated.execution_pointers.len(), 2); - assert_eq!(updated.execution_pointers[0].status, PointerStatus::Complete); + assert_eq!( + updated.execution_pointers[0].status, + PointerStatus::Complete + ); // Step 1 pointer should be active and pending. assert_eq!(updated.execution_pointers[1].step_id, 1); // Second execution: step 1 completes. - executor.execute(&instance.id, &def, ®istry, None).await.unwrap(); + executor + .execute(&instance.id, &def, ®istry, None) + .await + .unwrap(); - let updated = persistence.get_workflow_instance(&instance.id).await.unwrap(); + let updated = persistence + .get_workflow_instance(&instance.id) + .await + .unwrap(); assert_eq!(updated.status, WorkflowStatus::Complete); - assert_eq!(updated.execution_pointers[1].status, PointerStatus::Complete); + assert_eq!( + updated.execution_pointers[1].status, + PointerStatus::Complete + ); } #[tokio::test] @@ -745,9 +741,17 @@ mod tests { let mut def = WorkflowDefinition::new("test", 1); let mut s0 = WorkflowStep::new(0, step_type::()); - s0.outcomes.push(StepOutcome { next_step: 1, label: None, value: None }); + s0.outcomes.push(StepOutcome { + next_step: 1, + label: None, + value: None, + }); let mut s1 = WorkflowStep::new(1, step_type::()); - s1.outcomes.push(StepOutcome { next_step: 2, label: None, value: None }); + s1.outcomes.push(StepOutcome { + next_step: 2, + label: None, + value: None, + }); let s2 = WorkflowStep::new(2, step_type::()); def.steps.push(s0); def.steps.push(s1); @@ -759,10 +763,16 @@ mod tests { // Execute three times for three steps. for _ in 0..3 { - executor.execute(&instance.id, &def, ®istry, None).await.unwrap(); + executor + .execute(&instance.id, &def, ®istry, None) + .await + .unwrap(); } - let updated = persistence.get_workflow_instance(&instance.id).await.unwrap(); + let updated = persistence + .get_workflow_instance(&instance.id) + .await + .unwrap(); assert_eq!(updated.status, WorkflowStatus::Complete); assert_eq!(updated.execution_pointers.len(), 3); for p in &updated.execution_pointers { @@ -792,16 +802,24 @@ mod tests { value: Some(serde_json::json!("yes")), }); def.steps.push(s0); - def.steps.push(WorkflowStep::new(1, step_type::())); - def.steps.push(WorkflowStep::new(2, step_type::())); + def.steps + .push(WorkflowStep::new(1, step_type::())); + def.steps + .push(WorkflowStep::new(2, step_type::())); let mut instance = WorkflowInstance::new("test", 1, serde_json::json!({})); instance.execution_pointers.push(ExecutionPointer::new(0)); persistence.create_new_workflow(&instance).await.unwrap(); - executor.execute(&instance.id, &def, ®istry, None).await.unwrap(); + executor + .execute(&instance.id, &def, ®istry, None) + .await + .unwrap(); - let updated = persistence.get_workflow_instance(&instance.id).await.unwrap(); + let updated = persistence + .get_workflow_instance(&instance.id) + .await + .unwrap(); assert_eq!(updated.execution_pointers.len(), 2); // Should route to step 2 (the "yes" branch). assert_eq!(updated.execution_pointers[1].step_id, 2); @@ -816,15 +834,22 @@ mod tests { registry.register::(); let mut def = WorkflowDefinition::new("test", 1); - def.steps.push(WorkflowStep::new(0, step_type::())); + def.steps + .push(WorkflowStep::new(0, step_type::())); let mut instance = WorkflowInstance::new("test", 1, serde_json::json!({})); instance.execution_pointers.push(ExecutionPointer::new(0)); persistence.create_new_workflow(&instance).await.unwrap(); - executor.execute(&instance.id, &def, ®istry, None).await.unwrap(); + executor + .execute(&instance.id, &def, ®istry, None) + .await + .unwrap(); - let updated = persistence.get_workflow_instance(&instance.id).await.unwrap(); + let updated = persistence + .get_workflow_instance(&instance.id) + .await + .unwrap(); assert_eq!(updated.status, WorkflowStatus::Runnable); assert!(updated.execution_pointers[0].active); assert_eq!( @@ -842,16 +867,26 @@ mod tests { registry.register::(); let mut def = WorkflowDefinition::new("test", 1); - def.steps.push(WorkflowStep::new(0, step_type::())); + def.steps + .push(WorkflowStep::new(0, step_type::())); let mut instance = WorkflowInstance::new("test", 1, serde_json::json!({})); instance.execution_pointers.push(ExecutionPointer::new(0)); persistence.create_new_workflow(&instance).await.unwrap(); - executor.execute(&instance.id, &def, ®istry, None).await.unwrap(); + executor + .execute(&instance.id, &def, ®istry, None) + .await + .unwrap(); - let updated = persistence.get_workflow_instance(&instance.id).await.unwrap(); - assert_eq!(updated.execution_pointers[0].status, PointerStatus::Sleeping); + let updated = persistence + .get_workflow_instance(&instance.id) + .await + .unwrap(); + assert_eq!( + updated.execution_pointers[0].status, + PointerStatus::Sleeping + ); assert!(updated.execution_pointers[0].sleep_until.is_some()); assert!(updated.execution_pointers[0].active); } @@ -865,15 +900,22 @@ mod tests { registry.register::(); let mut def = WorkflowDefinition::new("test", 1); - def.steps.push(WorkflowStep::new(0, step_type::())); + def.steps + .push(WorkflowStep::new(0, step_type::())); let mut instance = WorkflowInstance::new("test", 1, serde_json::json!({})); instance.execution_pointers.push(ExecutionPointer::new(0)); persistence.create_new_workflow(&instance).await.unwrap(); - executor.execute(&instance.id, &def, ®istry, None).await.unwrap(); + executor + .execute(&instance.id, &def, ®istry, None) + .await + .unwrap(); - let updated = persistence.get_workflow_instance(&instance.id).await.unwrap(); + let updated = persistence + .get_workflow_instance(&instance.id) + .await + .unwrap(); assert_eq!( updated.execution_pointers[0].status, PointerStatus::WaitingForEvent @@ -899,7 +941,8 @@ mod tests { registry.register::(); let mut def = WorkflowDefinition::new("test", 1); - def.steps.push(WorkflowStep::new(0, step_type::())); + def.steps + .push(WorkflowStep::new(0, step_type::())); let mut instance = WorkflowInstance::new("test", 1, serde_json::json!({})); let mut pointer = ExecutionPointer::new(0); @@ -911,10 +954,19 @@ mod tests { instance.execution_pointers.push(pointer); persistence.create_new_workflow(&instance).await.unwrap(); - executor.execute(&instance.id, &def, ®istry, None).await.unwrap(); + executor + .execute(&instance.id, &def, ®istry, None) + .await + .unwrap(); - let updated = persistence.get_workflow_instance(&instance.id).await.unwrap(); - assert_eq!(updated.execution_pointers[0].status, PointerStatus::Complete); + let updated = persistence + .get_workflow_instance(&instance.id) + .await + .unwrap(); + assert_eq!( + updated.execution_pointers[0].status, + PointerStatus::Complete + ); assert_eq!(updated.status, WorkflowStatus::Complete); } @@ -931,15 +983,22 @@ mod tests { let mut s0 = WorkflowStep::new(0, step_type::()); s0.children.push(1); def.steps.push(s0); - def.steps.push(WorkflowStep::new(1, step_type::())); + def.steps + .push(WorkflowStep::new(1, step_type::())); let mut instance = WorkflowInstance::new("test", 1, serde_json::json!({})); instance.execution_pointers.push(ExecutionPointer::new(0)); persistence.create_new_workflow(&instance).await.unwrap(); - executor.execute(&instance.id, &def, ®istry, None).await.unwrap(); + executor + .execute(&instance.id, &def, ®istry, None) + .await + .unwrap(); - let updated = persistence.get_workflow_instance(&instance.id).await.unwrap(); + let updated = persistence + .get_workflow_instance(&instance.id) + .await + .unwrap(); // 1 original + 3 children. assert_eq!(updated.execution_pointers.len(), 4); // Children should have scope containing the parent pointer id. @@ -973,11 +1032,20 @@ mod tests { instance.execution_pointers.push(ExecutionPointer::new(0)); persistence.create_new_workflow(&instance).await.unwrap(); - executor.execute(&instance.id, &def, ®istry, None).await.unwrap(); + executor + .execute(&instance.id, &def, ®istry, None) + .await + .unwrap(); - let updated = persistence.get_workflow_instance(&instance.id).await.unwrap(); + let updated = persistence + .get_workflow_instance(&instance.id) + .await + .unwrap(); assert_eq!(updated.execution_pointers[0].retry_count, 1); - assert_eq!(updated.execution_pointers[0].status, PointerStatus::Sleeping); + assert_eq!( + updated.execution_pointers[0].status, + PointerStatus::Sleeping + ); assert!(updated.execution_pointers[0].sleep_until.is_some()); assert_eq!(updated.status, WorkflowStatus::Runnable); } @@ -999,9 +1067,15 @@ mod tests { instance.execution_pointers.push(ExecutionPointer::new(0)); persistence.create_new_workflow(&instance).await.unwrap(); - executor.execute(&instance.id, &def, ®istry, None).await.unwrap(); + executor + .execute(&instance.id, &def, ®istry, None) + .await + .unwrap(); - let updated = persistence.get_workflow_instance(&instance.id).await.unwrap(); + let updated = persistence + .get_workflow_instance(&instance.id) + .await + .unwrap(); assert_eq!(updated.status, WorkflowStatus::Suspended); assert_eq!(updated.execution_pointers[0].status, PointerStatus::Failed); } @@ -1023,9 +1097,15 @@ mod tests { instance.execution_pointers.push(ExecutionPointer::new(0)); persistence.create_new_workflow(&instance).await.unwrap(); - executor.execute(&instance.id, &def, ®istry, None).await.unwrap(); + executor + .execute(&instance.id, &def, ®istry, None) + .await + .unwrap(); - let updated = persistence.get_workflow_instance(&instance.id).await.unwrap(); + let updated = persistence + .get_workflow_instance(&instance.id) + .await + .unwrap(); assert_eq!(updated.status, WorkflowStatus::Terminated); assert_eq!(updated.execution_pointers[0].status, PointerStatus::Failed); assert!(updated.complete_time.is_some()); @@ -1045,15 +1125,22 @@ mod tests { s0.error_behavior = Some(ErrorBehavior::Compensate); s0.compensation_step_id = Some(1); def.steps.push(s0); - def.steps.push(WorkflowStep::new(1, step_type::())); + def.steps + .push(WorkflowStep::new(1, step_type::())); let mut instance = WorkflowInstance::new("test", 1, serde_json::json!({})); instance.execution_pointers.push(ExecutionPointer::new(0)); persistence.create_new_workflow(&instance).await.unwrap(); - executor.execute(&instance.id, &def, ®istry, None).await.unwrap(); + executor + .execute(&instance.id, &def, ®istry, None) + .await + .unwrap(); - let updated = persistence.get_workflow_instance(&instance.id).await.unwrap(); + let updated = persistence + .get_workflow_instance(&instance.id) + .await + .unwrap(); assert_eq!(updated.execution_pointers[0].status, PointerStatus::Failed); // Compensation pointer should be created. assert_eq!(updated.execution_pointers.len(), 2); @@ -1070,8 +1157,10 @@ mod tests { registry.register::(); let mut def = WorkflowDefinition::new("test", 1); - def.steps.push(WorkflowStep::new(0, step_type::())); - def.steps.push(WorkflowStep::new(1, step_type::())); + def.steps + .push(WorkflowStep::new(0, step_type::())); + def.steps + .push(WorkflowStep::new(1, step_type::())); let mut instance = WorkflowInstance::new("test", 1, serde_json::json!({})); // Two independent active pointers. @@ -1079,14 +1168,22 @@ mod tests { instance.execution_pointers.push(ExecutionPointer::new(1)); persistence.create_new_workflow(&instance).await.unwrap(); - executor.execute(&instance.id, &def, ®istry, None).await.unwrap(); + executor + .execute(&instance.id, &def, ®istry, None) + .await + .unwrap(); - let updated = persistence.get_workflow_instance(&instance.id).await.unwrap(); + let updated = persistence + .get_workflow_instance(&instance.id) + .await + .unwrap(); assert_eq!(updated.status, WorkflowStatus::Complete); - assert!(updated - .execution_pointers - .iter() - .all(|p| p.status == PointerStatus::Complete)); + assert!( + updated + .execution_pointers + .iter() + .all(|p| p.status == PointerStatus::Complete) + ); } #[tokio::test] @@ -1114,9 +1211,15 @@ mod tests { persistence.create_new_workflow(&instance).await.unwrap(); // Should not error on a completed workflow. - executor.execute(&instance.id, &def, ®istry, None).await.unwrap(); + executor + .execute(&instance.id, &def, ®istry, None) + .await + .unwrap(); - let updated = persistence.get_workflow_instance(&instance.id).await.unwrap(); + let updated = persistence + .get_workflow_instance(&instance.id) + .await + .unwrap(); assert_eq!(updated.status, WorkflowStatus::Complete); } @@ -1129,7 +1232,8 @@ mod tests { registry.register::(); let mut def = WorkflowDefinition::new("test", 1); - def.steps.push(WorkflowStep::new(0, step_type::())); + def.steps + .push(WorkflowStep::new(0, step_type::())); let mut instance = WorkflowInstance::new("test", 1, serde_json::json!({})); let mut pointer = ExecutionPointer::new(0); @@ -1139,11 +1243,20 @@ mod tests { instance.execution_pointers.push(pointer); persistence.create_new_workflow(&instance).await.unwrap(); - executor.execute(&instance.id, &def, ®istry, None).await.unwrap(); + executor + .execute(&instance.id, &def, ®istry, None) + .await + .unwrap(); - let updated = persistence.get_workflow_instance(&instance.id).await.unwrap(); + let updated = persistence + .get_workflow_instance(&instance.id) + .await + .unwrap(); // Should still be sleeping since sleep_until is in the future. - assert_eq!(updated.execution_pointers[0].status, PointerStatus::Sleeping); + assert_eq!( + updated.execution_pointers[0].status, + PointerStatus::Sleeping + ); } #[tokio::test] @@ -1163,7 +1276,10 @@ mod tests { instance.execution_pointers.push(ExecutionPointer::new(0)); persistence.create_new_workflow(&instance).await.unwrap(); - executor.execute(&instance.id, &def, ®istry, None).await.unwrap(); + executor + .execute(&instance.id, &def, ®istry, None) + .await + .unwrap(); let errors = persistence.get_errors().await; assert_eq!(errors.len(), 1); @@ -1174,24 +1290,31 @@ mod tests { async fn lifecycle_events_published() { let (persistence, lock, queue) = create_providers(); let lifecycle = Arc::new(InMemoryLifecyclePublisher::new()); - let executor = create_executor(persistence.clone(), lock, queue) - .with_lifecycle(lifecycle.clone()); + let executor = + create_executor(persistence.clone(), lock, queue).with_lifecycle(lifecycle.clone()); let mut registry = StepRegistry::new(); registry.register::(); let mut def = WorkflowDefinition::new("test", 1); - def.steps.push(WorkflowStep::new(0, step_type::())); + def.steps + .push(WorkflowStep::new(0, step_type::())); let mut instance = WorkflowInstance::new("test", 1, serde_json::json!({})); instance.execution_pointers.push(ExecutionPointer::new(0)); persistence.create_new_workflow(&instance).await.unwrap(); - executor.execute(&instance.id, &def, ®istry, None).await.unwrap(); + executor + .execute(&instance.id, &def, ®istry, None) + .await + .unwrap(); // Executor itself doesn't publish lifecycle events in the current implementation, // but the with_lifecycle builder works correctly. - let updated = persistence.get_workflow_instance(&instance.id).await.unwrap(); + let updated = persistence + .get_workflow_instance(&instance.id) + .await + .unwrap(); assert_eq!(updated.status, WorkflowStatus::Complete); } @@ -1206,15 +1329,22 @@ mod tests { let mut def = WorkflowDefinition::new("test", 1); def.default_error_behavior = ErrorBehavior::Terminate; // Step has no error_behavior override. - def.steps.push(WorkflowStep::new(0, step_type::())); + def.steps + .push(WorkflowStep::new(0, step_type::())); let mut instance = WorkflowInstance::new("test", 1, serde_json::json!({})); instance.execution_pointers.push(ExecutionPointer::new(0)); persistence.create_new_workflow(&instance).await.unwrap(); - executor.execute(&instance.id, &def, ®istry, None).await.unwrap(); + executor + .execute(&instance.id, &def, ®istry, None) + .await + .unwrap(); - let updated = persistence.get_workflow_instance(&instance.id).await.unwrap(); + let updated = persistence + .get_workflow_instance(&instance.id) + .await + .unwrap(); assert_eq!(updated.status, WorkflowStatus::Terminated); } @@ -1227,15 +1357,22 @@ mod tests { registry.register::(); let mut def = WorkflowDefinition::new("test", 1); - def.steps.push(WorkflowStep::new(0, step_type::())); + def.steps + .push(WorkflowStep::new(0, step_type::())); let mut instance = WorkflowInstance::new("test", 1, serde_json::json!({})); instance.execution_pointers.push(ExecutionPointer::new(0)); persistence.create_new_workflow(&instance).await.unwrap(); - executor.execute(&instance.id, &def, ®istry, None).await.unwrap(); + executor + .execute(&instance.id, &def, ®istry, None) + .await + .unwrap(); - let updated = persistence.get_workflow_instance(&instance.id).await.unwrap(); + let updated = persistence + .get_workflow_instance(&instance.id) + .await + .unwrap(); assert!(updated.execution_pointers[0].start_time.is_some()); assert!(updated.execution_pointers[0].end_time.is_some()); } @@ -1257,15 +1394,22 @@ mod tests { value: Some(serde_json::json!("yes")), }); def.steps.push(s0); - def.steps.push(WorkflowStep::new(1, step_type::())); + def.steps + .push(WorkflowStep::new(1, step_type::())); let mut instance = WorkflowInstance::new("test", 1, serde_json::json!({})); instance.execution_pointers.push(ExecutionPointer::new(0)); persistence.create_new_workflow(&instance).await.unwrap(); - executor.execute(&instance.id, &def, ®istry, None).await.unwrap(); + executor + .execute(&instance.id, &def, ®istry, None) + .await + .unwrap(); - let updated = persistence.get_workflow_instance(&instance.id).await.unwrap(); + let updated = persistence + .get_workflow_instance(&instance.id) + .await + .unwrap(); assert_eq!( updated.execution_pointers[0].outcome, Some(serde_json::json!("yes")) @@ -1318,15 +1462,33 @@ mod tests { persistence.create_new_workflow(&instance).await.unwrap(); // First execution: fails, retry scheduled. - executor.execute(&instance.id, &def, ®istry, None).await.unwrap(); - let updated = persistence.get_workflow_instance(&instance.id).await.unwrap(); + executor + .execute(&instance.id, &def, ®istry, None) + .await + .unwrap(); + let updated = persistence + .get_workflow_instance(&instance.id) + .await + .unwrap(); assert_eq!(updated.execution_pointers[0].retry_count, 1); - assert_eq!(updated.execution_pointers[0].status, PointerStatus::Sleeping); + assert_eq!( + updated.execution_pointers[0].status, + PointerStatus::Sleeping + ); // Second execution: succeeds (sleep_until is in the past with 0ms interval). - executor.execute(&instance.id, &def, ®istry, None).await.unwrap(); - let updated = persistence.get_workflow_instance(&instance.id).await.unwrap(); - assert_eq!(updated.execution_pointers[0].status, PointerStatus::Complete); + executor + .execute(&instance.id, &def, ®istry, None) + .await + .unwrap(); + let updated = persistence + .get_workflow_instance(&instance.id) + .await + .unwrap(); + assert_eq!( + updated.execution_pointers[0].status, + PointerStatus::Complete + ); assert_eq!(updated.status, WorkflowStatus::Complete); } @@ -1342,9 +1504,15 @@ mod tests { // No execution pointers at all. persistence.create_new_workflow(&instance).await.unwrap(); - executor.execute(&instance.id, &def, ®istry, None).await.unwrap(); + executor + .execute(&instance.id, &def, ®istry, None) + .await + .unwrap(); - let updated = persistence.get_workflow_instance(&instance.id).await.unwrap(); + let updated = persistence + .get_workflow_instance(&instance.id) + .await + .unwrap(); assert_eq!(updated.status, WorkflowStatus::Runnable); } } diff --git a/wfe-core/src/models/condition.rs b/wfe-core/src/models/condition.rs index ef818ba..8e41e95 100644 --- a/wfe-core/src/models/condition.rs +++ b/wfe-core/src/models/condition.rs @@ -136,13 +136,11 @@ mod tests { #[test] fn step_condition_any_serde_round_trip() { - let condition = StepCondition::Any(vec![ - StepCondition::Comparison(FieldComparison { - field: ".x".to_string(), - operator: ComparisonOp::IsNull, - value: None, - }), - ]); + let condition = StepCondition::Any(vec![StepCondition::Comparison(FieldComparison { + field: ".x".to_string(), + operator: ComparisonOp::IsNull, + value: None, + })]); let json_str = serde_json::to_string(&condition).unwrap(); let deserialized: StepCondition = serde_json::from_str(&json_str).unwrap(); assert_eq!(condition, deserialized); @@ -150,13 +148,11 @@ mod tests { #[test] fn step_condition_none_serde_round_trip() { - let condition = StepCondition::None(vec![ - StepCondition::Comparison(FieldComparison { - field: ".err".to_string(), - operator: ComparisonOp::IsNotNull, - value: None, - }), - ]); + let condition = StepCondition::None(vec![StepCondition::Comparison(FieldComparison { + field: ".err".to_string(), + operator: ComparisonOp::IsNotNull, + value: None, + })]); let json_str = serde_json::to_string(&condition).unwrap(); let deserialized: StepCondition = serde_json::from_str(&json_str).unwrap(); assert_eq!(condition, deserialized); diff --git a/wfe-core/src/models/event.rs b/wfe-core/src/models/event.rs index 484ec9d..f616834 100644 --- a/wfe-core/src/models/event.rs +++ b/wfe-core/src/models/event.rs @@ -75,7 +75,11 @@ mod tests { #[test] fn new_event_defaults() { - let event = Event::new("order.created", "order-456", serde_json::json!({"amount": 100})); + let event = Event::new( + "order.created", + "order-456", + serde_json::json!({"amount": 100}), + ); assert_eq!(event.event_name, "order.created"); assert_eq!(event.event_key, "order-456"); assert!(!event.is_processed); diff --git a/wfe-core/src/models/execution_result.rs b/wfe-core/src/models/execution_result.rs index 2e79296..a172ef3 100644 --- a/wfe-core/src/models/execution_result.rs +++ b/wfe-core/src/models/execution_result.rs @@ -59,7 +59,10 @@ impl ExecutionResult { } /// Create child branches for parallel/foreach execution. - pub fn branch(values: Vec, persistence_data: Option) -> Self { + pub fn branch( + values: Vec, + persistence_data: Option, + ) -> Self { Self { proceed: false, branch_values: Some(values), @@ -137,7 +140,11 @@ mod tests { #[test] fn branch_creates_child_values() { - let values = vec![serde_json::json!(1), serde_json::json!(2), serde_json::json!(3)]; + let values = vec![ + serde_json::json!(1), + serde_json::json!(2), + serde_json::json!(3), + ]; let result = ExecutionResult::branch(values.clone(), None); assert!(!result.proceed); assert_eq!(result.branch_values, Some(values)); @@ -181,7 +188,8 @@ mod tests { #[test] fn serde_round_trip() { - let result = ExecutionResult::sleep(Duration::from_secs(30), Some(serde_json::json!({"x": 1}))); + let result = + ExecutionResult::sleep(Duration::from_secs(30), Some(serde_json::json!({"x": 1}))); let json = serde_json::to_string(&result).unwrap(); let deserialized: ExecutionResult = serde_json::from_str(&json).unwrap(); assert_eq!(result.proceed, deserialized.proceed); diff --git a/wfe-core/src/models/lifecycle.rs b/wfe-core/src/models/lifecycle.rs index 880cb55..146b5d1 100644 --- a/wfe-core/src/models/lifecycle.rs +++ b/wfe-core/src/models/lifecycle.rs @@ -18,9 +18,17 @@ pub enum LifecycleEventType { Suspended, Completed, Terminated, - Error { message: String }, - StepStarted { step_id: usize, step_name: Option }, - StepCompleted { step_id: usize, step_name: Option }, + Error { + message: String, + }, + StepStarted { + step_id: usize, + step_name: Option, + }, + StepCompleted { + step_id: usize, + step_name: Option, + }, } impl LifecycleEvent { @@ -56,7 +64,10 @@ mod tests { let event = LifecycleEvent::new("wf-1", "def-1", 1, LifecycleEventType::Started); let json = serde_json::to_string(&event).unwrap(); let deserialized: LifecycleEvent = serde_json::from_str(&json).unwrap(); - assert_eq!(event.workflow_instance_id, deserialized.workflow_instance_id); + assert_eq!( + event.workflow_instance_id, + deserialized.workflow_instance_id + ); assert_eq!(event.event_type, deserialized.event_type); } diff --git a/wfe-core/src/models/mod.rs b/wfe-core/src/models/mod.rs index 8149850..154dbe4 100644 --- a/wfe-core/src/models/mod.rs +++ b/wfe-core/src/models/mod.rs @@ -1,7 +1,6 @@ pub mod condition; pub mod error_behavior; pub mod event; -pub mod service; pub mod execution_error; pub mod execution_pointer; pub mod execution_result; @@ -10,6 +9,7 @@ pub mod poll_config; pub mod queue_type; pub mod scheduled_command; pub mod schema; +pub mod service; pub mod status; pub mod workflow_definition; pub mod workflow_instance; @@ -25,9 +25,11 @@ pub use poll_config::{HttpMethod, PollCondition, PollEndpointConfig}; pub use queue_type::QueueType; pub use scheduled_command::{CommandName, ScheduledCommand}; pub use schema::{SchemaType, WorkflowSchema}; +pub use service::{ + ReadinessCheck, ReadinessProbe, ServiceDefinition, ServiceEndpoint, ServicePort, +}; pub use status::{PointerStatus, WorkflowStatus}; pub use workflow_definition::{StepOutcome, WorkflowDefinition, WorkflowStep}; -pub use service::{ReadinessCheck, ReadinessProbe, ServiceDefinition, ServiceEndpoint, ServicePort}; pub use workflow_instance::WorkflowInstance; /// Serde helper for `Option` as milliseconds. diff --git a/wfe-core/src/models/schema.rs b/wfe-core/src/models/schema.rs index c3e0010..2992fe7 100644 --- a/wfe-core/src/models/schema.rs +++ b/wfe-core/src/models/schema.rs @@ -63,9 +63,7 @@ pub fn parse_type(s: &str) -> crate::Result { "integer" => Ok(SchemaType::Integer), "bool" => Ok(SchemaType::Bool), "any" => Ok(SchemaType::Any), - _ => Err(crate::WfeError::StepExecution(format!( - "Unknown type: {s}" - ))), + _ => Err(crate::WfeError::StepExecution(format!("Unknown type: {s}"))), } } @@ -110,8 +108,7 @@ pub fn validate_value(value: &serde_json::Value, expected: &SchemaType) -> Resul SchemaType::List(inner) => { if let Some(arr) = value.as_array() { for (i, item) in arr.iter().enumerate() { - validate_value(item, inner) - .map_err(|e| format!("list element [{i}]: {e}"))?; + validate_value(item, inner).map_err(|e| format!("list element [{i}]: {e}"))?; } Ok(()) } else { @@ -121,8 +118,7 @@ pub fn validate_value(value: &serde_json::Value, expected: &SchemaType) -> Resul SchemaType::Map(inner) => { if let Some(obj) = value.as_object() { for (key, val) in obj { - validate_value(val, inner) - .map_err(|e| format!("map key \"{key}\": {e}"))?; + validate_value(val, inner).map_err(|e| format!("map key \"{key}\": {e}"))?; } Ok(()) } else { diff --git a/wfe-core/src/primitives/foreach_step.rs b/wfe-core/src/primitives/foreach_step.rs index 08b01bc..51f9d63 100644 --- a/wfe-core/src/primitives/foreach_step.rs +++ b/wfe-core/src/primitives/foreach_step.rs @@ -130,7 +130,10 @@ mod tests { let result = step.run(&ctx).await.unwrap(); assert!(!result.proceed); - assert_eq!(result.branch_values, Some(vec![json!(1), json!(2), json!(3)])); + assert_eq!( + result.branch_values, + Some(vec![json!(1), json!(2), json!(3)]) + ); } #[tokio::test] diff --git a/wfe-core/src/primitives/if_step.rs b/wfe-core/src/primitives/if_step.rs index 250c2b5..2c49263 100644 --- a/wfe-core/src/primitives/if_step.rs +++ b/wfe-core/src/primitives/if_step.rs @@ -60,7 +60,10 @@ mod tests { let result = step.run(&ctx).await.unwrap(); assert!(!result.proceed); assert_eq!(result.branch_values, Some(vec![json!(null)])); - assert_eq!(result.persistence_data, Some(json!({"children_active": true}))); + assert_eq!( + result.persistence_data, + Some(json!({"children_active": true})) + ); } #[tokio::test] @@ -116,6 +119,9 @@ mod tests { let result = step.run(&ctx).await.unwrap(); assert!(!result.proceed); - assert_eq!(result.persistence_data, Some(json!({"children_active": true}))); + assert_eq!( + result.persistence_data, + Some(json!({"children_active": true})) + ); } } diff --git a/wfe-core/src/primitives/mod.rs b/wfe-core/src/primitives/mod.rs index af9ebac..6d9fb60 100644 --- a/wfe-core/src/primitives/mod.rs +++ b/wfe-core/src/primitives/mod.rs @@ -45,7 +45,7 @@ mod test_helpers { workflow, cancellation_token: CancellationToken::new(), host_context: None, - log_sink: None, + log_sink: None, } } diff --git a/wfe-core/src/primitives/poll_endpoint.rs b/wfe-core/src/primitives/poll_endpoint.rs index a47d45e..dc24a2f 100644 --- a/wfe-core/src/primitives/poll_endpoint.rs +++ b/wfe-core/src/primitives/poll_endpoint.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; -use crate::models::poll_config::PollEndpointConfig; use crate::models::ExecutionResult; +use crate::models::poll_config::PollEndpointConfig; use crate::traits::step::{StepBody, StepExecutionContext}; /// A step that polls an external HTTP endpoint until a condition is met. @@ -21,8 +21,8 @@ impl StepBody for PollEndpointStep { #[cfg(test)] mod tests { use super::*; - use crate::models::poll_config::{HttpMethod, PollCondition}; use crate::models::ExecutionPointer; + use crate::models::poll_config::{HttpMethod, PollCondition}; use crate::primitives::test_helpers::*; use std::collections::HashMap; use std::time::Duration; diff --git a/wfe-core/src/primitives/recur.rs b/wfe-core/src/primitives/recur.rs index 535aa56..191cb02 100644 --- a/wfe-core/src/primitives/recur.rs +++ b/wfe-core/src/primitives/recur.rs @@ -85,7 +85,10 @@ mod tests { let result = step.run(&ctx).await.unwrap(); assert!(!result.proceed); assert_eq!(result.sleep_for, Some(Duration::from_secs(10))); - assert_eq!(result.persistence_data, Some(json!({"children_active": true}))); + assert_eq!( + result.persistence_data, + Some(json!({"children_active": true})) + ); } #[tokio::test] @@ -130,6 +133,9 @@ mod tests { let result = step.run(&ctx).await.unwrap(); assert!(!result.proceed); assert!(result.sleep_for.is_none()); - assert_eq!(result.persistence_data, Some(json!({"children_active": true}))); + assert_eq!( + result.persistence_data, + Some(json!({"children_active": true})) + ); } } diff --git a/wfe-core/src/primitives/saga_container.rs b/wfe-core/src/primitives/saga_container.rs index fdc3e06..71f3d70 100644 --- a/wfe-core/src/primitives/saga_container.rs +++ b/wfe-core/src/primitives/saga_container.rs @@ -89,7 +89,10 @@ mod tests { let result = step.run(&ctx).await.unwrap(); assert!(!result.proceed); assert_eq!(result.branch_values, Some(vec![json!(null)])); - assert_eq!(result.persistence_data, Some(json!({"children_active": true}))); + assert_eq!( + result.persistence_data, + Some(json!({"children_active": true})) + ); } #[tokio::test] diff --git a/wfe-core/src/primitives/schedule.rs b/wfe-core/src/primitives/schedule.rs index 1f4b12f..182617a 100644 --- a/wfe-core/src/primitives/schedule.rs +++ b/wfe-core/src/primitives/schedule.rs @@ -60,7 +60,10 @@ mod tests { let result = step.run(&ctx).await.unwrap(); assert!(!result.proceed); assert_eq!(result.sleep_for, Some(Duration::from_secs(30))); - assert_eq!(result.persistence_data, Some(json!({"children_active": true}))); + assert_eq!( + result.persistence_data, + Some(json!({"children_active": true})) + ); } #[tokio::test] @@ -101,6 +104,9 @@ mod tests { let ctx = make_context(&pointer, &wf_step, &workflow); let result = step.run(&ctx).await.unwrap(); assert!(!result.proceed); - assert_eq!(result.persistence_data, Some(json!({"children_active": true}))); + assert_eq!( + result.persistence_data, + Some(json!({"children_active": true})) + ); } } diff --git a/wfe-core/src/primitives/sequence.rs b/wfe-core/src/primitives/sequence.rs index 74da8e3..face3a8 100644 --- a/wfe-core/src/primitives/sequence.rs +++ b/wfe-core/src/primitives/sequence.rs @@ -61,7 +61,10 @@ mod tests { let ctx = make_context(&pointer, &wf_step, &workflow); let result = step.run(&ctx).await.unwrap(); assert!(!result.proceed); - assert_eq!(result.persistence_data, Some(json!({"children_active": true}))); + assert_eq!( + result.persistence_data, + Some(json!({"children_active": true})) + ); } #[tokio::test] diff --git a/wfe-core/src/primitives/while_step.rs b/wfe-core/src/primitives/while_step.rs index 0210706..8e7bdce 100644 --- a/wfe-core/src/primitives/while_step.rs +++ b/wfe-core/src/primitives/while_step.rs @@ -69,7 +69,10 @@ mod tests { let result = step.run(&ctx).await.unwrap(); assert!(!result.proceed); assert_eq!(result.branch_values, Some(vec![json!(null)])); - assert_eq!(result.persistence_data, Some(json!({"children_active": true}))); + assert_eq!( + result.persistence_data, + Some(json!({"children_active": true})) + ); } #[tokio::test] @@ -141,6 +144,9 @@ mod tests { let result = step.run(&ctx).await.unwrap(); assert!(!result.proceed); assert!(result.branch_values.is_none()); - assert_eq!(result.persistence_data, Some(json!({"children_active": true}))); + assert_eq!( + result.persistence_data, + Some(json!({"children_active": true})) + ); } } diff --git a/wfe-core/src/test_support/in_memory_lifecycle.rs b/wfe-core/src/test_support/in_memory_lifecycle.rs index 39295d7..c9d6f28 100644 --- a/wfe-core/src/test_support/in_memory_lifecycle.rs +++ b/wfe-core/src/test_support/in_memory_lifecycle.rs @@ -3,9 +3,9 @@ use std::sync::Arc; use async_trait::async_trait; use tokio::sync::Mutex; +use crate::Result; use crate::models::LifecycleEvent; use crate::traits::LifecyclePublisher; -use crate::Result; /// An in-memory implementation of `LifecyclePublisher` for testing. #[derive(Debug, Clone)] diff --git a/wfe-core/src/test_support/in_memory_lock.rs b/wfe-core/src/test_support/in_memory_lock.rs index 75352d7..cd856c6 100644 --- a/wfe-core/src/test_support/in_memory_lock.rs +++ b/wfe-core/src/test_support/in_memory_lock.rs @@ -4,8 +4,8 @@ use std::sync::Arc; use async_trait::async_trait; use tokio::sync::Mutex; -use crate::traits::DistributedLockProvider; use crate::Result; +use crate::traits::DistributedLockProvider; /// An in-memory implementation of `DistributedLockProvider` for testing. #[derive(Debug, Clone)] diff --git a/wfe-core/src/test_support/in_memory_queue.rs b/wfe-core/src/test_support/in_memory_queue.rs index e1f023d..e3f111a 100644 --- a/wfe-core/src/test_support/in_memory_queue.rs +++ b/wfe-core/src/test_support/in_memory_queue.rs @@ -4,9 +4,9 @@ use std::sync::Arc; use async_trait::async_trait; use tokio::sync::Mutex; +use crate::Result; use crate::models::QueueType; use crate::traits::QueueProvider; -use crate::Result; /// An in-memory implementation of `QueueProvider` for testing. #[derive(Debug, Clone)] diff --git a/wfe-core/src/test_support/queue_suite.rs b/wfe-core/src/test_support/queue_suite.rs index 9f5e549..9dbddbf 100644 --- a/wfe-core/src/test_support/queue_suite.rs +++ b/wfe-core/src/test_support/queue_suite.rs @@ -17,18 +17,9 @@ macro_rules! queue_suite { #[tokio::test] async fn enqueue_dequeue_fifo() { let provider = ($factory)().await; - provider - .queue_work("a", QueueType::Workflow) - .await - .unwrap(); - provider - .queue_work("b", QueueType::Workflow) - .await - .unwrap(); - provider - .queue_work("c", QueueType::Workflow) - .await - .unwrap(); + provider.queue_work("a", QueueType::Workflow).await.unwrap(); + provider.queue_work("b", QueueType::Workflow).await.unwrap(); + provider.queue_work("c", QueueType::Workflow).await.unwrap(); assert_eq!( provider @@ -94,16 +85,20 @@ macro_rules! queue_suite { ); // Both should now be empty - assert!(provider - .dequeue_work(QueueType::Event) - .await - .unwrap() - .is_none()); - assert!(provider - .dequeue_work(QueueType::Workflow) - .await - .unwrap() - .is_none()); + assert!( + provider + .dequeue_work(QueueType::Event) + .await + .unwrap() + .is_none() + ); + assert!( + provider + .dequeue_work(QueueType::Workflow) + .await + .unwrap() + .is_none() + ); } } }; diff --git a/wfe-core/src/traits/middleware.rs b/wfe-core/src/traits/middleware.rs index 0ea1863..87b0853 100644 --- a/wfe-core/src/traits/middleware.rs +++ b/wfe-core/src/traits/middleware.rs @@ -69,7 +69,7 @@ mod tests { workflow: &instance, cancellation_token: tokio_util::sync::CancellationToken::new(), host_context: None, - log_sink: None, + log_sink: None, }; mw.pre_step(&ctx).await.unwrap(); } @@ -89,7 +89,7 @@ mod tests { workflow: &instance, cancellation_token: tokio_util::sync::CancellationToken::new(), host_context: None, - log_sink: None, + log_sink: None, }; let result = ExecutionResult::next(); mw.post_step(&ctx, &result).await.unwrap(); diff --git a/wfe-core/src/traits/mod.rs b/wfe-core/src/traits/mod.rs index 399cf8a..184484b 100644 --- a/wfe-core/src/traits/mod.rs +++ b/wfe-core/src/traits/mod.rs @@ -1,12 +1,12 @@ pub mod lifecycle; pub mod lock; -pub mod service; pub mod log_sink; pub mod middleware; pub mod persistence; pub mod queue; pub mod registry; pub mod search; +pub mod service; pub mod step; pub use lifecycle::LifecyclePublisher; diff --git a/wfe-core/src/traits/step.rs b/wfe-core/src/traits/step.rs index 213a03c..9ad1aef 100644 --- a/wfe-core/src/traits/step.rs +++ b/wfe-core/src/traits/step.rs @@ -1,6 +1,6 @@ use async_trait::async_trait; -use serde::de::DeserializeOwned; use serde::Serialize; +use serde::de::DeserializeOwned; use crate::models::{ExecutionPointer, ExecutionResult, WorkflowInstance, WorkflowStep}; diff --git a/wfe-deno/src/ops/builder.rs b/wfe-deno/src/ops/builder.rs index 0c13ad7..4d101a8 100644 --- a/wfe-deno/src/ops/builder.rs +++ b/wfe-deno/src/ops/builder.rs @@ -1,7 +1,7 @@ use std::time::Duration; -use deno_core::op2; use deno_core::OpState; +use deno_core::op2; use wfe_core::builder::WorkflowBuilder; use wfe_core::models::ErrorBehavior; diff --git a/wfe-deno/src/ops/event.rs b/wfe-deno/src/ops/event.rs index 1290cbc..879c640 100644 --- a/wfe-deno/src/ops/event.rs +++ b/wfe-deno/src/ops/event.rs @@ -1,5 +1,5 @@ -use deno_core::op2; use deno_core::OpState; +use deno_core::op2; use crate::state::WfeState; diff --git a/wfe-deno/src/ops/host.rs b/wfe-deno/src/ops/host.rs index 0dc541a..9cab0eb 100644 --- a/wfe-deno/src/ops/host.rs +++ b/wfe-deno/src/ops/host.rs @@ -1,7 +1,7 @@ use std::sync::Arc; -use deno_core::op2; use deno_core::OpState; +use deno_core::op2; use crate::state::WfeState; diff --git a/wfe-deno/src/ops/step.rs b/wfe-deno/src/ops/step.rs index 5a51c93..82dfc1d 100644 --- a/wfe-deno/src/ops/step.rs +++ b/wfe-deno/src/ops/step.rs @@ -1,7 +1,7 @@ use std::sync::Arc; -use deno_core::op2; use deno_core::OpState; +use deno_core::op2; use crate::bridge::JsStepBody; use crate::state::WfeState; @@ -23,10 +23,9 @@ pub async fn op_register_step( }; let counter = Arc::new(std::sync::atomic::AtomicU32::new(0)); - host.register_step_factory( - &step_type, - move || Box::new(JsStepBody::new(tx.clone(), counter.clone())), - ) + host.register_step_factory(&step_type, move || { + Box::new(JsStepBody::new(tx.clone(), counter.clone())) + }) .await; Ok(()) diff --git a/wfe-deno/src/ops/workflow.rs b/wfe-deno/src/ops/workflow.rs index b257cda..4255e2a 100644 --- a/wfe-deno/src/ops/workflow.rs +++ b/wfe-deno/src/ops/workflow.rs @@ -1,5 +1,5 @@ -use deno_core::op2; use deno_core::OpState; +use deno_core::op2; use crate::state::WfeState; diff --git a/wfe-deno/tests/integration.rs b/wfe-deno/tests/integration.rs index 8e3e813..d7c5d97 100644 --- a/wfe-deno/tests/integration.rs +++ b/wfe-deno/tests/integration.rs @@ -16,8 +16,7 @@ async fn run_js(code: &str) -> Result<(), Box> { /// Helper: run a JS module in a fresh wfe runtime and drive the event loop. async fn run_module(code: &str) -> Result<(), Box> { let mut runtime = create_wfe_runtime(); - let specifier = - deno_core::ModuleSpecifier::parse("ext:wfe-deno/test-module.js").unwrap(); + let specifier = deno_core::ModuleSpecifier::parse("ext:wfe-deno/test-module.js").unwrap(); let module_id = runtime .load_main_es_module_from_code(&specifier, code.to_string()) .await @@ -27,8 +26,7 @@ async fn run_module(code: &str) -> Result<(), Box> { .run_event_loop(Default::default()) .await .map_err(|e| format!("event loop error: {e}"))?; - eval.await - .map_err(|e| format!("module eval error: {e}"))?; + eval.await.map_err(|e| format!("module eval error: {e}"))?; Ok(()) } diff --git a/wfe-kubernetes/src/logs.rs b/wfe-kubernetes/src/logs.rs index e205b2a..e4a6642 100644 --- a/wfe-kubernetes/src/logs.rs +++ b/wfe-kubernetes/src/logs.rs @@ -1,10 +1,10 @@ -use futures::io::AsyncBufReadExt; use futures::StreamExt; +use futures::io::AsyncBufReadExt; use k8s_openapi::api::core::v1::Pod; use kube::api::LogParams; use kube::{Api, Client}; -use wfe_core::traits::log_sink::{LogChunk, LogSink, LogStreamType}; use wfe_core::WfeError; +use wfe_core::traits::log_sink::{LogChunk, LogSink, LogStreamType}; /// Stream logs from a pod container, optionally forwarding to a LogSink. /// @@ -29,9 +29,7 @@ pub async fn stream_logs( }; let stream = pods.log_stream(pod_name, ¶ms).await.map_err(|e| { - WfeError::StepExecution(format!( - "failed to stream logs from pod '{pod_name}': {e}" - )) + WfeError::StepExecution(format!("failed to stream logs from pod '{pod_name}': {e}")) })?; let mut stdout = String::new(); diff --git a/wfe-kubernetes/src/namespace.rs b/wfe-kubernetes/src/namespace.rs index 0139857..f797ff8 100644 --- a/wfe-kubernetes/src/namespace.rs +++ b/wfe-kubernetes/src/namespace.rs @@ -16,7 +16,13 @@ pub fn namespace_name(prefix: &str, workflow_id: &str) -> String { let sanitized: String = raw .to_lowercase() .chars() - .map(|c| if c.is_ascii_alphanumeric() || c == '-' { c } else { '-' }) + .map(|c| { + if c.is_ascii_alphanumeric() || c == '-' { + c + } else { + '-' + } + }) .take(63) .collect(); // Trim trailing hyphens @@ -55,9 +61,9 @@ pub async fn ensure_namespace( ..Default::default() }; - api.create(&PostParams::default(), &ns) - .await - .map_err(|e| WfeError::StepExecution(format!("failed to create namespace '{name}': {e}")))?; + api.create(&PostParams::default(), &ns).await.map_err(|e| { + WfeError::StepExecution(format!("failed to create namespace '{name}': {e}")) + })?; Ok(()) } @@ -65,9 +71,9 @@ pub async fn ensure_namespace( /// Delete a namespace and all resources within it. pub async fn delete_namespace(client: &Client, name: &str) -> Result<(), WfeError> { let api: Api = Api::all(client.clone()); - api.delete(name, &Default::default()) - .await - .map_err(|e| WfeError::StepExecution(format!("failed to delete namespace '{name}': {e}")))?; + api.delete(name, &Default::default()).await.map_err(|e| { + WfeError::StepExecution(format!("failed to delete namespace '{name}': {e}")) + })?; Ok(()) } diff --git a/wfe-kubernetes/src/output.rs b/wfe-kubernetes/src/output.rs index c9396fe..5a3f66c 100644 --- a/wfe-kubernetes/src/output.rs +++ b/wfe-kubernetes/src/output.rs @@ -152,10 +152,12 @@ mod tests { #[test] fn build_output_data_json_value() { - let parsed: HashMap = - [("count".into(), "42".into()), ("flag".into(), "true".into())] - .into_iter() - .collect(); + let parsed: HashMap = [ + ("count".into(), "42".into()), + ("flag".into(), "true".into()), + ] + .into_iter() + .collect(); let data = build_output_data("s", "", "", 0, &parsed); // Numbers and booleans should be parsed as JSON, not strings. assert_eq!(data["count"], 42); diff --git a/wfe-kubernetes/src/service_manifests.rs b/wfe-kubernetes/src/service_manifests.rs index d336545..31e432e 100644 --- a/wfe-kubernetes/src/service_manifests.rs +++ b/wfe-kubernetes/src/service_manifests.rs @@ -77,8 +77,16 @@ pub fn build_service_pod(svc: &ServiceDefinition, namespace: &str) -> Pod { command, args, resources: Some(ResourceRequirements { - limits: if limits.is_empty() { None } else { Some(limits) }, - requests: if requests.is_empty() { None } else { Some(requests) }, + limits: if limits.is_empty() { + None + } else { + Some(limits) + }, + requests: if requests.is_empty() { + None + } else { + Some(requests) + }, ..Default::default() }), ..Default::default() @@ -230,10 +238,7 @@ mod tests { let ports = spec.ports.as_ref().unwrap(); assert_eq!(ports.len(), 1); assert_eq!(ports[0].port, 5432); - assert_eq!( - ports[0].target_port, - Some(IntOrString::Int(5432)) - ); + assert_eq!(ports[0].target_port, Some(IntOrString::Int(5432))); } #[test] @@ -241,10 +246,7 @@ mod tests { let svc = ServiceDefinition { name: "app".into(), image: "myapp".into(), - ports: vec![ - WfeServicePort::tcp(8080), - WfeServicePort::tcp(8443), - ], + ports: vec![WfeServicePort::tcp(8080), WfeServicePort::tcp(8443)], env: Default::default(), readiness: None, command: vec![], diff --git a/wfe-kubernetes/src/service_provider.rs b/wfe-kubernetes/src/service_provider.rs index a742e83..1492d48 100644 --- a/wfe-kubernetes/src/service_provider.rs +++ b/wfe-kubernetes/src/service_provider.rs @@ -4,9 +4,9 @@ use async_trait::async_trait; use k8s_openapi::api::core::v1::Pod; use kube::api::PostParams; use kube::{Api, Client}; +use wfe_core::WfeError; use wfe_core::models::service::{ServiceDefinition, ServiceEndpoint}; use wfe_core::traits::ServiceProvider; -use wfe_core::WfeError; use crate::config::ClusterConfig; use crate::logs::wait_for_pod_running; @@ -77,11 +77,8 @@ impl ServiceProvider for KubernetesServiceProvider { .map(|r| Duration::from_millis(r.timeout_ms)) .unwrap_or(Duration::from_secs(120)); - match tokio::time::timeout( - timeout, - wait_for_pod_running(&self.client, &ns, &svc.name), - ) - .await + match tokio::time::timeout(timeout, wait_for_pod_running(&self.client, &ns, &svc.name)) + .await { Ok(Ok(())) => {} Ok(Err(e)) => { diff --git a/wfe-kubernetes/src/step.rs b/wfe-kubernetes/src/step.rs index fe94d7e..6d0ed20 100644 --- a/wfe-kubernetes/src/step.rs +++ b/wfe-kubernetes/src/step.rs @@ -6,9 +6,9 @@ use k8s_openapi::api::batch::v1::Job; use k8s_openapi::api::core::v1::Pod; use kube::api::{ListParams, PostParams}; use kube::{Api, Client}; +use wfe_core::WfeError; use wfe_core::models::ExecutionResult; use wfe_core::traits::step::{StepBody, StepExecutionContext}; -use wfe_core::WfeError; use crate::cleanup::delete_job; use crate::config::{ClusterConfig, KubernetesStepConfig}; @@ -86,13 +86,7 @@ impl StepBody for KubernetesStep { let env_overrides = extract_workflow_env(&context.workflow.data); // 4. Build Job manifest. - let job_manifest = build_job( - &self.config, - &step_name, - &ns, - &env_overrides, - &self.cluster, - ); + let job_manifest = build_job(&self.config, &step_name, &ns, &env_overrides, &self.cluster); let job_name = job_manifest .metadata .name @@ -111,7 +105,15 @@ impl StepBody for KubernetesStep { let result = if let Some(timeout_ms) = self.config.timeout_ms { match tokio::time::timeout( Duration::from_millis(timeout_ms), - self.execute_job(&client, &ns, &job_name, &step_name, definition_id, workflow_id, context), + self.execute_job( + &client, + &ns, + &job_name, + &step_name, + definition_id, + workflow_id, + context, + ), ) .await { @@ -125,8 +127,16 @@ impl StepBody for KubernetesStep { } } } else { - self.execute_job(&client, &ns, &job_name, &step_name, definition_id, workflow_id, context) - .await + self.execute_job( + &client, + &ns, + &job_name, + &step_name, + definition_id, + workflow_id, + context, + ) + .await }; // Always attempt cleanup. @@ -205,9 +215,7 @@ async fn wait_for_job_pod( .list(&ListParams::default().labels(&selector)) .await .map_err(|e| { - WfeError::StepExecution(format!( - "failed to list pods for job '{job_name}': {e}" - )) + WfeError::StepExecution(format!("failed to list pods for job '{job_name}': {e}")) })?; if let Some(pod) = pod_list.items.first() { @@ -236,9 +244,10 @@ async fn wait_for_job_completion( // Poll Job status. for _ in 0..600 { - let job = jobs.get(job_name).await.map_err(|e| { - WfeError::StepExecution(format!("failed to get job '{job_name}': {e}")) - })?; + let job = jobs + .get(job_name) + .await + .map_err(|e| WfeError::StepExecution(format!("failed to get job '{job_name}': {e}")))?; if let Some(status) = &job.status { if let Some(conditions) = &status.conditions { @@ -352,9 +361,6 @@ mod tests { let data = serde_json::json!({"config": {"nested": true}}); let env = extract_workflow_env(&data); // Nested object serialized as JSON string. - assert_eq!( - env.get("CONFIG"), - Some(&r#"{"nested":true}"#.to_string()) - ); + assert_eq!(env.get("CONFIG"), Some(&r#"{"nested":true}"#.to_string())); } } diff --git a/wfe-kubernetes/tests/integration.rs b/wfe-kubernetes/tests/integration.rs index e01c407..344434e 100644 --- a/wfe-kubernetes/tests/integration.rs +++ b/wfe-kubernetes/tests/integration.rs @@ -1,13 +1,13 @@ use std::collections::HashMap; use wfe_core::models::service::{ReadinessCheck, ReadinessProbe, ServiceDefinition, ServicePort}; -use wfe_core::traits::step::StepBody; use wfe_core::traits::ServiceProvider; -use wfe_kubernetes::config::{ClusterConfig, KubernetesStepConfig}; -use wfe_kubernetes::namespace; +use wfe_core::traits::step::StepBody; +use wfe_kubernetes::KubernetesServiceProvider; use wfe_kubernetes::cleanup; use wfe_kubernetes::client; -use wfe_kubernetes::KubernetesServiceProvider; +use wfe_kubernetes::config::{ClusterConfig, KubernetesStepConfig}; +use wfe_kubernetes::namespace; /// Path to the Lima sunbeam VM kubeconfig. fn kubeconfig_path() -> String { @@ -64,10 +64,14 @@ async fn namespace_create_and_delete() { let client = client::create_client(&config).await.unwrap(); let ns = "wfe-test-ns-lifecycle"; - namespace::ensure_namespace(&client, ns, "test-wf").await.unwrap(); + namespace::ensure_namespace(&client, ns, "test-wf") + .await + .unwrap(); // Idempotent — creating again should succeed. - namespace::ensure_namespace(&client, ns, "test-wf").await.unwrap(); + namespace::ensure_namespace(&client, ns, "test-wf") + .await + .unwrap(); namespace::delete_namespace(&client, ns).await.unwrap(); } @@ -107,10 +111,12 @@ async fn run_echo_job() { assert!(result.proceed); let output = result.output_data.unwrap(); - assert!(output["echo-step.stdout"] - .as_str() - .unwrap() - .contains("hello from k8s")); + assert!( + output["echo-step.stdout"] + .as_str() + .unwrap() + .contains("hello from k8s") + ); assert_eq!(output["echo-step.exit_code"], 0); // Cleanup namespace. @@ -132,8 +138,7 @@ async fn run_job_with_wfe_output() { let mut step = wfe_kubernetes::KubernetesStep::new(step_cfg, config.clone(), k8s_client.clone()); - let instance = - wfe_core::models::WorkflowInstance::new("output-wf", 1, serde_json::json!({})); + let instance = wfe_core::models::WorkflowInstance::new("output-wf", 1, serde_json::json!({})); let mut ws = wfe_core::models::WorkflowStep::new(0, "alpine-output"); ws.name = Some("output-step".into()); let pointer = wfe_core::models::ExecutionPointer::new(0); @@ -250,8 +255,7 @@ async fn run_job_with_timeout() { let mut step = wfe_kubernetes::KubernetesStep::new(step_cfg, config.clone(), k8s_client.clone()); - let instance = - wfe_core::models::WorkflowInstance::new("timeout-wf", 1, serde_json::json!({})); + let instance = wfe_core::models::WorkflowInstance::new("timeout-wf", 1, serde_json::json!({})); let mut ws = wfe_core::models::WorkflowStep::new(0, "alpine-timeout"); ws.name = Some("timeout-step".into()); let pointer = wfe_core::models::ExecutionPointer::new(0); @@ -484,7 +488,10 @@ async fn service_provider_provision_duplicate_name_fails() { }; // First provision succeeds. - let endpoints = provider.provision(workflow_id, &[svc.clone()]).await.unwrap(); + let endpoints = provider + .provision(workflow_id, &[svc.clone()]) + .await + .unwrap(); assert_eq!(endpoints.len(), 1); // Second provision with same name should fail (pod already exists). @@ -505,7 +512,9 @@ async fn service_provider_provision_service_object_conflict() { let workflow_id = &unique_id("svc-conflict"); let ns = namespace::namespace_name(&config.namespace_prefix, workflow_id); - namespace::ensure_namespace(&k8s_client, &ns, workflow_id).await.unwrap(); + namespace::ensure_namespace(&k8s_client, &ns, workflow_id) + .await + .unwrap(); // Pre-create just the K8s Service (not the pod). let svc_def = nginx_service(); diff --git a/wfe-opensearch/src/lib.rs b/wfe-opensearch/src/lib.rs index 463dbb8..e1b624d 100644 --- a/wfe-opensearch/src/lib.rs +++ b/wfe-opensearch/src/lib.rs @@ -80,7 +80,7 @@ impl SearchIndex for OpenSearchIndex { .client .indices() .exists(opensearch::indices::IndicesExistsParts::Index(&[ - &self.index_name, + &self.index_name ])) .send() .await diff --git a/wfe-opensearch/tests/search.rs b/wfe-opensearch/tests/search.rs index 038e9e9..e9dac30 100644 --- a/wfe-opensearch/tests/search.rs +++ b/wfe-opensearch/tests/search.rs @@ -1,6 +1,6 @@ use chrono::Utc; -use opensearch::http::transport::Transport; use opensearch::OpenSearch; +use opensearch::http::transport::Transport; use pretty_assertions::assert_eq; use serde_json::json; use uuid::Uuid; @@ -60,7 +60,7 @@ async fn cleanup(provider: &OpenSearchIndex) { .client() .indices() .delete(opensearch::indices::IndicesDeleteParts::Index(&[ - provider.index_name(), + provider.index_name() ])) .send() .await; @@ -164,7 +164,10 @@ async fn index_multiple_and_paginate() { refresh_index(&provider).await; // Search all, but skip 2 and take 2 - let page = provider.search("Paginated workflow", 2, 2, &[]).await.unwrap(); + let page = provider + .search("Paginated workflow", 2, 2, &[]) + .await + .unwrap(); assert_eq!(page.total, 5); assert_eq!(page.data.len(), 2); diff --git a/wfe-rustlang/src/cargo/config.rs b/wfe-rustlang/src/cargo/config.rs index af54540..f188ee6 100644 --- a/wfe-rustlang/src/cargo/config.rs +++ b/wfe-rustlang/src/cargo/config.rs @@ -229,7 +229,10 @@ mod tests { #[test] fn subcommand_args_nextest_has_run() { - assert_eq!(CargoCommand::Nextest.subcommand_args(), vec!["nextest", "run"]); + assert_eq!( + CargoCommand::Nextest.subcommand_args(), + vec!["nextest", "run"] + ); } #[test] @@ -241,8 +244,14 @@ mod tests { fn install_package_external_tools() { assert_eq!(CargoCommand::Audit.install_package(), Some("cargo-audit")); assert_eq!(CargoCommand::Deny.install_package(), Some("cargo-deny")); - assert_eq!(CargoCommand::Nextest.install_package(), Some("cargo-nextest")); - assert_eq!(CargoCommand::LlvmCov.install_package(), Some("cargo-llvm-cov")); + assert_eq!( + CargoCommand::Nextest.install_package(), + Some("cargo-nextest") + ); + assert_eq!( + CargoCommand::LlvmCov.install_package(), + Some("cargo-llvm-cov") + ); } #[test] diff --git a/wfe-rustlang/src/cargo/step.rs b/wfe-rustlang/src/cargo/step.rs index 41a5187..9ab8071 100644 --- a/wfe-rustlang/src/cargo/step.rs +++ b/wfe-rustlang/src/cargo/step.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; +use wfe_core::WfeError; use wfe_core::models::ExecutionResult; use wfe_core::traits::step::{StepBody, StepExecutionContext}; -use wfe_core::WfeError; use crate::cargo::config::{CargoCommand, CargoConfig}; @@ -88,7 +88,10 @@ impl CargoStep { /// Ensures an external cargo tool is installed before running it. /// For built-in cargo subcommands, this is a no-op. async fn ensure_tool_available(&self) -> Result<(), WfeError> { - let (binary, package) = match (self.config.command.binary_name(), self.config.command.install_package()) { + let (binary, package) = match ( + self.config.command.binary_name(), + self.config.command.install_package(), + ) { (Some(b), Some(p)) => (b, p), _ => return Ok(()), }; @@ -117,9 +120,11 @@ impl CargoStep { .stderr(std::process::Stdio::piped()) .output() .await - .map_err(|e| WfeError::StepExecution(format!( - "Failed to add llvm-tools-preview component: {e}" - )))?; + .map_err(|e| { + WfeError::StepExecution(format!( + "Failed to add llvm-tools-preview component: {e}" + )) + })?; if !component.status.success() { let stderr = String::from_utf8_lossy(&component.stderr); @@ -135,9 +140,7 @@ impl CargoStep { .stderr(std::process::Stdio::piped()) .output() .await - .map_err(|e| WfeError::StepExecution(format!( - "Failed to install {package}: {e}" - )))?; + .map_err(|e| WfeError::StepExecution(format!("Failed to install {package}: {e}")))?; if !install.status.success() { let stderr = String::from_utf8_lossy(&install.stderr); @@ -162,17 +165,16 @@ impl CargoStep { let doc_dir = std::path::Path::new(working_dir).join("target/doc"); let json_path = std::fs::read_dir(&doc_dir) - .map_err(|e| WfeError::StepExecution(format!( - "failed to read target/doc: {e}" - )))? + .map_err(|e| WfeError::StepExecution(format!("failed to read target/doc: {e}")))? .filter_map(|entry| entry.ok()) - .find(|entry| { - entry.path().extension().is_some_and(|ext| ext == "json") - }) + .find(|entry| entry.path().extension().is_some_and(|ext| ext == "json")) .map(|entry| entry.path()) - .ok_or_else(|| WfeError::StepExecution( - "no JSON file found in target/doc/ — did rustdoc --output-format json succeed?".to_string() - ))?; + .ok_or_else(|| { + WfeError::StepExecution( + "no JSON file found in target/doc/ — did rustdoc --output-format json succeed?" + .to_string(), + ) + })?; tracing::info!(path = %json_path.display(), "reading rustdoc JSON"); @@ -180,20 +182,20 @@ impl CargoStep { WfeError::StepExecution(format!("failed to read {}: {e}", json_path.display())) })?; - let krate: rustdoc_types::Crate = serde_json::from_str(&json_content).map_err(|e| { - WfeError::StepExecution(format!("failed to parse rustdoc JSON: {e}")) - })?; + let krate: rustdoc_types::Crate = serde_json::from_str(&json_content) + .map_err(|e| WfeError::StepExecution(format!("failed to parse rustdoc JSON: {e}")))?; let mdx_files = transform_to_mdx(&krate); - let output_dir = self.config.output_dir + let output_dir = self + .config + .output_dir .as_deref() .unwrap_or("target/doc/mdx"); let output_path = std::path::Path::new(working_dir).join(output_dir); - write_mdx_files(&mdx_files, &output_path).map_err(|e| { - WfeError::StepExecution(format!("failed to write MDX files: {e}")) - })?; + write_mdx_files(&mdx_files, &output_path) + .map_err(|e| WfeError::StepExecution(format!("failed to write MDX files: {e}")))?; let file_count = mdx_files.len(); tracing::info!( @@ -214,7 +216,10 @@ impl CargoStep { outputs.insert( "mdx.files".to_string(), serde_json::Value::Array( - file_paths.into_iter().map(serde_json::Value::String).collect(), + file_paths + .into_iter() + .map(serde_json::Value::String) + .collect(), ), ); @@ -224,7 +229,10 @@ impl CargoStep { #[async_trait] impl StepBody for CargoStep { - async fn run(&mut self, context: &StepExecutionContext<'_>) -> wfe_core::Result { + async fn run( + &mut self, + context: &StepExecutionContext<'_>, + ) -> wfe_core::Result { let step_name = context.step.name.as_deref().unwrap_or("unknown"); let subcmd = self.config.command.as_str(); @@ -248,9 +256,9 @@ impl StepBody for CargoStep { } } } else { - cmd.output() - .await - .map_err(|e| WfeError::StepExecution(format!("Failed to spawn cargo {subcmd}: {e}")))? + cmd.output().await.map_err(|e| { + WfeError::StepExecution(format!("Failed to spawn cargo {subcmd}: {e}")) + })? }; let stdout = String::from_utf8_lossy(&output.stdout).to_string(); @@ -317,7 +325,11 @@ mod tests { let cmd = step.build_command(); let prog = cmd.as_std().get_program().to_str().unwrap(); assert_eq!(prog, "cargo"); - let args: Vec<_> = cmd.as_std().get_args().map(|a| a.to_str().unwrap()).collect(); + let args: Vec<_> = cmd + .as_std() + .get_args() + .map(|a| a.to_str().unwrap()) + .collect(); assert_eq!(args, vec!["build"]); } @@ -329,7 +341,11 @@ mod tests { let cmd = step.build_command(); let prog = cmd.as_std().get_program().to_str().unwrap(); assert_eq!(prog, "rustup"); - let args: Vec<_> = cmd.as_std().get_args().map(|a| a.to_str().unwrap()).collect(); + let args: Vec<_> = cmd + .as_std() + .get_args() + .map(|a| a.to_str().unwrap()) + .collect(); assert_eq!(args, vec!["run", "nightly", "cargo", "test"]); } @@ -340,8 +356,15 @@ mod tests { config.features = vec!["feat1".to_string(), "feat2".to_string()]; let step = CargoStep::new(config); let cmd = step.build_command(); - let args: Vec<_> = cmd.as_std().get_args().map(|a| a.to_str().unwrap()).collect(); - assert_eq!(args, vec!["check", "-p", "my-crate", "--features", "feat1,feat2"]); + let args: Vec<_> = cmd + .as_std() + .get_args() + .map(|a| a.to_str().unwrap()) + .collect(); + assert_eq!( + args, + vec!["check", "-p", "my-crate", "--features", "feat1,feat2"] + ); } #[test] @@ -351,8 +374,20 @@ mod tests { config.target = Some("aarch64-unknown-linux-gnu".to_string()); let step = CargoStep::new(config); let cmd = step.build_command(); - let args: Vec<_> = cmd.as_std().get_args().map(|a| a.to_str().unwrap()).collect(); - assert_eq!(args, vec!["build", "--release", "--target", "aarch64-unknown-linux-gnu"]); + let args: Vec<_> = cmd + .as_std() + .get_args() + .map(|a| a.to_str().unwrap()) + .collect(); + assert_eq!( + args, + vec![ + "build", + "--release", + "--target", + "aarch64-unknown-linux-gnu" + ] + ); } #[test] @@ -364,10 +399,23 @@ mod tests { config.extra_args = vec!["--".to_string(), "-D".to_string(), "warnings".to_string()]; let step = CargoStep::new(config); let cmd = step.build_command(); - let args: Vec<_> = cmd.as_std().get_args().map(|a| a.to_str().unwrap()).collect(); + let args: Vec<_> = cmd + .as_std() + .get_args() + .map(|a| a.to_str().unwrap()) + .collect(); assert_eq!( args, - vec!["clippy", "--all-features", "--no-default-features", "--profile", "dev", "--", "-D", "warnings"] + vec![ + "clippy", + "--all-features", + "--no-default-features", + "--profile", + "dev", + "--", + "-D", + "warnings" + ] ); } @@ -377,17 +425,29 @@ mod tests { config.extra_args = vec!["--check".to_string()]; let step = CargoStep::new(config); let cmd = step.build_command(); - let args: Vec<_> = cmd.as_std().get_args().map(|a| a.to_str().unwrap()).collect(); + let args: Vec<_> = cmd + .as_std() + .get_args() + .map(|a| a.to_str().unwrap()) + .collect(); assert_eq!(args, vec!["fmt", "--check"]); } #[test] fn build_command_publish_dry_run() { let mut config = minimal_config(CargoCommand::Publish); - config.extra_args = vec!["--dry-run".to_string(), "--registry".to_string(), "my-reg".to_string()]; + config.extra_args = vec![ + "--dry-run".to_string(), + "--registry".to_string(), + "my-reg".to_string(), + ]; let step = CargoStep::new(config); let cmd = step.build_command(); - let args: Vec<_> = cmd.as_std().get_args().map(|a| a.to_str().unwrap()).collect(); + let args: Vec<_> = cmd + .as_std() + .get_args() + .map(|a| a.to_str().unwrap()) + .collect(); assert_eq!(args, vec!["publish", "--dry-run", "--registry", "my-reg"]); } @@ -398,18 +458,27 @@ mod tests { config.release = true; let step = CargoStep::new(config); let cmd = step.build_command(); - let args: Vec<_> = cmd.as_std().get_args().map(|a| a.to_str().unwrap()).collect(); + let args: Vec<_> = cmd + .as_std() + .get_args() + .map(|a| a.to_str().unwrap()) + .collect(); assert_eq!(args, vec!["doc", "--release", "--no-deps"]); } #[test] fn build_command_env_vars() { let mut config = minimal_config(CargoCommand::Build); - config.env.insert("RUSTFLAGS".to_string(), "-D warnings".to_string()); + config + .env + .insert("RUSTFLAGS".to_string(), "-D warnings".to_string()); let step = CargoStep::new(config); let cmd = step.build_command(); let envs: Vec<_> = cmd.as_std().get_envs().collect(); - assert!(envs.iter().any(|(k, v)| *k == "RUSTFLAGS" && v == &Some("-D warnings".as_ref()))); + assert!( + envs.iter() + .any(|(k, v)| *k == "RUSTFLAGS" && v == &Some("-D warnings".as_ref())) + ); } #[test] @@ -418,14 +487,21 @@ mod tests { config.working_dir = Some("/my/project".to_string()); let step = CargoStep::new(config); let cmd = step.build_command(); - assert_eq!(cmd.as_std().get_current_dir(), Some(std::path::Path::new("/my/project"))); + assert_eq!( + cmd.as_std().get_current_dir(), + Some(std::path::Path::new("/my/project")) + ); } #[test] fn build_command_audit() { let step = CargoStep::new(minimal_config(CargoCommand::Audit)); let cmd = step.build_command(); - let args: Vec<_> = cmd.as_std().get_args().map(|a| a.to_str().unwrap()).collect(); + let args: Vec<_> = cmd + .as_std() + .get_args() + .map(|a| a.to_str().unwrap()) + .collect(); assert_eq!(args, vec!["audit"]); } @@ -435,7 +511,11 @@ mod tests { config.extra_args = vec!["check".to_string()]; let step = CargoStep::new(config); let cmd = step.build_command(); - let args: Vec<_> = cmd.as_std().get_args().map(|a| a.to_str().unwrap()).collect(); + let args: Vec<_> = cmd + .as_std() + .get_args() + .map(|a| a.to_str().unwrap()) + .collect(); assert_eq!(args, vec!["deny", "check"]); } @@ -443,7 +523,11 @@ mod tests { fn build_command_nextest() { let step = CargoStep::new(minimal_config(CargoCommand::Nextest)); let cmd = step.build_command(); - let args: Vec<_> = cmd.as_std().get_args().map(|a| a.to_str().unwrap()).collect(); + let args: Vec<_> = cmd + .as_std() + .get_args() + .map(|a| a.to_str().unwrap()) + .collect(); assert_eq!(args, vec!["nextest", "run"]); } @@ -454,25 +538,44 @@ mod tests { config.extra_args = vec!["--no-fail-fast".to_string()]; let step = CargoStep::new(config); let cmd = step.build_command(); - let args: Vec<_> = cmd.as_std().get_args().map(|a| a.to_str().unwrap()).collect(); - assert_eq!(args, vec!["nextest", "run", "--features", "feat1", "--no-fail-fast"]); + let args: Vec<_> = cmd + .as_std() + .get_args() + .map(|a| a.to_str().unwrap()) + .collect(); + assert_eq!( + args, + vec!["nextest", "run", "--features", "feat1", "--no-fail-fast"] + ); } #[test] fn build_command_llvm_cov() { let step = CargoStep::new(minimal_config(CargoCommand::LlvmCov)); let cmd = step.build_command(); - let args: Vec<_> = cmd.as_std().get_args().map(|a| a.to_str().unwrap()).collect(); + let args: Vec<_> = cmd + .as_std() + .get_args() + .map(|a| a.to_str().unwrap()) + .collect(); assert_eq!(args, vec!["llvm-cov"]); } #[test] fn build_command_llvm_cov_with_args() { let mut config = minimal_config(CargoCommand::LlvmCov); - config.extra_args = vec!["--html".to_string(), "--output-dir".to_string(), "coverage".to_string()]; + config.extra_args = vec![ + "--html".to_string(), + "--output-dir".to_string(), + "coverage".to_string(), + ]; let step = CargoStep::new(config); let cmd = step.build_command(); - let args: Vec<_> = cmd.as_std().get_args().map(|a| a.to_str().unwrap()).collect(); + let args: Vec<_> = cmd + .as_std() + .get_args() + .map(|a| a.to_str().unwrap()) + .collect(); assert_eq!(args, vec!["llvm-cov", "--html", "--output-dir", "coverage"]); } @@ -482,10 +585,24 @@ mod tests { let cmd = step.build_command(); let prog = cmd.as_std().get_program().to_str().unwrap(); assert_eq!(prog, "rustup"); - let args: Vec<_> = cmd.as_std().get_args().map(|a| a.to_str().unwrap()).collect(); + let args: Vec<_> = cmd + .as_std() + .get_args() + .map(|a| a.to_str().unwrap()) + .collect(); assert_eq!( args, - vec!["run", "nightly", "cargo", "rustdoc", "--", "-Z", "unstable-options", "--output-format", "json"] + vec![ + "run", + "nightly", + "cargo", + "rustdoc", + "--", + "-Z", + "unstable-options", + "--output-format", + "json" + ] ); } @@ -496,10 +613,27 @@ mod tests { config.extra_args = vec!["--no-deps".to_string()]; let step = CargoStep::new(config); let cmd = step.build_command(); - let args: Vec<_> = cmd.as_std().get_args().map(|a| a.to_str().unwrap()).collect(); + let args: Vec<_> = cmd + .as_std() + .get_args() + .map(|a| a.to_str().unwrap()) + .collect(); assert_eq!( args, - vec!["run", "nightly", "cargo", "rustdoc", "-p", "my-crate", "--no-deps", "--", "-Z", "unstable-options", "--output-format", "json"] + vec![ + "run", + "nightly", + "cargo", + "rustdoc", + "-p", + "my-crate", + "--no-deps", + "--", + "-Z", + "unstable-options", + "--output-format", + "json" + ] ); } @@ -509,7 +643,11 @@ mod tests { config.toolchain = Some("nightly-2024-06-01".to_string()); let step = CargoStep::new(config); let cmd = step.build_command(); - let args: Vec<_> = cmd.as_std().get_args().map(|a| a.to_str().unwrap()).collect(); + let args: Vec<_> = cmd + .as_std() + .get_args() + .map(|a| a.to_str().unwrap()) + .collect(); assert!(args.contains(&"nightly-2024-06-01")); } diff --git a/wfe-rustlang/src/rustdoc/transformer.rs b/wfe-rustlang/src/rustdoc/transformer.rs index eef3c41..2e8be0d 100644 --- a/wfe-rustlang/src/rustdoc/transformer.rs +++ b/wfe-rustlang/src/rustdoc/transformer.rs @@ -117,8 +117,7 @@ fn render_module(module_path: &str, items: &[(&Item, &str)], krate: &Crate) -> S items .iter() .find(|(item, kind)| { - *kind == "Modules" - && item.name.as_deref() == module_path.split("::").last() + *kind == "Modules" && item.name.as_deref() == module_path.split("::").last() }) .and_then(|(item, _)| item.docs.as_ref()) .map(|d| first_sentence(d)) @@ -136,8 +135,15 @@ fn render_module(module_path: &str, items: &[(&Item, &str)], krate: &Crate) -> S } let kind_order = [ - "Modules", "Structs", "Enums", "Traits", "Functions", - "Type Aliases", "Constants", "Statics", "Macros", + "Modules", + "Structs", + "Enums", + "Traits", + "Functions", + "Type Aliases", + "Constants", + "Statics", + "Macros", ]; for kind in &kind_order { @@ -266,16 +272,15 @@ fn render_signature(item: &Item, krate: &Crate) -> Option { } Some(sig) } - ItemEnum::TypeAlias(ta) => { - Some(format!("pub type {name} = {}", render_type(&ta.type_, krate))) - } - ItemEnum::Constant { type_, const_: c } => { - Some(format!( - "pub const {name}: {} = {}", - render_type(type_, krate), - c.value.as_deref().unwrap_or("...") - )) - } + ItemEnum::TypeAlias(ta) => Some(format!( + "pub type {name} = {}", + render_type(&ta.type_, krate) + )), + ItemEnum::Constant { type_, const_: c } => Some(format!( + "pub const {name}: {} = {}", + render_type(type_, krate), + c.value.as_deref().unwrap_or("...") + )), ItemEnum::Macro(_) => Some(format!("macro_rules! {name} {{ ... }}")), _ => None, } @@ -309,7 +314,11 @@ fn render_type(ty: &Type, krate: &Crate) -> String { } Type::Generic(name) => name.clone(), Type::Primitive(name) => name.clone(), - Type::BorrowedRef { lifetime, is_mutable, type_ } => { + Type::BorrowedRef { + lifetime, + is_mutable, + type_, + } => { let mut s = String::from("&"); if let Some(lt) = lifetime { s.push_str(lt); @@ -346,7 +355,12 @@ fn render_type(ty: &Type, krate: &Crate) -> String { .collect(); format!("impl {}", rendered.join(" + ")) } - Type::QualifiedPath { name, self_type, trait_, .. } => { + Type::QualifiedPath { + name, + self_type, + trait_, + .. + } => { let self_str = render_type(self_type, krate); if let Some(t) = trait_ { format!("<{self_str} as {}>::{name}", t.path) @@ -417,11 +431,17 @@ mod tests { deprecation: None, inner: ItemEnum::Function(Function { sig: FunctionSignature { - inputs: params.into_iter().map(|(n, t)| (n.to_string(), t)).collect(), + inputs: params + .into_iter() + .map(|(n, t)| (n.to_string(), t)) + .collect(), output, is_c_variadic: false, }, - generics: Generics { params: vec![], where_predicates: vec![] }, + generics: Generics { + params: vec![], + where_predicates: vec![], + }, header: FunctionHeader { is_const: false, is_unsafe: false, @@ -446,7 +466,10 @@ mod tests { deprecation: None, inner: ItemEnum::Struct(Struct { kind: StructKind::Unit, - generics: Generics { params: vec![], where_predicates: vec![] }, + generics: Generics { + params: vec![], + where_predicates: vec![], + }, impls: vec![], }), } @@ -464,7 +487,10 @@ mod tests { attrs: vec![], deprecation: None, inner: ItemEnum::Enum(Enum { - generics: Generics { params: vec![], where_predicates: vec![] }, + generics: Generics { + params: vec![], + where_predicates: vec![], + }, variants: vec![], has_stripped_variants: false, impls: vec![], @@ -488,7 +514,10 @@ mod tests { is_unsafe: false, is_dyn_compatible: true, items: vec![], - generics: Generics { params: vec![], where_predicates: vec![] }, + generics: Generics { + params: vec![], + where_predicates: vec![], + }, bounds: vec![], implementations: vec![], }), @@ -540,35 +569,57 @@ mod tests { #[test] fn render_type_tuple() { let krate = empty_crate(); - let ty = Type::Tuple(vec![Type::Primitive("u32".into()), Type::Primitive("String".into())]); + let ty = Type::Tuple(vec![ + Type::Primitive("u32".into()), + Type::Primitive("String".into()), + ]); assert_eq!(render_type(&ty, &krate), "(u32, String)"); } #[test] fn render_type_slice() { let krate = empty_crate(); - assert_eq!(render_type(&Type::Slice(Box::new(Type::Primitive("u8".into()))), &krate), "[u8]"); + assert_eq!( + render_type(&Type::Slice(Box::new(Type::Primitive("u8".into()))), &krate), + "[u8]" + ); } #[test] fn render_type_array() { let krate = empty_crate(); - let ty = Type::Array { type_: Box::new(Type::Primitive("u8".into())), len: "32".into() }; + let ty = Type::Array { + type_: Box::new(Type::Primitive("u8".into())), + len: "32".into(), + }; assert_eq!(render_type(&ty, &krate), "[u8; 32]"); } #[test] fn render_type_raw_pointer() { let krate = empty_crate(); - let ty = Type::RawPointer { is_mutable: true, type_: Box::new(Type::Primitive("u8".into())) }; + let ty = Type::RawPointer { + is_mutable: true, + type_: Box::new(Type::Primitive("u8".into())), + }; assert_eq!(render_type(&ty, &krate), "*mut u8"); } #[test] fn render_function_signature() { let krate = empty_crate(); - let item = make_function("add", vec![("a", Type::Primitive("u32".into())), ("b", Type::Primitive("u32".into()))], Some(Type::Primitive("u32".into()))); - assert_eq!(render_signature(&item, &krate).unwrap(), "fn add(a: u32, b: u32) -> u32"); + let item = make_function( + "add", + vec![ + ("a", Type::Primitive("u32".into())), + ("b", Type::Primitive("u32".into())), + ], + Some(Type::Primitive("u32".into())), + ); + assert_eq!( + render_signature(&item, &krate).unwrap(), + "fn add(a: u32, b: u32) -> u32" + ); } #[test] @@ -581,25 +632,51 @@ mod tests { #[test] fn render_struct_signature() { let krate = empty_crate(); - assert_eq!(render_signature(&make_struct("MyStruct"), &krate).unwrap(), "pub struct MyStruct;"); + assert_eq!( + render_signature(&make_struct("MyStruct"), &krate).unwrap(), + "pub struct MyStruct;" + ); } #[test] fn render_enum_signature() { let krate = empty_crate(); - assert_eq!(render_signature(&make_enum("Color"), &krate).unwrap(), "pub enum Color { }"); + assert_eq!( + render_signature(&make_enum("Color"), &krate).unwrap(), + "pub enum Color { }" + ); } #[test] fn render_trait_signature() { let krate = empty_crate(); - assert_eq!(render_signature(&make_trait("Drawable"), &krate).unwrap(), "pub trait Drawable"); + assert_eq!( + render_signature(&make_trait("Drawable"), &krate).unwrap(), + "pub trait Drawable" + ); } #[test] fn item_kind_labels() { - assert_eq!(item_kind_label(&ItemEnum::Module(Module { is_crate: false, items: vec![], is_stripped: false })), Some("Modules")); - assert_eq!(item_kind_label(&ItemEnum::Struct(Struct { kind: StructKind::Unit, generics: Generics { params: vec![], where_predicates: vec![] }, impls: vec![] })), Some("Structs")); + assert_eq!( + item_kind_label(&ItemEnum::Module(Module { + is_crate: false, + items: vec![], + is_stripped: false + })), + Some("Modules") + ); + assert_eq!( + item_kind_label(&ItemEnum::Struct(Struct { + kind: StructKind::Unit, + generics: Generics { + params: vec![], + where_predicates: vec![] + }, + impls: vec![] + })), + Some("Structs") + ); } #[test] @@ -613,7 +690,14 @@ mod tests { let func = make_function("hello", vec![], None); let id = Id(1); krate.index.insert(id.clone(), func); - krate.paths.insert(id, ItemSummary { crate_id: 0, path: vec!["my_crate".into(), "hello".into()], kind: ItemKind::Function }); + krate.paths.insert( + id, + ItemSummary { + crate_id: 0, + path: vec!["my_crate".into(), "hello".into()], + kind: ItemKind::Function, + }, + ); let files = transform_to_mdx(&krate); assert_eq!(files.len(), 1); @@ -628,11 +712,25 @@ mod tests { let mut krate = empty_crate(); let func = make_function("do_thing", vec![], None); krate.index.insert(Id(1), func); - krate.paths.insert(Id(1), ItemSummary { crate_id: 0, path: vec!["mc".into(), "do_thing".into()], kind: ItemKind::Function }); + krate.paths.insert( + Id(1), + ItemSummary { + crate_id: 0, + path: vec!["mc".into(), "do_thing".into()], + kind: ItemKind::Function, + }, + ); let st = make_struct("Widget"); krate.index.insert(Id(2), st); - krate.paths.insert(Id(2), ItemSummary { crate_id: 0, path: vec!["mc".into(), "Widget".into()], kind: ItemKind::Struct }); + krate.paths.insert( + Id(2), + ItemSummary { + crate_id: 0, + path: vec!["mc".into(), "Widget".into()], + kind: ItemKind::Struct, + }, + ); let files = transform_to_mdx(&krate); assert_eq!(files.len(), 1); @@ -654,7 +752,11 @@ mod tests { links: HashMap::new(), attrs: vec![], deprecation: None, - inner: ItemEnum::Module(Module { is_crate: true, items: vec![Id(1)], is_stripped: false }), + inner: ItemEnum::Module(Module { + is_crate: true, + items: vec![Id(1)], + is_stripped: false, + }), }; krate.root = Id(0); krate.index.insert(Id(0), root_module); @@ -662,12 +764,23 @@ mod tests { // Add a function so the module generates a file. let func = make_function("f", vec![], None); krate.index.insert(Id(1), func); - krate.paths.insert(Id(1), ItemSummary { crate_id: 0, path: vec!["f".into()], kind: ItemKind::Function }); + krate.paths.insert( + Id(1), + ItemSummary { + crate_id: 0, + path: vec!["f".into()], + kind: ItemKind::Function, + }, + ); let files = transform_to_mdx(&krate); // The root module's description in frontmatter should have escaped quotes. let index = files.iter().find(|f| f.path == "index.mdx").unwrap(); - assert!(index.content.contains("\\\"quoted\\\""), "content: {}", index.content); + assert!( + index.content.contains("\\\"quoted\\\""), + "content: {}", + index.content + ); } #[test] @@ -677,7 +790,9 @@ mod tests { path: "Option".into(), id: Id(99), args: Some(Box::new(rustdoc_types::GenericArgs::AngleBracketed { - args: vec![rustdoc_types::GenericArg::Type(Type::Primitive("u32".into()))], + args: vec![rustdoc_types::GenericArg::Type(Type::Primitive( + "u32".into(), + ))], constraints: vec![], })), }); @@ -687,13 +802,15 @@ mod tests { #[test] fn render_type_impl_trait() { let krate = empty_crate(); - let ty = Type::ImplTrait(vec![ - rustdoc_types::GenericBound::TraitBound { - trait_: rustdoc_types::Path { path: "Display".into(), id: Id(99), args: None }, - generic_params: vec![], - modifier: rustdoc_types::TraitBoundModifier::None, + let ty = Type::ImplTrait(vec![rustdoc_types::GenericBound::TraitBound { + trait_: rustdoc_types::Path { + path: "Display".into(), + id: Id(99), + args: None, }, - ]); + generic_params: vec![], + modifier: rustdoc_types::TraitBoundModifier::None, + }]); assert_eq!(render_type(&ty, &krate), "impl Display"); } @@ -702,7 +819,11 @@ mod tests { let krate = empty_crate(); let ty = Type::DynTrait(rustdoc_types::DynTrait { traits: vec![rustdoc_types::PolyTrait { - trait_: rustdoc_types::Path { path: "Error".into(), id: Id(99), args: None }, + trait_: rustdoc_types::Path { + path: "Error".into(), + id: Id(99), + args: None, + }, generic_params: vec![], }], lifetime: None, @@ -720,7 +841,12 @@ mod tests { is_c_variadic: false, }, generic_params: vec![], - header: FunctionHeader { is_const: false, is_unsafe: false, is_async: false, abi: Abi::Rust }, + header: FunctionHeader { + is_const: false, + is_unsafe: false, + is_async: false, + abi: Abi::Rust, + }, })); assert_eq!(render_type(&ty, &krate), "fn(u32) -> bool"); } @@ -728,7 +854,10 @@ mod tests { #[test] fn render_type_const_pointer() { let krate = empty_crate(); - let ty = Type::RawPointer { is_mutable: false, type_: Box::new(Type::Primitive("u8".into())) }; + let ty = Type::RawPointer { + is_mutable: false, + type_: Box::new(Type::Primitive("u8".into())), + }; assert_eq!(render_type(&ty, &krate), "*const u8"); } @@ -743,9 +872,16 @@ mod tests { let krate = empty_crate(); let ty = Type::QualifiedPath { name: "Item".into(), - args: Box::new(rustdoc_types::GenericArgs::AngleBracketed { args: vec![], constraints: vec![] }), + args: Box::new(rustdoc_types::GenericArgs::AngleBracketed { + args: vec![], + constraints: vec![], + }), self_type: Box::new(Type::Generic("T".into())), - trait_: Some(rustdoc_types::Path { path: "Iterator".into(), id: Id(99), args: None }), + trait_: Some(rustdoc_types::Path { + path: "Iterator".into(), + id: Id(99), + args: None, + }), }; assert_eq!(render_type(&ty, &krate), "::Item"); } @@ -753,74 +889,137 @@ mod tests { #[test] fn item_kind_label_all_variants() { // Test the remaining untested variants - assert_eq!(item_kind_label(&ItemEnum::Enum(Enum { - generics: Generics { params: vec![], where_predicates: vec![] }, - variants: vec![], has_stripped_variants: false, impls: vec![], - })), Some("Enums")); - assert_eq!(item_kind_label(&ItemEnum::Trait(Trait { - is_auto: false, is_unsafe: false, is_dyn_compatible: true, - items: vec![], generics: Generics { params: vec![], where_predicates: vec![] }, - bounds: vec![], implementations: vec![], - })), Some("Traits")); + assert_eq!( + item_kind_label(&ItemEnum::Enum(Enum { + generics: Generics { + params: vec![], + where_predicates: vec![] + }, + variants: vec![], + has_stripped_variants: false, + impls: vec![], + })), + Some("Enums") + ); + assert_eq!( + item_kind_label(&ItemEnum::Trait(Trait { + is_auto: false, + is_unsafe: false, + is_dyn_compatible: true, + items: vec![], + generics: Generics { + params: vec![], + where_predicates: vec![] + }, + bounds: vec![], + implementations: vec![], + })), + Some("Traits") + ); assert_eq!(item_kind_label(&ItemEnum::Macro("".into())), Some("Macros")); - assert_eq!(item_kind_label(&ItemEnum::Static(rustdoc_types::Static { - type_: Type::Primitive("u32".into()), - is_mutable: false, - is_unsafe: false, - expr: String::new(), - })), Some("Statics")); + assert_eq!( + item_kind_label(&ItemEnum::Static(rustdoc_types::Static { + type_: Type::Primitive("u32".into()), + is_mutable: false, + is_unsafe: false, + expr: String::new(), + })), + Some("Statics") + ); // Impl blocks should be skipped - assert_eq!(item_kind_label(&ItemEnum::Impl(rustdoc_types::Impl { - is_unsafe: false, generics: Generics { params: vec![], where_predicates: vec![] }, - provided_trait_methods: vec![], trait_: None, for_: Type::Primitive("u32".into()), - items: vec![], is_negative: false, is_synthetic: false, - blanket_impl: None, - })), None); + assert_eq!( + item_kind_label(&ItemEnum::Impl(rustdoc_types::Impl { + is_unsafe: false, + generics: Generics { + params: vec![], + where_predicates: vec![] + }, + provided_trait_methods: vec![], + trait_: None, + for_: Type::Primitive("u32".into()), + items: vec![], + is_negative: false, + is_synthetic: false, + blanket_impl: None, + })), + None + ); } #[test] fn render_constant_signature() { let krate = empty_crate(); let item = Item { - id: Id(5), crate_id: 0, - name: Some("MAX_SIZE".into()), span: None, - visibility: Visibility::Public, docs: None, - links: HashMap::new(), attrs: vec![], deprecation: None, + id: Id(5), + crate_id: 0, + name: Some("MAX_SIZE".into()), + span: None, + visibility: Visibility::Public, + docs: None, + links: HashMap::new(), + attrs: vec![], + deprecation: None, inner: ItemEnum::Constant { type_: Type::Primitive("usize".into()), - const_: rustdoc_types::Constant { expr: "1024".into(), value: Some("1024".into()), is_literal: true }, + const_: rustdoc_types::Constant { + expr: "1024".into(), + value: Some("1024".into()), + is_literal: true, + }, }, }; - assert_eq!(render_signature(&item, &krate).unwrap(), "pub const MAX_SIZE: usize = 1024"); + assert_eq!( + render_signature(&item, &krate).unwrap(), + "pub const MAX_SIZE: usize = 1024" + ); } #[test] fn render_type_alias_signature() { let krate = empty_crate(); let item = Item { - id: Id(6), crate_id: 0, - name: Some("Result".into()), span: None, - visibility: Visibility::Public, docs: None, - links: HashMap::new(), attrs: vec![], deprecation: None, + id: Id(6), + crate_id: 0, + name: Some("Result".into()), + span: None, + visibility: Visibility::Public, + docs: None, + links: HashMap::new(), + attrs: vec![], + deprecation: None, inner: ItemEnum::TypeAlias(rustdoc_types::TypeAlias { type_: Type::Primitive("u32".into()), - generics: Generics { params: vec![], where_predicates: vec![] }, + generics: Generics { + params: vec![], + where_predicates: vec![], + }, }), }; - assert_eq!(render_signature(&item, &krate).unwrap(), "pub type Result = u32"); + assert_eq!( + render_signature(&item, &krate).unwrap(), + "pub type Result = u32" + ); } #[test] fn render_macro_signature() { let krate = empty_crate(); let item = Item { - id: Id(7), crate_id: 0, - name: Some("my_macro".into()), span: None, - visibility: Visibility::Public, docs: None, - links: HashMap::new(), attrs: vec![], deprecation: None, + id: Id(7), + crate_id: 0, + name: Some("my_macro".into()), + span: None, + visibility: Visibility::Public, + docs: None, + links: HashMap::new(), + attrs: vec![], + deprecation: None, inner: ItemEnum::Macro("macro body".into()), }; - assert_eq!(render_signature(&item, &krate).unwrap(), "macro_rules! my_macro { ... }"); + assert_eq!( + render_signature(&item, &krate).unwrap(), + "macro_rules! my_macro { ... }" + ); } #[test] @@ -839,9 +1038,15 @@ mod tests { #[test] fn write_mdx_files_creates_directories() { let tmp = tempfile::tempdir().unwrap(); - let files = vec![MdxFile { path: "nested/module.mdx".into(), content: "# Test\n".into() }]; + let files = vec![MdxFile { + path: "nested/module.mdx".into(), + content: "# Test\n".into(), + }]; write_mdx_files(&files, tmp.path()).unwrap(); assert!(tmp.path().join("nested/module.mdx").exists()); - assert_eq!(std::fs::read_to_string(tmp.path().join("nested/module.mdx")).unwrap(), "# Test\n"); + assert_eq!( + std::fs::read_to_string(tmp.path().join("nested/module.mdx")).unwrap(), + "# Test\n" + ); } } diff --git a/wfe-rustlang/src/rustup/config.rs b/wfe-rustlang/src/rustup/config.rs index b3c80bc..01c6884 100644 --- a/wfe-rustlang/src/rustup/config.rs +++ b/wfe-rustlang/src/rustup/config.rs @@ -60,7 +60,10 @@ mod tests { #[test] fn command_as_str() { assert_eq!(RustupCommand::Install.as_str(), "install"); - assert_eq!(RustupCommand::ToolchainInstall.as_str(), "toolchain-install"); + assert_eq!( + RustupCommand::ToolchainInstall.as_str(), + "toolchain-install" + ); assert_eq!(RustupCommand::ComponentAdd.as_str(), "component-add"); assert_eq!(RustupCommand::TargetAdd.as_str(), "target-add"); } @@ -118,7 +121,11 @@ mod tests { let config = RustupConfig { command: RustupCommand::ComponentAdd, toolchain: Some("nightly".to_string()), - components: vec!["clippy".to_string(), "rustfmt".to_string(), "rust-src".to_string()], + components: vec![ + "clippy".to_string(), + "rustfmt".to_string(), + "rust-src".to_string(), + ], targets: vec![], profile: None, default_toolchain: None, @@ -138,7 +145,10 @@ mod tests { command: RustupCommand::TargetAdd, toolchain: Some("stable".to_string()), components: vec![], - targets: vec!["wasm32-unknown-unknown".to_string(), "aarch64-linux-android".to_string()], + targets: vec![ + "wasm32-unknown-unknown".to_string(), + "aarch64-linux-android".to_string(), + ], profile: None, default_toolchain: None, extra_args: vec![], @@ -147,7 +157,10 @@ mod tests { let json = serde_json::to_string(&config).unwrap(); let de: RustupConfig = serde_json::from_str(&json).unwrap(); assert_eq!(de.command, RustupCommand::TargetAdd); - assert_eq!(de.targets, vec!["wasm32-unknown-unknown", "aarch64-linux-android"]); + assert_eq!( + de.targets, + vec!["wasm32-unknown-unknown", "aarch64-linux-android"] + ); } #[test] diff --git a/wfe-rustlang/src/rustup/step.rs b/wfe-rustlang/src/rustup/step.rs index 721f7ca..729636c 100644 --- a/wfe-rustlang/src/rustup/step.rs +++ b/wfe-rustlang/src/rustup/step.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; +use wfe_core::WfeError; use wfe_core::models::ExecutionResult; use wfe_core::traits::step::{StepBody, StepExecutionContext}; -use wfe_core::WfeError; use crate::rustup::config::{RustupCommand, RustupConfig}; @@ -26,7 +26,8 @@ impl RustupStep { fn build_install_command(&self) -> tokio::process::Command { let mut cmd = tokio::process::Command::new("sh"); // Pipe rustup-init through sh with non-interactive flag. - let mut script = "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y".to_string(); + let mut script = + "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y".to_string(); if let Some(ref profile) = self.config.profile { script.push_str(&format!(" --profile {profile}")); @@ -112,7 +113,10 @@ impl RustupStep { #[async_trait] impl StepBody for RustupStep { - async fn run(&mut self, context: &StepExecutionContext<'_>) -> wfe_core::Result { + async fn run( + &mut self, + context: &StepExecutionContext<'_>, + ) -> wfe_core::Result { let step_name = context.step.name.as_deref().unwrap_or("unknown"); let subcmd = self.config.command.as_str(); @@ -133,9 +137,9 @@ impl StepBody for RustupStep { } } } else { - cmd.output() - .await - .map_err(|e| WfeError::StepExecution(format!("Failed to spawn rustup {subcmd}: {e}")))? + cmd.output().await.map_err(|e| { + WfeError::StepExecution(format!("Failed to spawn rustup {subcmd}: {e}")) + })? }; let stdout = String::from_utf8_lossy(&output.stdout).to_string(); @@ -189,7 +193,11 @@ mod tests { let cmd = step.build_command(); let prog = cmd.as_std().get_program().to_str().unwrap(); assert_eq!(prog, "sh"); - let args: Vec<_> = cmd.as_std().get_args().map(|a| a.to_str().unwrap()).collect(); + let args: Vec<_> = cmd + .as_std() + .get_args() + .map(|a| a.to_str().unwrap()) + .collect(); assert_eq!(args[0], "-c"); assert!(args[1].contains("rustup.rs")); assert!(args[1].contains("-y")); @@ -202,7 +210,11 @@ mod tests { config.default_toolchain = Some("nightly".to_string()); let step = RustupStep::new(config); let cmd = step.build_command(); - let args: Vec<_> = cmd.as_std().get_args().map(|a| a.to_str().unwrap()).collect(); + let args: Vec<_> = cmd + .as_std() + .get_args() + .map(|a| a.to_str().unwrap()) + .collect(); assert!(args[1].contains("--profile minimal")); assert!(args[1].contains("--default-toolchain nightly")); } @@ -213,7 +225,11 @@ mod tests { config.extra_args = vec!["--no-modify-path".to_string()]; let step = RustupStep::new(config); let cmd = step.build_command(); - let args: Vec<_> = cmd.as_std().get_args().map(|a| a.to_str().unwrap()).collect(); + let args: Vec<_> = cmd + .as_std() + .get_args() + .map(|a| a.to_str().unwrap()) + .collect(); assert!(args[1].contains("--no-modify-path")); } @@ -233,8 +249,21 @@ mod tests { let cmd = step.build_command(); let prog = cmd.as_std().get_program().to_str().unwrap(); assert_eq!(prog, "rustup"); - let args: Vec<_> = cmd.as_std().get_args().map(|a| a.to_str().unwrap()).collect(); - assert_eq!(args, vec!["toolchain", "install", "nightly-2024-06-01", "--profile", "minimal"]); + let args: Vec<_> = cmd + .as_std() + .get_args() + .map(|a| a.to_str().unwrap()) + .collect(); + assert_eq!( + args, + vec![ + "toolchain", + "install", + "nightly-2024-06-01", + "--profile", + "minimal" + ] + ); } #[test] @@ -251,7 +280,11 @@ mod tests { }; let step = RustupStep::new(config); let cmd = step.build_command(); - let args: Vec<_> = cmd.as_std().get_args().map(|a| a.to_str().unwrap()).collect(); + let args: Vec<_> = cmd + .as_std() + .get_args() + .map(|a| a.to_str().unwrap()) + .collect(); assert_eq!(args, vec!["toolchain", "install", "stable", "--force"]); } @@ -271,8 +304,22 @@ mod tests { let cmd = step.build_command(); let prog = cmd.as_std().get_program().to_str().unwrap(); assert_eq!(prog, "rustup"); - let args: Vec<_> = cmd.as_std().get_args().map(|a| a.to_str().unwrap()).collect(); - assert_eq!(args, vec!["component", "add", "clippy", "rustfmt", "--toolchain", "nightly"]); + let args: Vec<_> = cmd + .as_std() + .get_args() + .map(|a| a.to_str().unwrap()) + .collect(); + assert_eq!( + args, + vec![ + "component", + "add", + "clippy", + "rustfmt", + "--toolchain", + "nightly" + ] + ); } #[test] @@ -289,7 +336,11 @@ mod tests { }; let step = RustupStep::new(config); let cmd = step.build_command(); - let args: Vec<_> = cmd.as_std().get_args().map(|a| a.to_str().unwrap()).collect(); + let args: Vec<_> = cmd + .as_std() + .get_args() + .map(|a| a.to_str().unwrap()) + .collect(); assert_eq!(args, vec!["component", "add", "rust-src"]); } @@ -309,8 +360,21 @@ mod tests { let cmd = step.build_command(); let prog = cmd.as_std().get_program().to_str().unwrap(); assert_eq!(prog, "rustup"); - let args: Vec<_> = cmd.as_std().get_args().map(|a| a.to_str().unwrap()).collect(); - assert_eq!(args, vec!["target", "add", "wasm32-unknown-unknown", "--toolchain", "stable"]); + let args: Vec<_> = cmd + .as_std() + .get_args() + .map(|a| a.to_str().unwrap()) + .collect(); + assert_eq!( + args, + vec![ + "target", + "add", + "wasm32-unknown-unknown", + "--toolchain", + "stable" + ] + ); } #[test] @@ -330,8 +394,20 @@ mod tests { }; let step = RustupStep::new(config); let cmd = step.build_command(); - let args: Vec<_> = cmd.as_std().get_args().map(|a| a.to_str().unwrap()).collect(); - assert_eq!(args, vec!["target", "add", "wasm32-unknown-unknown", "aarch64-linux-android"]); + let args: Vec<_> = cmd + .as_std() + .get_args() + .map(|a| a.to_str().unwrap()) + .collect(); + assert_eq!( + args, + vec![ + "target", + "add", + "wasm32-unknown-unknown", + "aarch64-linux-android" + ] + ); } #[test] @@ -348,10 +424,21 @@ mod tests { }; let step = RustupStep::new(config); let cmd = step.build_command(); - let args: Vec<_> = cmd.as_std().get_args().map(|a| a.to_str().unwrap()).collect(); + let args: Vec<_> = cmd + .as_std() + .get_args() + .map(|a| a.to_str().unwrap()) + .collect(); assert_eq!( args, - vec!["target", "add", "x86_64-unknown-linux-musl", "--toolchain", "nightly", "--force"] + vec![ + "target", + "add", + "x86_64-unknown-linux-musl", + "--toolchain", + "nightly", + "--force" + ] ); } } diff --git a/wfe-server-protos/build.rs b/wfe-server-protos/build.rs index 21ea9c6..673aad8 100644 --- a/wfe-server-protos/build.rs +++ b/wfe-server-protos/build.rs @@ -13,11 +13,7 @@ fn main() -> Result<(), Box> { .build_server(true) .build_client(true) .file_descriptor_set_path(&descriptor_path) - .compile_with_config( - prost_config, - &proto_files, - &["proto"], - )?; + .compile_with_config(prost_config, &proto_files, &["proto"])?; Ok(()) } diff --git a/wfe-server-protos/src/lib.rs b/wfe-server-protos/src/lib.rs index cb62534..757b367 100644 --- a/wfe-server-protos/src/lib.rs +++ b/wfe-server-protos/src/lib.rs @@ -17,4 +17,5 @@ pub use prost_types; pub use tonic; /// Encoded file descriptor set for gRPC reflection. -pub const FILE_DESCRIPTOR_SET: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/wfe_descriptor.bin")); +pub const FILE_DESCRIPTOR_SET: &[u8] = + include_bytes!(concat!(env!("OUT_DIR"), "/wfe_descriptor.bin")); diff --git a/wfe-server/src/auth.rs b/wfe-server/src/auth.rs index b78cff1..bc35484 100644 --- a/wfe-server/src/auth.rs +++ b/wfe-server/src/auth.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; +use jsonwebtoken::{Algorithm, DecodingKey, Validation, decode}; use serde::Deserialize; use tokio::sync::RwLock; use tonic::{Request, Status}; @@ -99,7 +99,10 @@ impl AuthState { let resp: JwksResponse = reqwest::get(uri).await?.json().await?; let mut cache = self.jwks.write().await; *cache = Some(JwksCache { keys: resp.keys }); - tracing::debug!(key_count = cache.as_ref().unwrap().keys.len(), "JWKS refreshed"); + tracing::debug!( + key_count = cache.as_ref().unwrap().keys.len(), + "JWKS refreshed" + ); Ok(()) } @@ -128,7 +131,9 @@ impl AuthState { /// Validate a JWT against the cached JWKS (synchronous — for use in interceptors). /// Shared logic used by both `check()` and `make_interceptor()`. fn validate_jwt_cached(&self, token: &str) -> Result<(), Status> { - let cache = self.jwks.try_read() + let cache = self + .jwks + .try_read() .map_err(|_| Status::unavailable("JWKS refresh in progress"))?; let jwks = cache .as_ref() @@ -228,9 +233,7 @@ fn extract_bearer_token(request: &Request) -> Result<&str, Status> { } /// Map JWK key algorithm to jsonwebtoken Algorithm. -fn key_algorithm_to_jwt_algorithm( - ka: jsonwebtoken::jwk::KeyAlgorithm, -) -> Option { +fn key_algorithm_to_jwt_algorithm(ka: jsonwebtoken::jwk::KeyAlgorithm) -> Option { use jsonwebtoken::jwk::KeyAlgorithm as KA; match ka { KA::RS256 => Some(Algorithm::RS256), @@ -473,7 +476,7 @@ mod tests { issuer: &str, audience: Option<&str>, ) -> (Vec, String) { - use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; + use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD}; use rsa::RsaPrivateKey; let mut rng = rand::thread_rng(); @@ -498,8 +501,7 @@ mod tests { let pem = private_key .to_pkcs1_pem(rsa::pkcs1::LineEnding::LF) .unwrap(); - let encoding_key = - jsonwebtoken::EncodingKey::from_rsa_pem(pem.as_bytes()).unwrap(); + let encoding_key = jsonwebtoken::EncodingKey::from_rsa_pem(pem.as_bytes()).unwrap(); let mut header = jsonwebtoken::Header::new(jsonwebtoken::Algorithm::RS256); header.kid = Some("test-key-1".to_string()); @@ -684,9 +686,18 @@ mod tests { #[test] fn key_algorithm_mapping() { use jsonwebtoken::jwk::KeyAlgorithm as KA; - assert_eq!(key_algorithm_to_jwt_algorithm(KA::RS256), Some(Algorithm::RS256)); - assert_eq!(key_algorithm_to_jwt_algorithm(KA::ES256), Some(Algorithm::ES256)); - assert_eq!(key_algorithm_to_jwt_algorithm(KA::EdDSA), Some(Algorithm::EdDSA)); + assert_eq!( + key_algorithm_to_jwt_algorithm(KA::RS256), + Some(Algorithm::RS256) + ); + assert_eq!( + key_algorithm_to_jwt_algorithm(KA::ES256), + Some(Algorithm::ES256) + ); + assert_eq!( + key_algorithm_to_jwt_algorithm(KA::EdDSA), + Some(Algorithm::EdDSA) + ); // HS256 should be rejected (symmetric algorithm). assert_eq!(key_algorithm_to_jwt_algorithm(KA::HS256), None); assert_eq!(key_algorithm_to_jwt_algorithm(KA::HS384), None); diff --git a/wfe-server/src/config.rs b/wfe-server/src/config.rs index ff4275a..81b9278 100644 --- a/wfe-server/src/config.rs +++ b/wfe-server/src/config.rs @@ -174,10 +174,7 @@ pub fn load(cli: &Cli) -> ServerConfig { // Persistence override. if let Some(ref backend) = cli.persistence { - let url = cli - .db_url - .clone() - .unwrap_or_else(|| "wfe.db".to_string()); + let url = cli.db_url.clone().unwrap_or_else(|| "wfe.db".to_string()); config.persistence = match backend.as_str() { "postgres" => PersistenceConfig::Postgres { url }, _ => PersistenceConfig::Sqlite { path: url }, @@ -231,7 +228,10 @@ mod tests { let config = ServerConfig::default(); assert_eq!(config.grpc_addr, "0.0.0.0:50051".parse().unwrap()); assert_eq!(config.http_addr, "0.0.0.0:8080".parse().unwrap()); - assert!(matches!(config.persistence, PersistenceConfig::Sqlite { .. })); + assert!(matches!( + config.persistence, + PersistenceConfig::Sqlite { .. } + )); assert!(matches!(config.queue, QueueConfig::InMemory)); assert!(config.search.is_none()); assert!(config.auth.tokens.is_empty()); @@ -270,11 +270,17 @@ version = 1 "#; let config: ServerConfig = toml::from_str(toml).unwrap(); assert_eq!(config.grpc_addr, "127.0.0.1:9090".parse().unwrap()); - assert!(matches!(config.persistence, PersistenceConfig::Postgres { .. })); + assert!(matches!( + config.persistence, + PersistenceConfig::Postgres { .. } + )); assert!(matches!(config.queue, QueueConfig::Valkey { .. })); assert!(config.search.is_some()); assert_eq!(config.auth.tokens.len(), 2); - assert_eq!(config.auth.webhook_secrets.get("github").unwrap(), "mysecret"); + assert_eq!( + config.auth.webhook_secrets.get("github").unwrap(), + "mysecret" + ); assert_eq!(config.webhook.triggers.len(), 1); assert_eq!(config.webhook.triggers[0].workflow_id, "ci"); } @@ -295,8 +301,12 @@ version = 1 }; let config = load(&cli); assert_eq!(config.grpc_addr, "127.0.0.1:9999".parse().unwrap()); - assert!(matches!(config.persistence, PersistenceConfig::Postgres { ref url } if url == "postgres://db/wfe")); - assert!(matches!(config.queue, QueueConfig::Valkey { ref url } if url == "redis://valkey:6379")); + assert!( + matches!(config.persistence, PersistenceConfig::Postgres { ref url } if url == "postgres://db/wfe") + ); + assert!( + matches!(config.queue, QueueConfig::Valkey { ref url } if url == "redis://valkey:6379") + ); assert_eq!(config.search.unwrap().url, "http://os:9200"); assert_eq!(config.workflows_dir.unwrap(), PathBuf::from("/workflows")); assert_eq!(config.auth.tokens, vec!["tok1", "tok2"]); @@ -317,7 +327,10 @@ version = 1 auth_tokens: None, }; let config = load(&cli); - assert!(matches!(config.persistence, PersistenceConfig::Postgres { .. })); + assert!(matches!( + config.persistence, + PersistenceConfig::Postgres { .. } + )); } // ── Security regression tests ── @@ -358,6 +371,9 @@ commit = "$.head_commit.id" "#; let config: WebhookConfig = toml::from_str(toml).unwrap(); assert_eq!(config.triggers[0].data_mapping.len(), 2); - assert_eq!(config.triggers[0].data_mapping["repo"], "$.repository.full_name"); + assert_eq!( + config.triggers[0].data_mapping["repo"], + "$.repository.full_name" + ); } } diff --git a/wfe-server/src/lifecycle_bus.rs b/wfe-server/src/lifecycle_bus.rs index 9707bab..aa58416 100644 --- a/wfe-server/src/lifecycle_bus.rs +++ b/wfe-server/src/lifecycle_bus.rs @@ -52,9 +52,14 @@ mod tests { let mut rx1 = bus.subscribe(); let mut rx2 = bus.subscribe(); - bus.publish(LifecycleEvent::new("wf-1", "def-1", 1, LifecycleEventType::Completed)) - .await - .unwrap(); + bus.publish(LifecycleEvent::new( + "wf-1", + "def-1", + 1, + LifecycleEventType::Completed, + )) + .await + .unwrap(); let e1 = rx1.recv().await.unwrap(); let e2 = rx2.recv().await.unwrap(); @@ -66,9 +71,14 @@ mod tests { async fn no_subscribers_does_not_error() { let bus = BroadcastLifecyclePublisher::new(16); // No subscribers — should not panic. - bus.publish(LifecycleEvent::new("wf-1", "def-1", 1, LifecycleEventType::Started)) - .await - .unwrap(); + bus.publish(LifecycleEvent::new( + "wf-1", + "def-1", + 1, + LifecycleEventType::Started, + )) + .await + .unwrap(); } #[tokio::test] diff --git a/wfe-server/src/log_search.rs b/wfe-server/src/log_search.rs index 4d6dcc6..71ebc00 100644 --- a/wfe-server/src/log_search.rs +++ b/wfe-server/src/log_search.rs @@ -304,8 +304,8 @@ mod tests { // ── OpenSearch integration tests ──────────────────────────────── fn opensearch_url() -> Option { - let url = std::env::var("WFE_SEARCH_URL") - .unwrap_or_else(|_| "http://localhost:9200".to_string()); + let url = + std::env::var("WFE_SEARCH_URL").unwrap_or_else(|_| "http://localhost:9200".to_string()); // Quick TCP probe to check if OpenSearch is reachable. let addr = url .strip_prefix("http://") @@ -340,10 +340,7 @@ mod tests { /// Delete the test index to start clean. async fn cleanup_index(url: &str) { let client = reqwest::Client::new(); - let _ = client - .delete(format!("{url}/{LOG_INDEX}")) - .send() - .await; + let _ = client.delete(format!("{url}/{LOG_INDEX}")).send().await; } #[tokio::test] @@ -375,18 +372,37 @@ mod tests { index.ensure_index().await.unwrap(); // Index some log chunks. - let chunk = make_test_chunk("wf-search-1", "build", LogStreamType::Stdout, "compiling wfe-core v1.5.0"); + let chunk = make_test_chunk( + "wf-search-1", + "build", + LogStreamType::Stdout, + "compiling wfe-core v1.5.0", + ); index.index_chunk(&chunk).await.unwrap(); - let chunk = make_test_chunk("wf-search-1", "build", LogStreamType::Stderr, "warning: unused variable"); + let chunk = make_test_chunk( + "wf-search-1", + "build", + LogStreamType::Stderr, + "warning: unused variable", + ); index.index_chunk(&chunk).await.unwrap(); - let chunk = make_test_chunk("wf-search-1", "test", LogStreamType::Stdout, "test result: ok. 79 passed"); + let chunk = make_test_chunk( + "wf-search-1", + "test", + LogStreamType::Stdout, + "test result: ok. 79 passed", + ); index.index_chunk(&chunk).await.unwrap(); // OpenSearch needs a refresh to make docs searchable. let client = reqwest::Client::new(); - client.post(format!("{url}/{LOG_INDEX}/_refresh")).send().await.unwrap(); + client + .post(format!("{url}/{LOG_INDEX}/_refresh")) + .send() + .await + .unwrap(); // Search by text. let (results, total) = index @@ -456,12 +472,21 @@ mod tests { // Index 5 chunks. for i in 0..5 { - let chunk = make_test_chunk("wf-page", "build", LogStreamType::Stdout, &format!("line {i}")); + let chunk = make_test_chunk( + "wf-page", + "build", + LogStreamType::Stdout, + &format!("line {i}"), + ); index.index_chunk(&chunk).await.unwrap(); } let client = reqwest::Client::new(); - client.post(format!("{url}/{LOG_INDEX}/_refresh")).send().await.unwrap(); + client + .post(format!("{url}/{LOG_INDEX}/_refresh")) + .send() + .await + .unwrap(); // Get first 2. let (results, total) = index @@ -506,11 +531,20 @@ mod tests { let index = LogSearchIndex::new(&url).unwrap(); index.ensure_index().await.unwrap(); - let chunk = make_test_chunk("wf-fields", "clippy", LogStreamType::Stderr, "error: type mismatch"); + let chunk = make_test_chunk( + "wf-fields", + "clippy", + LogStreamType::Stderr, + "error: type mismatch", + ); index.index_chunk(&chunk).await.unwrap(); let client = reqwest::Client::new(); - client.post(format!("{url}/{LOG_INDEX}/_refresh")).send().await.unwrap(); + client + .post(format!("{url}/{LOG_INDEX}/_refresh")) + .send() + .await + .unwrap(); let (results, _) = index .search("type mismatch", None, None, None, 0, 10) diff --git a/wfe-server/src/log_store.rs b/wfe-server/src/log_store.rs index 354bcc9..e095802 100644 --- a/wfe-server/src/log_store.rs +++ b/wfe-server/src/log_store.rs @@ -109,8 +109,12 @@ mod tests { #[tokio::test] async fn write_and_read_history() { let store = LogStore::new(); - store.write_chunk(make_chunk("wf-1", 0, "build", "line 1\n")).await; - store.write_chunk(make_chunk("wf-1", 0, "build", "line 2\n")).await; + store + .write_chunk(make_chunk("wf-1", 0, "build", "line 1\n")) + .await; + store + .write_chunk(make_chunk("wf-1", 0, "build", "line 2\n")) + .await; let history = store.get_history("wf-1", None); assert_eq!(history.len(), 2); @@ -121,8 +125,12 @@ mod tests { #[tokio::test] async fn history_filtered_by_step() { let store = LogStore::new(); - store.write_chunk(make_chunk("wf-1", 0, "build", "build log\n")).await; - store.write_chunk(make_chunk("wf-1", 1, "test", "test log\n")).await; + store + .write_chunk(make_chunk("wf-1", 0, "build", "build log\n")) + .await; + store + .write_chunk(make_chunk("wf-1", 1, "test", "test log\n")) + .await; let build_only = store.get_history("wf-1", Some(0)); assert_eq!(build_only.len(), 1); @@ -144,7 +152,9 @@ mod tests { let store = LogStore::new(); let mut rx = store.subscribe("wf-1"); - store.write_chunk(make_chunk("wf-1", 0, "build", "hello\n")).await; + store + .write_chunk(make_chunk("wf-1", 0, "build", "hello\n")) + .await; let received = rx.recv().await.unwrap(); assert_eq!(received.data, b"hello\n"); @@ -157,8 +167,12 @@ mod tests { let mut rx1 = store.subscribe("wf-1"); let mut rx2 = store.subscribe("wf-2"); - store.write_chunk(make_chunk("wf-1", 0, "build", "wf1 log\n")).await; - store.write_chunk(make_chunk("wf-2", 0, "test", "wf2 log\n")).await; + store + .write_chunk(make_chunk("wf-1", 0, "build", "wf1 log\n")) + .await; + store + .write_chunk(make_chunk("wf-2", 0, "test", "wf2 log\n")) + .await; let e1 = rx1.recv().await.unwrap(); assert_eq!(e1.workflow_id, "wf-1"); @@ -171,7 +185,9 @@ mod tests { async fn no_subscribers_does_not_error() { let store = LogStore::new(); // No subscribers — should not panic. - store.write_chunk(make_chunk("wf-1", 0, "build", "orphan log\n")).await; + store + .write_chunk(make_chunk("wf-1", 0, "build", "orphan log\n")) + .await; // History should still be stored. assert_eq!(store.get_history("wf-1", None).len(), 1); } @@ -182,7 +198,9 @@ mod tests { let mut rx1 = store.subscribe("wf-1"); let mut rx2 = store.subscribe("wf-1"); - store.write_chunk(make_chunk("wf-1", 0, "build", "shared\n")).await; + store + .write_chunk(make_chunk("wf-1", 0, "build", "shared\n")) + .await; let e1 = rx1.recv().await.unwrap(); let e2 = rx2.recv().await.unwrap(); diff --git a/wfe-server/src/main.rs b/wfe-server/src/main.rs index f5aa48e..eaaaf03 100644 --- a/wfe-server/src/main.rs +++ b/wfe-server/src/main.rs @@ -152,9 +152,7 @@ async fn main() -> Result<(), Box> { wfe_service = wfe_service.with_log_search(index); } let (health_reporter, health_service) = tonic_health::server::health_reporter(); - health_reporter - .set_serving::>() - .await; + health_reporter.set_serving::>().await; // 11. Build auth state. let auth_state = Arc::new(auth::AuthState::new(config.auth.clone()).await); @@ -168,13 +166,31 @@ async fn main() -> Result<(), Box> { // HIGH-08: Limit webhook payload size to 2 MB to prevent OOM DoS. let http_router = axum::Router::new() - .route("/webhooks/events", axum::routing::post(webhook::handle_generic_event)) - .route("/webhooks/github", axum::routing::post(webhook::handle_github_webhook)) - .route("/webhooks/gitea", axum::routing::post(webhook::handle_gitea_webhook)) + .route( + "/webhooks/events", + axum::routing::post(webhook::handle_generic_event), + ) + .route( + "/webhooks/github", + axum::routing::post(webhook::handle_github_webhook), + ) + .route( + "/webhooks/gitea", + axum::routing::post(webhook::handle_gitea_webhook), + ) .route("/healthz", axum::routing::get(webhook::health_check)) - .route("/schema/workflow.proto", axum::routing::get(serve_proto_schema)) - .route("/schema/workflow.json", axum::routing::get(serve_json_schema)) - .route("/schema/workflow.yaml", axum::routing::get(serve_yaml_example)) + .route( + "/schema/workflow.proto", + axum::routing::get(serve_proto_schema), + ) + .route( + "/schema/workflow.json", + axum::routing::get(serve_json_schema), + ) + .route( + "/schema/workflow.yaml", + axum::routing::get(serve_yaml_example), + ) .layer(axum::extract::DefaultBodyLimit::max(2 * 1024 * 1024)) .with_state(webhook_state); @@ -234,7 +250,10 @@ async fn load_yaml_definitions(host: &wfe::WorkflowHost, dir: &std::path::Path) for entry in entries.flatten() { let path = entry.path(); - if path.extension().is_some_and(|ext| ext == "yaml" || ext == "yml") { + if path + .extension() + .is_some_and(|ext| ext == "yaml" || ext == "yml") + { match wfe_yaml::load_workflow_from_str( &std::fs::read_to_string(&path).unwrap_or_default(), &config, @@ -261,7 +280,10 @@ async fn load_yaml_definitions(host: &wfe::WorkflowHost, dir: &std::path::Path) /// Serve the raw .proto schema file. async fn serve_proto_schema() -> impl axum::response::IntoResponse { ( - [(axum::http::header::CONTENT_TYPE, "text/plain; charset=utf-8")], + [( + axum::http::header::CONTENT_TYPE, + "text/plain; charset=utf-8", + )], include_str!("../../wfe-server-protos/proto/wfe/v1/wfe.proto"), ) } diff --git a/wfe-server/src/webhook.rs b/wfe-server/src/webhook.rs index 976f40a..d862e6d 100644 --- a/wfe-server/src/webhook.rs +++ b/wfe-server/src/webhook.rs @@ -1,10 +1,10 @@ use std::sync::Arc; +use axum::Json; use axum::body::Bytes; use axum::extract::State; use axum::http::{HeaderMap, StatusCode}; use axum::response::IntoResponse; -use axum::Json; use hmac::{Hmac, Mac}; use sha2::Sha256; @@ -107,7 +107,11 @@ pub async fn handle_github_webhook( // Publish as event (for workflows waiting on events). if let Err(e) = state .host - .publish_event(&forge_event.event_name, &forge_event.event_key, forge_event.data.clone()) + .publish_event( + &forge_event.event_name, + &forge_event.event_key, + forge_event.data.clone(), + ) .await { tracing::error!(error = %e, "failed to publish forge event"); @@ -208,7 +212,11 @@ pub async fn handle_gitea_webhook( if let Err(e) = state .host - .publish_event(&forge_event.event_name, &forge_event.event_key, forge_event.data.clone()) + .publish_event( + &forge_event.event_name, + &forge_event.event_key, + forge_event.data.clone(), + ) .await { tracing::error!(error = %e, "failed to publish forge event"); @@ -362,10 +370,7 @@ fn map_forge_event(event_type: &str, payload: &serde_json::Value) -> ForgeEvent /// Extract data fields from payload using simple JSONPath-like mapping. /// Supports `$.field.nested` syntax. -fn map_trigger_data( - trigger: &WebhookTrigger, - payload: &serde_json::Value, -) -> serde_json::Value { +fn map_trigger_data(trigger: &WebhookTrigger, payload: &serde_json::Value) -> serde_json::Value { let mut data = serde_json::Map::new(); for (key, path) in &trigger.data_mapping { if let Some(value) = resolve_json_path(payload, path) { @@ -376,7 +381,10 @@ fn map_trigger_data( } /// Resolve a simple JSONPath expression like `$.repository.full_name`. -fn resolve_json_path<'a>(value: &'a serde_json::Value, path: &str) -> Option<&'a serde_json::Value> { +fn resolve_json_path<'a>( + value: &'a serde_json::Value, + path: &str, +) -> Option<&'a serde_json::Value> { let path = path.strip_prefix("$.").unwrap_or(path); let mut current = value; for segment in path.split('.') { diff --git a/wfe-valkey/src/lifecycle.rs b/wfe-valkey/src/lifecycle.rs index eaadf8c..bfeef39 100644 --- a/wfe-valkey/src/lifecycle.rs +++ b/wfe-valkey/src/lifecycle.rs @@ -28,10 +28,7 @@ impl LifecyclePublisher for ValkeyLifecyclePublisher { let mut conn = self.conn.clone(); let json = serde_json::to_string(&event)?; - let instance_channel = format!( - "{}:lifecycle:{}", - self.prefix, event.workflow_instance_id - ); + let instance_channel = format!("{}:lifecycle:{}", self.prefix, event.workflow_instance_id); let all_channel = format!("{}:lifecycle:all", self.prefix); // Publish to the instance-specific channel. diff --git a/wfe-valkey/tests/lifecycle.rs b/wfe-valkey/tests/lifecycle.rs index a91a37b..8acb8ef 100644 --- a/wfe-valkey/tests/lifecycle.rs +++ b/wfe-valkey/tests/lifecycle.rs @@ -17,8 +17,9 @@ async fn publish_subscribe_round_trip() { } let prefix = format!("wfe_test_{}", uuid::Uuid::new_v4().simple()); - let publisher = - wfe_valkey::ValkeyLifecyclePublisher::new("redis://localhost:6379", &prefix).await.unwrap(); + let publisher = wfe_valkey::ValkeyLifecyclePublisher::new("redis://localhost:6379", &prefix) + .await + .unwrap(); let instance_id = "wf-lifecycle-test-1"; let channel = format!("{}:lifecycle:{}", prefix, instance_id); @@ -42,12 +43,7 @@ async fn publish_subscribe_round_trip() { // Small delay to ensure the subscription is active before publishing. tokio::time::sleep(Duration::from_millis(200)).await; - let event = LifecycleEvent::new( - instance_id, - "def-1", - 1, - LifecycleEventType::Started, - ); + let event = LifecycleEvent::new(instance_id, "def-1", 1, LifecycleEventType::Started); publisher.publish(event).await.unwrap(); // Wait for the message with a timeout. @@ -71,8 +67,9 @@ async fn publish_to_all_channel() { } let prefix = format!("wfe_test_{}", uuid::Uuid::new_v4().simple()); - let publisher = - wfe_valkey::ValkeyLifecyclePublisher::new("redis://localhost:6379", &prefix).await.unwrap(); + let publisher = wfe_valkey::ValkeyLifecyclePublisher::new("redis://localhost:6379", &prefix) + .await + .unwrap(); let all_channel = format!("{}:lifecycle:all", prefix); @@ -93,12 +90,7 @@ async fn publish_to_all_channel() { tokio::time::sleep(Duration::from_millis(200)).await; - let event = LifecycleEvent::new( - "wf-all-test", - "def-1", - 1, - LifecycleEventType::Completed, - ); + let event = LifecycleEvent::new("wf-all-test", "def-1", 1, LifecycleEventType::Completed); publisher.publish(event).await.unwrap(); let received = tokio::time::timeout(Duration::from_secs(5), rx.recv()) diff --git a/wfe-valkey/tests/lock.rs b/wfe-valkey/tests/lock.rs index 681a504..27ad2ef 100644 --- a/wfe-valkey/tests/lock.rs +++ b/wfe-valkey/tests/lock.rs @@ -2,7 +2,9 @@ use wfe_core::lock_suite; async fn make_provider() -> wfe_valkey::ValkeyLockProvider { let prefix = format!("wfe_test_{}", uuid::Uuid::new_v4().simple()); - wfe_valkey::ValkeyLockProvider::new("redis://localhost:6379", &prefix).await.unwrap() + wfe_valkey::ValkeyLockProvider::new("redis://localhost:6379", &prefix) + .await + .unwrap() } lock_suite!(make_provider); diff --git a/wfe-valkey/tests/queue.rs b/wfe-valkey/tests/queue.rs index 48f08e0..2286515 100644 --- a/wfe-valkey/tests/queue.rs +++ b/wfe-valkey/tests/queue.rs @@ -2,7 +2,9 @@ use wfe_core::queue_suite; async fn make_provider() -> wfe_valkey::ValkeyQueueProvider { let prefix = format!("wfe_test_{}", uuid::Uuid::new_v4().simple()); - wfe_valkey::ValkeyQueueProvider::new("redis://localhost:6379", &prefix).await.unwrap() + wfe_valkey::ValkeyQueueProvider::new("redis://localhost:6379", &prefix) + .await + .unwrap() } queue_suite!(make_provider); diff --git a/wfe-yaml/src/executors/deno/module_loader.rs b/wfe-yaml/src/executors/deno/module_loader.rs index 523bf34..9c649b9 100644 --- a/wfe-yaml/src/executors/deno/module_loader.rs +++ b/wfe-yaml/src/executors/deno/module_loader.rs @@ -96,15 +96,13 @@ impl ModuleLoader for WfeModuleLoader { // Relative or bare path — resolve against referrer. // This handles ./foo, ../foo, and /foo (absolute path on same origin, e.g. esm.sh redirects) - if specifier.starts_with("./") - || specifier.starts_with("../") - || specifier.starts_with('/') + if specifier.starts_with("./") || specifier.starts_with("../") || specifier.starts_with('/') { let base = ModuleSpecifier::parse(referrer) .map_err(|e| JsErrorBox::generic(format!("Invalid referrer '{referrer}': {e}")))?; - let resolved = base - .join(specifier) - .map_err(|e| JsErrorBox::generic(format!("Failed to resolve '{specifier}': {e}")))?; + let resolved = base.join(specifier).map_err(|e| { + JsErrorBox::generic(format!("Failed to resolve '{specifier}': {e}")) + })?; // Check permissions based on scheme. match resolved.scheme() { @@ -172,11 +170,9 @@ impl ModuleLoader for WfeModuleLoader { .map_err(|e| JsErrorBox::new("PermissionError", e.to_string()))?; } - let response = reqwest::get(&url) - .await - .map_err(|e| { - JsErrorBox::generic(format!("Failed to fetch module '{url}': {e}")) - })?; + let response = reqwest::get(&url).await.map_err(|e| { + JsErrorBox::generic(format!("Failed to fetch module '{url}': {e}")) + })?; if !response.status().is_success() { return Err(JsErrorBox::generic(format!( @@ -224,9 +220,10 @@ impl ModuleLoader for WfeModuleLoader { &specifier, None, ))), - Err(e) => ModuleLoadResponse::Sync(Err(JsErrorBox::generic( - format!("Failed to read module '{}': {e}", path.display()), - ))), + Err(e) => ModuleLoadResponse::Sync(Err(JsErrorBox::generic(format!( + "Failed to read module '{}': {e}", + path.display() + )))), } } Err(e) => ModuleLoadResponse::Sync(Err(e)), @@ -274,7 +271,11 @@ mod tests { ..Default::default() }); let result = loader - .resolve("npm:lodash@4", "ext:wfe/bootstrap.js", ResolutionKind::Import) + .resolve( + "npm:lodash@4", + "ext:wfe/bootstrap.js", + ResolutionKind::Import, + ) .unwrap(); assert_eq!(result.as_str(), "https://esm.sh/lodash@4"); } @@ -304,7 +305,12 @@ mod tests { ResolutionKind::Import, ); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("Permission denied")); + assert!( + result + .unwrap_err() + .to_string() + .contains("Permission denied") + ); } #[test] @@ -320,10 +326,12 @@ mod tests { ResolutionKind::DynamicImport, ); assert!(result.is_err()); - assert!(result - .unwrap_err() - .to_string() - .contains("Dynamic import is not allowed")); + assert!( + result + .unwrap_err() + .to_string() + .contains("Dynamic import is not allowed") + ); } #[test] @@ -361,11 +369,7 @@ mod tests { ..Default::default() }); let result = loader - .resolve( - "./helper.js", - "file:///tmp/main.js", - ResolutionKind::Import, - ) + .resolve("./helper.js", "file:///tmp/main.js", ResolutionKind::Import) .unwrap(); assert_eq!(result.as_str(), "file:///tmp/helper.js"); } diff --git a/wfe-yaml/src/executors/deno/ops/http.rs b/wfe-yaml/src/executors/deno/ops/http.rs index ec7b9e2..8ddd2a5 100644 --- a/wfe-yaml/src/executors/deno/ops/http.rs +++ b/wfe-yaml/src/executors/deno/ops/http.rs @@ -2,8 +2,8 @@ use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; -use deno_core::op2; use deno_core::OpState; +use deno_core::op2; use deno_error::JsErrorBox; use serde::{Deserialize, Serialize}; diff --git a/wfe-yaml/src/executors/deno/ops/workflow.rs b/wfe-yaml/src/executors/deno/ops/workflow.rs index de443fc..c0ecfb2 100644 --- a/wfe-yaml/src/executors/deno/ops/workflow.rs +++ b/wfe-yaml/src/executors/deno/ops/workflow.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; -use deno_core::op2; use deno_core::OpState; +use deno_core::op2; /// Workflow data available to the script via `inputs()`. pub struct WorkflowInputs { @@ -28,11 +28,7 @@ pub fn op_inputs(state: &mut OpState) -> serde_json::Value { /// Stores a key/value pair in the step outputs. #[op2] -pub fn op_output( - state: &mut OpState, - #[string] key: String, - #[serde] value: serde_json::Value, -) { +pub fn op_output(state: &mut OpState, #[string] key: String, #[serde] value: serde_json::Value) { let outputs = state.borrow_mut::(); outputs.map.insert(key, value); } @@ -56,7 +52,8 @@ pub async fn op_read_file( { let s = state.borrow(); let checker = s.borrow::(); - checker.check_read(&path) + checker + .check_read(&path) .map_err(|e| deno_error::JsErrorBox::new("PermissionError", e.to_string()))?; } tokio::fs::read_to_string(&path) @@ -66,7 +63,13 @@ pub async fn op_read_file( deno_core::extension!( wfe_ops, - ops = [op_inputs, op_output, op_log, op_read_file, super::http::op_fetch], + ops = [ + op_inputs, + op_output, + op_log, + op_read_file, + super::http::op_fetch + ], esm_entry_point = "ext:wfe/bootstrap.js", esm = ["ext:wfe/bootstrap.js" = "src/executors/deno/js/bootstrap.js"], ); diff --git a/wfe-yaml/src/executors/deno/permissions.rs b/wfe-yaml/src/executors/deno/permissions.rs index 3f50b8a..a0130b4 100644 --- a/wfe-yaml/src/executors/deno/permissions.rs +++ b/wfe-yaml/src/executors/deno/permissions.rs @@ -120,9 +120,9 @@ impl PermissionChecker { /// Detect `..` path traversal components. fn has_traversal(path: &str) -> bool { - Path::new(path).components().any(|c| { - matches!(c, std::path::Component::ParentDir) - }) + Path::new(path) + .components() + .any(|c| matches!(c, std::path::Component::ParentDir)) } } @@ -130,12 +130,7 @@ impl PermissionChecker { mod tests { use super::*; - fn perms( - net: &[&str], - read: &[&str], - write: &[&str], - env: &[&str], - ) -> PermissionChecker { + fn perms(net: &[&str], read: &[&str], write: &[&str], env: &[&str]) -> PermissionChecker { PermissionChecker::from_config(&DenoPermissions { net: net.iter().map(|s| s.to_string()).collect(), read: read.iter().map(|s| s.to_string()).collect(), @@ -182,9 +177,7 @@ mod tests { #[test] fn read_path_traversal_blocked() { let checker = perms(&[], &["/tmp"], &[], &[]); - let err = checker - .check_read("/tmp/../../../etc/passwd") - .unwrap_err(); + let err = checker.check_read("/tmp/../../../etc/passwd").unwrap_err(); assert_eq!(err.kind, "read"); assert!(err.resource.contains("..")); } @@ -205,9 +198,7 @@ mod tests { #[test] fn write_path_traversal_blocked() { let checker = perms(&[], &[], &["/tmp/out"], &[]); - assert!(checker - .check_write("/tmp/out/../../etc/shadow") - .is_err()); + assert!(checker.check_write("/tmp/out/../../etc/shadow").is_err()); } #[test] diff --git a/wfe-yaml/src/executors/deno/runtime.rs b/wfe-yaml/src/executors/deno/runtime.rs index 07fa2c7..3611f66 100644 --- a/wfe-yaml/src/executors/deno/runtime.rs +++ b/wfe-yaml/src/executors/deno/runtime.rs @@ -8,7 +8,7 @@ use wfe_core::WfeError; use super::config::DenoConfig; use super::module_loader::WfeModuleLoader; -use super::ops::workflow::{wfe_ops, StepMeta, StepOutputs, WorkflowInputs}; +use super::ops::workflow::{StepMeta, StepOutputs, WorkflowInputs, wfe_ops}; use super::permissions::PermissionChecker; /// Create a configured `JsRuntime` for executing a workflow step script. @@ -61,8 +61,8 @@ pub fn would_auto_add_esm_sh(config: &DenoConfig) -> bool { #[cfg(test)] mod tests { - use super::*; use super::super::config::DenoPermissions; + use super::*; #[test] fn create_runtime_succeeds() { diff --git a/wfe-yaml/src/executors/deno/step.rs b/wfe-yaml/src/executors/deno/step.rs index d171145..58ae98b 100644 --- a/wfe-yaml/src/executors/deno/step.rs +++ b/wfe-yaml/src/executors/deno/step.rs @@ -1,7 +1,7 @@ use async_trait::async_trait; +use wfe_core::WfeError; use wfe_core::models::ExecutionResult; use wfe_core::traits::step::{StepBody, StepExecutionContext}; -use wfe_core::WfeError; use super::config::DenoConfig; use super::ops::workflow::StepOutputs; @@ -95,7 +95,9 @@ impl StepBody for DenoStep { /// Check if the source code uses ES module syntax or top-level await. fn needs_module_evaluation(source: &str) -> bool { // Top-level await requires module evaluation. ES import/export also require it. - source.contains("import ") || source.contains("import(") || source.contains("export ") + source.contains("import ") + || source.contains("import(") + || source.contains("export ") || source.contains("await ") } @@ -191,9 +193,8 @@ async fn run_module_inner( "wfe:///inline-module.js".to_string() }; - let specifier = deno_core::ModuleSpecifier::parse(&module_url).map_err(|e| { - WfeError::StepExecution(format!("Invalid module URL '{module_url}': {e}")) - })?; + let specifier = deno_core::ModuleSpecifier::parse(&module_url) + .map_err(|e| WfeError::StepExecution(format!("Invalid module URL '{module_url}': {e}")))?; let module_id = runtime .load_main_es_module_from_code(&specifier, source.to_string()) diff --git a/wfe-yaml/src/executors/shell.rs b/wfe-yaml/src/executors/shell.rs index 539c206..ed0aed6 100644 --- a/wfe-yaml/src/executors/shell.rs +++ b/wfe-yaml/src/executors/shell.rs @@ -2,9 +2,9 @@ use std::collections::HashMap; use async_trait::async_trait; use serde::{Deserialize, Serialize}; +use wfe_core::WfeError; use wfe_core::models::ExecutionResult; use wfe_core::traits::step::{StepBody, StepExecutionContext}; -use wfe_core::WfeError; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ShellConfig { @@ -31,8 +31,15 @@ impl ShellStep { // Inject workflow data as UPPER_CASE env vars (top-level keys only). // Skip keys that would override security-sensitive environment variables. const BLOCKED_KEYS: &[&str] = &[ - "PATH", "LD_PRELOAD", "LD_LIBRARY_PATH", "DYLD_LIBRARY_PATH", - "HOME", "SHELL", "USER", "LOGNAME", "TERM", + "PATH", + "LD_PRELOAD", + "LD_LIBRARY_PATH", + "DYLD_LIBRARY_PATH", + "HOME", + "SHELL", + "USER", + "LOGNAME", + "TERM", ]; if let Some(data_obj) = context.workflow.data.as_object() { for (key, value) in data_obj { @@ -78,19 +85,25 @@ impl ShellStep { let workflow_id = context.workflow.id.clone(); let definition_id = context.workflow.workflow_definition_id.clone(); let step_id = context.step.id; - let step_name = context.step.name.clone().unwrap_or_else(|| "unknown".to_string()); + let step_name = context + .step + .name + .clone() + .unwrap_or_else(|| "unknown".to_string()); let mut cmd = self.build_command(context); - let mut child = cmd.spawn().map_err(|e| { - WfeError::StepExecution(format!("Failed to spawn shell command: {e}")) - })?; + let mut child = cmd + .spawn() + .map_err(|e| WfeError::StepExecution(format!("Failed to spawn shell command: {e}")))?; - let stdout_pipe = child.stdout.take().ok_or_else(|| { - WfeError::StepExecution("failed to capture stdout pipe".to_string()) - })?; - let stderr_pipe = child.stderr.take().ok_or_else(|| { - WfeError::StepExecution("failed to capture stderr pipe".to_string()) - })?; + let stdout_pipe = child + .stdout + .take() + .ok_or_else(|| WfeError::StepExecution("failed to capture stdout pipe".to_string()))?; + let stderr_pipe = child + .stderr + .take() + .ok_or_else(|| WfeError::StepExecution("failed to capture stderr pipe".to_string()))?; let mut stdout_lines = BufReader::new(stdout_pipe).lines(); let mut stderr_lines = BufReader::new(stderr_pipe).lines(); @@ -194,9 +207,9 @@ impl ShellStep { } } } else { - cmd.output() - .await - .map_err(|e| WfeError::StepExecution(format!("Failed to spawn shell command: {e}")))? + cmd.output().await.map_err(|e| { + WfeError::StepExecution(format!("Failed to spawn shell command: {e}")) + })? }; let stdout = String::from_utf8_lossy(&output.stdout).to_string(); @@ -209,7 +222,10 @@ impl ShellStep { #[async_trait] impl StepBody for ShellStep { - async fn run(&mut self, context: &StepExecutionContext<'_>) -> wfe_core::Result { + async fn run( + &mut self, + context: &StepExecutionContext<'_>, + ) -> wfe_core::Result { let (stdout, stderr, exit_code) = if context.log_sink.is_some() { self.run_streaming(context).await? } else { diff --git a/wfe-yaml/src/lib.rs b/wfe-yaml/src/lib.rs index a0c2777..0673a6d 100644 --- a/wfe-yaml/src/lib.rs +++ b/wfe-yaml/src/lib.rs @@ -9,8 +9,8 @@ pub mod validation; use std::collections::{HashMap, HashSet}; use std::path::Path; -use serde::de::Error as _; use serde::Deserialize; +use serde::de::Error as _; use crate::compiler::CompiledWorkflow; use crate::error::YamlWorkflowError; @@ -50,8 +50,11 @@ pub fn load_workflow_from_str( // Parse to a generic YAML value first, then resolve merge keys (<<:). // This adds YAML 1.1 merge key support on top of serde_yaml 0.9's YAML 1.2 parser. let raw_value: serde_yaml::Value = serde_yaml::from_str(&interpolated)?; - let merged_value = yaml_merge_keys::merge_keys_serde(raw_value) - .map_err(|e| YamlWorkflowError::Parse(serde_yaml::Error::custom(format!("merge key resolution failed: {e}"))))?; + let merged_value = yaml_merge_keys::merge_keys_serde(raw_value).map_err(|e| { + YamlWorkflowError::Parse(serde_yaml::Error::custom(format!( + "merge key resolution failed: {e}" + ))) + })?; // Deserialize the merge-resolved value into our schema. let file: schema::YamlWorkflowFile = serde_yaml::from_value(merged_value)?; @@ -108,12 +111,11 @@ pub fn load_workflow_with_includes( let interpolated = interpolation::interpolate(yaml, config)?; let raw_value: serde_yaml::Value = serde_yaml::from_str(&interpolated)?; - let merged_value = yaml_merge_keys::merge_keys_serde(raw_value) - .map_err(|e| { - YamlWorkflowError::Parse(serde_yaml::Error::custom(format!( - "merge key resolution failed: {e}" - ))) - })?; + let merged_value = yaml_merge_keys::merge_keys_serde(raw_value).map_err(|e| { + YamlWorkflowError::Parse(serde_yaml::Error::custom(format!( + "merge key resolution failed: {e}" + ))) + })?; let with_includes: YamlWorkflowFileWithIncludes = serde_yaml::from_value(merged_value)?; @@ -121,13 +123,11 @@ pub fn load_workflow_with_includes( // Process includes. for include_path_str in &with_includes.include { - let include_path = base_path.parent().unwrap_or(base_path).join(include_path_str); - load_includes_recursive( - &include_path, - config, - &mut main_specs, - &mut visited, - )?; + let include_path = base_path + .parent() + .unwrap_or(base_path) + .join(include_path_str); + load_includes_recursive(&include_path, config, &mut main_specs, &mut visited)?; } // Main file takes precedence: included specs are only added if their ID @@ -149,14 +149,12 @@ fn load_includes_recursive( specs: &mut Vec, visited: &mut HashSet, ) -> Result<(), YamlWorkflowError> { - let canonical = path - .canonicalize() - .map_err(|e| { - YamlWorkflowError::Io(std::io::Error::new( - std::io::ErrorKind::NotFound, - format!("Include file not found: {}: {e}", path.display()), - )) - })?; + let canonical = path.canonicalize().map_err(|e| { + YamlWorkflowError::Io(std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("Include file not found: {}: {e}", path.display()), + )) + })?; let canonical_str = canonical.to_string_lossy().to_string(); if !visited.insert(canonical_str.clone()) { @@ -169,12 +167,11 @@ fn load_includes_recursive( let yaml = std::fs::read_to_string(&canonical)?; let interpolated = interpolation::interpolate(&yaml, config)?; let raw_value: serde_yaml::Value = serde_yaml::from_str(&interpolated)?; - let merged_value = yaml_merge_keys::merge_keys_serde(raw_value) - .map_err(|e| { - YamlWorkflowError::Parse(serde_yaml::Error::custom(format!( - "merge key resolution failed: {e}" - ))) - })?; + let merged_value = yaml_merge_keys::merge_keys_serde(raw_value).map_err(|e| { + YamlWorkflowError::Parse(serde_yaml::Error::custom(format!( + "merge key resolution failed: {e}" + ))) + })?; let with_includes: YamlWorkflowFileWithIncludes = serde_yaml::from_value(merged_value)?; @@ -190,7 +187,10 @@ fn load_includes_recursive( // Recurse into nested includes. for nested_include in &with_includes.include { - let nested_path = canonical.parent().unwrap_or(&canonical).join(nested_include); + let nested_path = canonical + .parent() + .unwrap_or(&canonical) + .join(nested_include); load_includes_recursive(&nested_path, config, specs, visited)?; } diff --git a/wfe-yaml/src/types.rs b/wfe-yaml/src/types.rs index 67a1e53..b343794 100644 --- a/wfe-yaml/src/types.rs +++ b/wfe-yaml/src/types.rs @@ -59,7 +59,9 @@ pub fn parse_type_string(s: &str) -> Result { // Check for generic types: list<...> or map<...> if let Some(inner_start) = s.find('<') { if !s.ends_with('>') { - return Err(format!("Malformed generic type: '{s}' (missing closing '>')")); + return Err(format!( + "Malformed generic type: '{s}' (missing closing '>')" + )); } let container = &s[..inner_start]; let inner_str = &s[inner_start + 1..s.len() - 1]; diff --git a/wfe-yaml/src/validation.rs b/wfe-yaml/src/validation.rs index 214983a..061f285 100644 --- a/wfe-yaml/src/validation.rs +++ b/wfe-yaml/src/validation.rs @@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet}; use crate::error::YamlWorkflowError; use crate::schema::{WorkflowSpec, YamlCombinator, YamlComparison, YamlCondition, YamlStep}; -use crate::types::{parse_type_string, SchemaType}; +use crate::types::{SchemaType, parse_type_string}; /// Validate a parsed workflow spec. pub fn validate(spec: &WorkflowSpec) -> Result<(), YamlWorkflowError> { @@ -494,11 +494,7 @@ fn validate_field_path( return Err(YamlWorkflowError::Validation(format!( "Condition references unknown input field '{field_name}'. \ Available inputs: [{}]", - spec.inputs - .keys() - .cloned() - .collect::>() - .join(", ") + spec.inputs.keys().cloned().collect::>().join(", ") ))); } } @@ -509,11 +505,7 @@ fn validate_field_path( return Err(YamlWorkflowError::Validation(format!( "Condition references unknown output field '{field_name}'. \ Available outputs: [{}]", - spec.outputs - .keys() - .cloned() - .collect::>() - .join(", ") + spec.outputs.keys().cloned().collect::>().join(", ") ))); } } diff --git a/wfe-yaml/tests/compiler.rs b/wfe-yaml/tests/compiler.rs index 0899e3c..642cca0 100644 --- a/wfe-yaml/tests/compiler.rs +++ b/wfe-yaml/tests/compiler.rs @@ -221,7 +221,10 @@ workflow: let test_config: wfe_yaml::executors::shell::ShellConfig = serde_json::from_value(test_step.step_config.clone().unwrap()).unwrap(); assert_eq!(test_config.run, "cargo build"); - assert_eq!(test_config.shell, "bash", "shell should be inherited from YAML anchor alias"); + assert_eq!( + test_config.shell, "bash", + "shell should be inherited from YAML anchor alias" + ); } #[test] @@ -473,8 +476,14 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; - assert!(err.contains("explode"), "Error should mention the invalid type, got: {err}"); + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; + assert!( + err.contains("explode"), + "Error should mention the invalid type, got: {err}" + ); } #[test] @@ -517,7 +526,10 @@ workflow: .iter() .find(|s| s.name.as_deref() == Some(*child_name)) .unwrap(); - assert!(child.step_config.is_some(), "Child {child_name} should have step_config"); + assert!( + child.step_config.is_some(), + "Child {child_name} should have step_config" + ); } // Factories should include entries for all 3 children. @@ -806,7 +818,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("config"), "Error should mention missing config, got: {err}" @@ -1019,11 +1034,11 @@ workflow: /// SubWorkflowStep (from wfe-core), not a placeholder. #[tokio::test] async fn workflow_step_factory_produces_real_sub_workflow_step() { + use std::future::Future; + use std::pin::Pin; + use std::sync::Mutex; use wfe_core::models::{ExecutionPointer, WorkflowInstance, WorkflowStep as WfStep}; use wfe_core::traits::step::{HostContext, StepExecutionContext}; - use std::pin::Pin; - use std::future::Future; - use std::sync::Mutex; let yaml = r#" workflows: @@ -1047,30 +1062,45 @@ workflows: let workflows = load_workflow_from_str(yaml, &config).unwrap(); // Find the parent workflow's factory for the "run-child" step - let parent = workflows.iter().find(|w| w.definition.id == "parent").unwrap(); - let factory_key = parent.step_factories.iter() + let parent = workflows + .iter() + .find(|w| w.definition.id == "parent") + .unwrap(); + let factory_key = parent + .step_factories + .iter() .find(|(k, _)| k.contains("run-child")) .map(|(k, _)| k.clone()) .expect("run-child factory should exist"); // Create a step from the factory - let factory = &parent.step_factories.iter() + let factory = &parent + .step_factories + .iter() .find(|(k, _)| *k == factory_key) - .unwrap().1; + .unwrap() + .1; let mut step = factory(); // Mock host context that records the start_workflow call - struct MockHost { called: Mutex } + struct MockHost { + called: Mutex, + } impl HostContext for MockHost { - fn start_workflow(&self, _def: &str, _ver: u32, _data: serde_json::Value) - -> Pin> + Send + '_>> - { + fn start_workflow( + &self, + _def: &str, + _ver: u32, + _data: serde_json::Value, + ) -> Pin> + Send + '_>> { *self.called.lock().unwrap() = true; Box::pin(async { Ok("child-instance-id".to_string()) }) } } - let host = MockHost { called: Mutex::new(false) }; + let host = MockHost { + called: Mutex::new(false), + }; let pointer = ExecutionPointer::new(0); let wf_step = WfStep::new(0, &factory_key); let workflow = WorkflowInstance::new("parent", 1, serde_json::json!({})); @@ -1182,15 +1212,13 @@ workflow: } // Second child: not match &children[1] { - wfe_core::models::StepCondition::Not(inner) => { - match inner.as_ref() { - wfe_core::models::StepCondition::Comparison(cmp) => { - assert_eq!(cmp.field, ".inputs.skip"); - assert_eq!(cmp.operator, wfe_core::models::ComparisonOp::Equals); - } - other => panic!("Expected Comparison inside Not, got: {other:?}"), + wfe_core::models::StepCondition::Not(inner) => match inner.as_ref() { + wfe_core::models::StepCondition::Comparison(cmp) => { + assert_eq!(cmp.field, ".inputs.skip"); + assert_eq!(cmp.operator, wfe_core::models::ComparisonOp::Equals); } - } + other => panic!("Expected Comparison inside Not, got: {other:?}"), + }, other => panic!("Expected Not, got: {other:?}"), } } diff --git a/wfe-yaml/tests/deno.rs b/wfe-yaml/tests/deno.rs index 71e7972..a98e759 100644 --- a/wfe-yaml/tests/deno.rs +++ b/wfe-yaml/tests/deno.rs @@ -42,7 +42,7 @@ fn make_context<'a>( workflow, cancellation_token: tokio_util::sync::CancellationToken::new(), host_context: None, - log_sink: None, + log_sink: None, } } @@ -224,10 +224,7 @@ workflow: let result = wfe_yaml::load_single_workflow_from_str(yaml, &config); assert!(result.is_err()); let msg = result.err().unwrap().to_string(); - assert!( - msg.contains("config") || msg.contains("Deno"), - "got: {msg}" - ); + assert!(msg.contains("config") || msg.contains("Deno"), "got: {msg}"); } #[test] @@ -247,10 +244,7 @@ workflow: let result = wfe_yaml::load_single_workflow_from_str(yaml, &config); assert!(result.is_err()); let msg = result.err().unwrap().to_string(); - assert!( - msg.contains("script") || msg.contains("file"), - "got: {msg}" - ); + assert!(msg.contains("script") || msg.contains("file"), "got: {msg}"); } #[test] @@ -269,7 +263,10 @@ workflow: let compiled = wfe_yaml::load_single_workflow_from_str(yaml, &config).unwrap(); assert!(!compiled.step_factories.is_empty()); let (key, _factory) = &compiled.step_factories[0]; - assert!(key.contains("deno"), "factory key should contain 'deno', got: {key}"); + assert!( + key.contains("deno"), + "factory key should contain 'deno', got: {key}" + ); } // --------------------------------------------------------------------------- @@ -345,10 +342,7 @@ async fn deno_fetch_denied_host() { "expected permission error, got: {data:?}" ); let err_msg = data["error"].as_str().unwrap(); - assert!( - err_msg.contains("Permission denied"), - "got: {err_msg}" - ); + assert!(err_msg.contains("Permission denied"), "got: {err_msg}"); } #[tokio::test] @@ -384,8 +378,13 @@ async fn deno_fetch_returns_json() { async fn deno_fetch_post_with_body() { let server = wiremock::MockServer::start().await; wiremock::Mock::given(wiremock::matchers::method("POST")) - .and(wiremock::matchers::header("content-type", "application/json")) - .and(wiremock::matchers::body_json(serde_json::json!({"key": "val"}))) + .and(wiremock::matchers::header( + "content-type", + "application/json", + )) + .and(wiremock::matchers::body_json( + serde_json::json!({"key": "val"}), + )) .respond_with(wiremock::ResponseTemplate::new(201).set_body_string("created")) .mount(&server) .await; @@ -501,7 +500,11 @@ async fn deno_import_local_file() { let dir = tempfile::tempdir().unwrap(); let helper_path = dir.path().join("helper.js"); let mut f = std::fs::File::create(&helper_path).unwrap(); - writeln!(f, "export function greet(name) {{ return `hello ${{name}}`; }}").unwrap(); + writeln!( + f, + "export function greet(name) {{ return `hello ${{name}}`; }}" + ) + .unwrap(); drop(f); let main_path = dir.path().join("main.js"); @@ -536,10 +539,7 @@ output("greeting", greet("world"));"#, async fn deno_dynamic_import_denied() { let server = wiremock::MockServer::start().await; wiremock::Mock::given(wiremock::matchers::any()) - .respond_with( - wiremock::ResponseTemplate::new(200) - .set_body_string("export const x = 1;"), - ) + .respond_with(wiremock::ResponseTemplate::new(200).set_body_string("export const x = 1;")) .mount(&server) .await; diff --git a/wfe-yaml/tests/deno_e2e.rs b/wfe-yaml/tests/deno_e2e.rs index 248dfa0..1920029 100644 --- a/wfe-yaml/tests/deno_e2e.rs +++ b/wfe-yaml/tests/deno_e2e.rs @@ -105,7 +105,10 @@ workflow: "#; let instance = run_yaml_workflow(yaml).await; assert_eq!(instance.status, WorkflowStatus::Complete); - assert_eq!(instance.data["message"], serde_json::json!("hello from deno")); + assert_eq!( + instance.data["message"], + serde_json::json!("hello from deno") + ); } #[tokio::test] @@ -114,8 +117,7 @@ async fn yaml_deno_with_fetch_wiremock() { wiremock::Mock::given(wiremock::matchers::method("GET")) .and(wiremock::matchers::path("/api/data")) .respond_with( - wiremock::ResponseTemplate::new(200) - .set_body_json(serde_json::json!({"value": 42})), + wiremock::ResponseTemplate::new(200).set_body_json(serde_json::json!({"value": 42})), ) .mount(&server) .await; @@ -325,8 +327,7 @@ workflow: const data = inputs(); output("doubled", data.value * 2); "#; - let instance = - run_yaml_workflow_with_data(yaml, serde_json::json!({"value": 21})).await; + let instance = run_yaml_workflow_with_data(yaml, serde_json::json!({"value": 21})).await; assert_eq!(instance.status, WorkflowStatus::Complete); assert_eq!(instance.data["doubled"], serde_json::json!(42)); } @@ -547,10 +548,7 @@ workflow: match result { Err(e) => { let msg = e.to_string(); - assert!( - msg.contains("config") || msg.contains("Deno"), - "got: {msg}" - ); + assert!(msg.contains("config") || msg.contains("Deno"), "got: {msg}"); } Ok(_) => panic!("expected error for deno step without config"), } @@ -574,10 +572,7 @@ workflow: match result { Err(e) => { let msg = e.to_string(); - assert!( - msg.contains("script") || msg.contains("file"), - "got: {msg}" - ); + assert!(msg.contains("script") || msg.contains("file"), "got: {msg}"); } Ok(_) => panic!("expected error for deno step without script or file"), } @@ -633,8 +628,14 @@ workflow: "#; let config = HashMap::new(); let compiled = load_single_workflow_from_str(yaml, &config).unwrap(); - let has_shell = compiled.step_factories.iter().any(|(k, _)| k.contains("shell")); - let has_deno = compiled.step_factories.iter().any(|(k, _)| k.contains("deno")); + let has_shell = compiled + .step_factories + .iter() + .any(|(k, _)| k.contains("shell")); + let has_deno = compiled + .step_factories + .iter() + .any(|(k, _)| k.contains("deno")); assert!(has_shell, "should have shell factory"); assert!(has_deno, "should have deno factory"); } @@ -785,7 +786,10 @@ workflow: "#; let instance = run_yaml_workflow(yaml).await; assert_eq!(instance.status, WorkflowStatus::Complete); - assert_eq!(instance.data["nested"]["a"]["b"]["c"], serde_json::json!(42)); + assert_eq!( + instance.data["nested"]["a"]["b"]["c"], + serde_json::json!(42) + ); assert_eq!(instance.data["array"], serde_json::json!([1, 2, 3])); assert!(instance.data["null_val"].is_null()); assert_eq!(instance.data["bool_val"], serde_json::json!(false)); diff --git a/wfe-yaml/tests/lib_tests.rs b/wfe-yaml/tests/lib_tests.rs index 54eef05..79e83a7 100644 --- a/wfe-yaml/tests/lib_tests.rs +++ b/wfe-yaml/tests/lib_tests.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::io::Write; -use wfe_yaml::{load_workflow, load_single_workflow_from_str}; +use wfe_yaml::{load_single_workflow_from_str, load_workflow}; #[test] fn load_workflow_from_file() { @@ -35,7 +35,10 @@ fn load_workflow_from_nonexistent_file_returns_error() { let path = std::path::Path::new("/tmp/nonexistent_wfe_test_file_12345.yaml"); let result = load_workflow(path, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("IO error") || err.contains("No such file"), "Expected IO error, got: {err}" @@ -47,7 +50,10 @@ fn load_workflow_from_str_with_invalid_yaml_returns_error() { let yaml = "this is not valid yaml: [[["; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("YAML parse error"), "Expected YAML parse error, got: {err}" @@ -96,7 +102,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("missing"), "Expected unresolved variable error, got: {err}" diff --git a/wfe-yaml/tests/rustlang_containerd.rs b/wfe-yaml/tests/rustlang_containerd.rs index a7586a9..1d10b0f 100644 --- a/wfe-yaml/tests/rustlang_containerd.rs +++ b/wfe-yaml/tests/rustlang_containerd.rs @@ -55,9 +55,19 @@ async fn run_yaml_workflow_with_config( ) -> wfe::models::WorkflowInstance { let compiled = load_single_workflow_from_str(yaml, config).unwrap(); for step in &compiled.definition.steps { - eprintln!(" step: {:?} type={} config={:?}", step.name, step.step_type, step.step_config); + eprintln!( + " step: {:?} type={} config={:?}", + step.name, step.step_type, step.step_config + ); } - eprintln!(" factories: {:?}", compiled.step_factories.iter().map(|(k, _)| k.clone()).collect::>()); + eprintln!( + " factories: {:?}", + compiled + .step_factories + .iter() + .map(|(k, _)| k.clone()) + .collect::>() + ); let persistence = Arc::new(InMemoryPersistenceProvider::new()); let lock = Arc::new(InMemoryLockProvider::new()); @@ -197,7 +207,9 @@ fn make_config( #[tokio::test] #[ignore = "requires containerd daemon"] async fn minimal_echo_in_containerd_via_workflow() { - let _ = tracing_subscriber::fmt().with_env_filter("wfe_containerd=debug,wfe_core::executor=debug").try_init(); + let _ = tracing_subscriber::fmt() + .with_env_filter("wfe_containerd=debug,wfe_core::executor=debug") + .try_init(); let Some(addr) = containerd_addr() else { eprintln!("SKIP: containerd not available"); return; @@ -237,10 +249,7 @@ async fn minimal_echo_in_containerd_via_workflow() { eprintln!("Status: {:?}, Data: {:?}", instance.status, instance.data); assert_eq!(instance.status, WorkflowStatus::Complete); let data = instance.data.as_object().unwrap(); - assert_eq!( - data.get("echo.status").and_then(|v| v.as_str()), - Some("ok"), - ); + assert_eq!(data.get("echo.status").and_then(|v| v.as_str()), Some("ok"),); } // --------------------------------------------------------------------------- @@ -259,79 +268,129 @@ async fn full_rust_pipeline_in_container() { let rustup_home = shared_tempdir("rustup"); let workspace = shared_tempdir("workspace"); - let config = make_config( - &addr, - &cargo_home, - &rustup_home, - Some(&workspace), - ); + let config = make_config(&addr, &cargo_home, &rustup_home, Some(&workspace)); let steps = [ containerd_step_yaml( - "install-rust", "host", "if-not-present", "10m", None, false, + "install-rust", + "host", + "if-not-present", + "10m", + None, + false, " apt-get update && apt-get install -y curl gcc pkg-config libssl-dev\n\ \x20 curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable", ), containerd_step_yaml( - "install-tools", "host", "never", "10m", None, false, + "install-tools", + "host", + "never", + "10m", + None, + false, " rustup component add clippy rustfmt llvm-tools-preview\n\ \x20 cargo install cargo-audit cargo-deny cargo-nextest cargo-llvm-cov", ), containerd_step_yaml( - "create-project", "host", "never", "2m", None, true, + "create-project", + "host", + "never", + "2m", + None, + true, " cargo init /workspace/test-crate --name test-crate\n\ \x20 cd /workspace/test-crate\n\ \x20 echo '#[cfg(test)] mod tests { #[test] fn it_works() { assert_eq!(2+2,4); } }' >> src/main.rs", ), containerd_step_yaml( - "cargo-fmt", "none", "never", "2m", - Some("/workspace/test-crate"), true, + "cargo-fmt", + "none", + "never", + "2m", + Some("/workspace/test-crate"), + true, " cargo fmt -- --check || cargo fmt", ), containerd_step_yaml( - "cargo-check", "none", "never", "5m", - Some("/workspace/test-crate"), true, + "cargo-check", + "none", + "never", + "5m", + Some("/workspace/test-crate"), + true, " cargo check", ), containerd_step_yaml( - "cargo-clippy", "none", "never", "5m", - Some("/workspace/test-crate"), true, + "cargo-clippy", + "none", + "never", + "5m", + Some("/workspace/test-crate"), + true, " cargo clippy -- -D warnings", ), containerd_step_yaml( - "cargo-test", "none", "never", "5m", - Some("/workspace/test-crate"), true, + "cargo-test", + "none", + "never", + "5m", + Some("/workspace/test-crate"), + true, " cargo test", ), containerd_step_yaml( - "cargo-build", "none", "never", "5m", - Some("/workspace/test-crate"), true, + "cargo-build", + "none", + "never", + "5m", + Some("/workspace/test-crate"), + true, " cargo build --release", ), containerd_step_yaml( - "cargo-nextest", "none", "never", "5m", - Some("/workspace/test-crate"), true, + "cargo-nextest", + "none", + "never", + "5m", + Some("/workspace/test-crate"), + true, " cargo nextest run", ), containerd_step_yaml( - "cargo-llvm-cov", "none", "never", "5m", - Some("/workspace/test-crate"), true, + "cargo-llvm-cov", + "none", + "never", + "5m", + Some("/workspace/test-crate"), + true, " cargo llvm-cov --summary-only", ), containerd_step_yaml( - "cargo-audit", "host", "never", "5m", - Some("/workspace/test-crate"), true, + "cargo-audit", + "host", + "never", + "5m", + Some("/workspace/test-crate"), + true, " cargo audit || true", ), containerd_step_yaml( - "cargo-deny", "none", "never", "5m", - Some("/workspace/test-crate"), true, + "cargo-deny", + "none", + "never", + "5m", + Some("/workspace/test-crate"), + true, " cargo deny init\n\ \x20 cargo deny check || true", ), containerd_step_yaml( - "cargo-doc", "none", "never", "5m", - Some("/workspace/test-crate"), true, + "cargo-doc", + "none", + "never", + "5m", + Some("/workspace/test-crate"), + true, " cargo doc --no-deps", ), ]; diff --git a/wfe-yaml/tests/schema.rs b/wfe-yaml/tests/schema.rs index e0982fa..68a3483 100644 --- a/wfe-yaml/tests/schema.rs +++ b/wfe-yaml/tests/schema.rs @@ -17,10 +17,7 @@ workflow: assert_eq!(parsed.workflow.version, 1); assert_eq!(parsed.workflow.steps.len(), 1); assert_eq!(parsed.workflow.steps[0].name, "hello"); - assert_eq!( - parsed.workflow.steps[0].step_type.as_deref(), - Some("shell") - ); + assert_eq!(parsed.workflow.steps[0].step_type.as_deref(), Some("shell")); } #[test] @@ -263,20 +260,14 @@ workflow: let parsed: YamlWorkflow = serde_yaml::from_str(yaml).unwrap(); assert_eq!(parsed.workflow.inputs.len(), 3); assert_eq!(parsed.workflow.inputs.get("repo_url").unwrap(), "string"); - assert_eq!( - parsed.workflow.inputs.get("tags").unwrap(), - "list" - ); + assert_eq!(parsed.workflow.inputs.get("tags").unwrap(), "list"); assert_eq!(parsed.workflow.inputs.get("verbose").unwrap(), "bool?"); assert_eq!(parsed.workflow.outputs.len(), 2); assert_eq!( parsed.workflow.outputs.get("artifact_path").unwrap(), "string" ); - assert_eq!( - parsed.workflow.outputs.get("exit_code").unwrap(), - "integer" - ); + assert_eq!(parsed.workflow.outputs.get("exit_code").unwrap(), "integer"); } #[test] diff --git a/wfe-yaml/tests/shell.rs b/wfe-yaml/tests/shell.rs index ea0e8d2..b966747 100644 --- a/wfe-yaml/tests/shell.rs +++ b/wfe-yaml/tests/shell.rs @@ -60,7 +60,9 @@ struct CollectingLogSink { impl CollectingLogSink { fn new() -> Self { - Self { chunks: tokio::sync::Mutex::new(Vec::new()) } + Self { + chunks: tokio::sync::Mutex::new(Vec::new()), + } } async fn chunks(&self) -> Vec { @@ -242,11 +244,8 @@ workflow: run: echo "{wfe_prefix}[output result=$GREETING]" "# ); - let instance = run_yaml_workflow_with_data( - &yaml, - serde_json::json!({"greeting": "hi there"}), - ) - .await; + let instance = + run_yaml_workflow_with_data(&yaml, serde_json::json!({"greeting": "hi there"})).await; assert_eq!(instance.status, WorkflowStatus::Complete); if let Some(data) = instance.data.as_object() { @@ -320,19 +319,33 @@ workflow: assert_eq!(instance.status, WorkflowStatus::Complete); let chunks = log_sink.chunks().await; - assert!(chunks.len() >= 2, "expected at least 2 stdout chunks, got {}", chunks.len()); + assert!( + chunks.len() >= 2, + "expected at least 2 stdout chunks, got {}", + chunks.len() + ); let stdout_chunks: Vec<_> = chunks .iter() .filter(|c| c.stream == wfe_core::traits::LogStreamType::Stdout) .collect(); - assert!(stdout_chunks.len() >= 2, "expected at least 2 stdout chunks"); + assert!( + stdout_chunks.len() >= 2, + "expected at least 2 stdout chunks" + ); - let all_data: String = stdout_chunks.iter() + let all_data: String = stdout_chunks + .iter() .map(|c| String::from_utf8_lossy(&c.data).to_string()) .collect(); - assert!(all_data.contains("line one"), "stdout should contain 'line one', got: {all_data}"); - assert!(all_data.contains("line two"), "stdout should contain 'line two', got: {all_data}"); + assert!( + all_data.contains("line one"), + "stdout should contain 'line one', got: {all_data}" + ); + assert!( + all_data.contains("line two"), + "stdout should contain 'line two', got: {all_data}" + ); // Verify chunk metadata. for chunk in &stdout_chunks { @@ -364,10 +377,14 @@ workflow: .collect(); assert!(!stderr_chunks.is_empty(), "expected stderr chunks"); - let stderr_data: String = stderr_chunks.iter() + let stderr_data: String = stderr_chunks + .iter() .map(|c| String::from_utf8_lossy(&c.data).to_string()) .collect(); - assert!(stderr_data.contains("stderr output"), "stderr should contain 'stderr output', got: {stderr_data}"); + assert!( + stderr_data.contains("stderr output"), + "stderr should contain 'stderr output', got: {stderr_data}" + ); } #[tokio::test] @@ -392,8 +409,14 @@ workflow: let chunks = log_sink.chunks().await; let step_names: Vec<_> = chunks.iter().map(|c| c.step_name.as_str()).collect(); - assert!(step_names.contains(&"step-a"), "should have chunks from step-a"); - assert!(step_names.contains(&"step-b"), "should have chunks from step-b"); + assert!( + step_names.contains(&"step-a"), + "should have chunks from step-a" + ); + assert!( + step_names.contains(&"step-b"), + "should have chunks from step-b" + ); } #[tokio::test] @@ -412,7 +435,13 @@ workflow: let instance = run_yaml_workflow(yaml).await; assert_eq!(instance.status, WorkflowStatus::Complete); let data = instance.data.as_object().unwrap(); - assert!(data.get("echo-step.stdout").unwrap().as_str().unwrap().contains("no sink")); + assert!( + data.get("echo-step.stdout") + .unwrap() + .as_str() + .unwrap() + .contains("no sink") + ); } // ── Security regression tests ──────────────────────────────────────── @@ -431,11 +460,8 @@ workflow: run: echo "$PATH" "#; // Set a workflow data key "path" that would override PATH if not blocked. - let instance = run_yaml_workflow_with_data( - yaml, - serde_json::json!({"path": "/attacker/bin"}), - ) - .await; + let instance = + run_yaml_workflow_with_data(yaml, serde_json::json!({"path": "/attacker/bin"})).await; assert_eq!(instance.status, WorkflowStatus::Complete); let data = instance.data.as_object().unwrap(); @@ -463,11 +489,8 @@ workflow: run: echo "{wfe_prefix}[output val=$MY_CUSTOM_VAR]" "# ); - let instance = run_yaml_workflow_with_data( - &yaml, - serde_json::json!({"my_custom_var": "works"}), - ) - .await; + let instance = + run_yaml_workflow_with_data(&yaml, serde_json::json!({"my_custom_var": "works"})).await; assert_eq!(instance.status, WorkflowStatus::Complete); let data = instance.data.as_object().unwrap(); diff --git a/wfe-yaml/tests/types.rs b/wfe-yaml/tests/types.rs index bf36031..d4dc781 100644 --- a/wfe-yaml/tests/types.rs +++ b/wfe-yaml/tests/types.rs @@ -1,4 +1,4 @@ -use wfe_yaml::types::{parse_type_string, SchemaType}; +use wfe_yaml::types::{SchemaType, parse_type_string}; #[test] fn parse_all_primitives() { diff --git a/wfe-yaml/tests/validation.rs b/wfe-yaml/tests/validation.rs index 51fc0ca..a0284d4 100644 --- a/wfe-yaml/tests/validation.rs +++ b/wfe-yaml/tests/validation.rs @@ -12,7 +12,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("at least one step"), "Expected 'at least one step' error, got: {err}" @@ -30,7 +33,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("type") && err.contains("parallel"), "Expected error about missing type or parallel, got: {err}" @@ -54,7 +60,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("cannot have both"), "Expected 'cannot have both' error, got: {err}" @@ -79,7 +88,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("Duplicate step name") && err.contains("deploy"), "Expected duplicate name error, got: {err}" @@ -100,7 +112,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("config.run") || err.contains("config.file"), "Expected error about missing run/file, got: {err}" @@ -119,7 +134,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("config"), "Expected error about missing config, got: {err}" @@ -142,7 +160,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("panic"), "Expected error mentioning invalid type, got: {err}" @@ -165,7 +186,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("crash"), "Expected error mentioning invalid type, got: {err}" @@ -185,7 +209,11 @@ workflow: run: echo hello "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); - assert!(result.is_ok(), "Valid workflow should pass, got: {:?}", result.err()); + assert!( + result.is_ok(), + "Valid workflow should pass, got: {:?}", + result.err() + ); } #[test] @@ -203,7 +231,11 @@ workflow: run: echo a "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); - assert!(result.is_ok(), "Valid parallel workflow should pass, got: {:?}", result.err()); + assert!( + result.is_ok(), + "Valid parallel workflow should pass, got: {:?}", + result.err() + ); } #[test] @@ -225,7 +257,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("Duplicate step name"), "Expected duplicate name error for hook, got: {err}" @@ -250,7 +285,11 @@ workflow: run: echo ok "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); - assert!(result.is_ok(), "Valid on_success hook should pass, got: {:?}", result.err()); + assert!( + result.is_ok(), + "Valid on_success hook should pass, got: {:?}", + result.err() + ); } #[test] @@ -271,7 +310,11 @@ workflow: run: cleanup.sh "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); - assert!(result.is_ok(), "Valid ensure hook should pass, got: {:?}", result.err()); + assert!( + result.is_ok(), + "Valid ensure hook should pass, got: {:?}", + result.err() + ); } #[test] @@ -320,7 +363,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("Duplicate step name") && err.contains("task-a"), "Expected duplicate name in parallel children, got: {err}" @@ -341,7 +387,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("config"), "Expected error about missing config, got: {err}" @@ -362,7 +411,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("config.workflow"), "Expected error about missing config.workflow, got: {err}" @@ -382,7 +434,11 @@ workflow: workflow: child-wf "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); - assert!(result.is_ok(), "Valid workflow step should pass, got: {:?}", result.err()); + assert!( + result.is_ok(), + "Valid workflow step should pass, got: {:?}", + result.err() + ); } // --- Multi-workflow validation tests --- @@ -407,7 +463,11 @@ workflows: run: cargo test "#; let result = load_workflow_from_str(yaml, &HashMap::new()); - assert!(result.is_ok(), "Valid multi-workflow should pass, got: {:?}", result.err()); + assert!( + result.is_ok(), + "Valid multi-workflow should pass, got: {:?}", + result.err() + ); assert_eq!(result.unwrap().len(), 2); } @@ -432,7 +492,10 @@ workflows: "#; let result = load_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("Duplicate workflow ID"), "Expected duplicate workflow ID error, got: {err}" @@ -461,7 +524,10 @@ workflows: "#; let result = load_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("Cannot specify both"), "Expected error about both workflow and workflows, got: {err}" @@ -476,7 +542,10 @@ something_else: "#; let result = load_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("Must specify either"), "Expected error about missing workflow/workflows, got: {err}" @@ -490,7 +559,10 @@ workflows: [] "#; let result = load_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("empty"), "Expected error about empty workflows, got: {err}" @@ -520,7 +592,10 @@ workflows: "#; let result = load_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("Circular workflow reference"), "Expected circular reference error, got: {err}" @@ -541,7 +616,10 @@ workflows: "#; let result = load_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("Circular workflow reference"), "Expected circular reference error, got: {err}" @@ -568,7 +646,11 @@ workflows: run: echo working "#; let result = load_workflow_from_str(yaml, &HashMap::new()); - assert!(result.is_ok(), "Valid workflow reference should pass, got: {:?}", result.err()); + assert!( + result.is_ok(), + "Valid workflow reference should pass, got: {:?}", + result.err() + ); } #[test] @@ -585,7 +667,11 @@ workflow: workflow: some-external-wf "#; let result = load_workflow_from_str(yaml, &HashMap::new()); - assert!(result.is_ok(), "External workflow ref should not error, got: {:?}", result.err()); + assert!( + result.is_ok(), + "External workflow ref should not error, got: {:?}", + result.err() + ); } #[test] @@ -609,7 +695,10 @@ workflows: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("Expected single workflow"), "Expected single workflow error, got: {err}" @@ -631,7 +720,11 @@ workflows: run: echo hello "#; let result = load_workflow_from_str(yaml, &HashMap::new()); - assert!(result.is_ok(), "Single workflow in multi-mode should pass, got: {:?}", result.err()); + assert!( + result.is_ok(), + "Single workflow in multi-mode should pass, got: {:?}", + result.err() + ); assert_eq!(result.unwrap().len(), 1); } @@ -662,7 +755,11 @@ workflows: run: echo gamma "#; let result = load_workflow_from_str(yaml, &HashMap::new()); - assert!(result.is_ok(), "Multiple independent workflows should pass, got: {:?}", result.err()); + assert!( + result.is_ok(), + "Multiple independent workflows should pass, got: {:?}", + result.err() + ); assert_eq!(result.unwrap().len(), 3); } @@ -686,7 +783,11 @@ workflows: run: echo working "#; let result = load_workflow_from_str(yaml, &HashMap::new()); - assert!(result.is_ok(), "Cross-referenced workflows should pass, got: {:?}", result.err()); + assert!( + result.is_ok(), + "Cross-referenced workflows should pass, got: {:?}", + result.err() + ); } // --- Cycle detection edge cases --- @@ -719,7 +820,10 @@ workflows: "#; let result = load_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("Circular workflow reference"), "Expected circular reference error for 3-node cycle, got: {err}" @@ -753,7 +857,11 @@ workflows: run: echo done "#; let result = load_workflow_from_str(yaml, &HashMap::new()); - assert!(result.is_ok(), "Linear chain should not be a cycle, got: {:?}", result.err()); + assert!( + result.is_ok(), + "Linear chain should not be a cycle, got: {:?}", + result.err() + ); } #[test] @@ -794,7 +902,11 @@ workflows: run: echo done "#; let result = load_workflow_from_str(yaml, &HashMap::new()); - assert!(result.is_ok(), "Diamond dependency should not be a cycle, got: {:?}", result.err()); + assert!( + result.is_ok(), + "Diamond dependency should not be a cycle, got: {:?}", + result.err() + ); } // --- Deno step validation --- @@ -811,7 +923,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("Deno") && err.contains("config"), "Expected Deno config error, got: {err}" @@ -833,7 +948,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("Deno") && (err.contains("script") || err.contains("file")), "Expected Deno script/file error, got: {err}" @@ -854,7 +972,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("BuildKit") && err.contains("config"), "Expected BuildKit config error, got: {err}" @@ -875,7 +996,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("dockerfile"), "Expected dockerfile error, got: {err}" @@ -896,7 +1020,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("context"), "Expected context error, got: {err}" @@ -919,7 +1046,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("push") && err.contains("tags"), "Expected push/tags error, got: {err}" @@ -970,7 +1100,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("Containerd") && err.contains("config"), "Expected Containerd config error, got: {err}" @@ -991,11 +1124,11 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; - assert!( - err.contains("image"), - "Expected image error, got: {err}" - ); + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; + assert!(err.contains("image"), "Expected image error, got: {err}"); } #[test] @@ -1012,7 +1145,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("run") || err.contains("command"), "Expected run/command error, got: {err}" @@ -1037,7 +1173,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("cannot have both"), "Expected 'cannot have both' error, got: {err}" @@ -1060,7 +1199,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("network") && err.contains("overlay"), "Expected invalid network error, got: {err}" @@ -1111,7 +1253,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("pull") && err.contains("aggressive"), "Expected invalid pull policy error, got: {err}" @@ -1190,7 +1335,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("config"), "Expected config error for invalid hook, got: {err}" @@ -1214,7 +1362,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("config"), "Expected config error for invalid on_success hook, got: {err}" @@ -1238,7 +1389,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("config"), "Expected config error for invalid ensure hook, got: {err}" @@ -1261,7 +1415,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("config"), "Expected config error for deeply nested invalid step, got: {err}" @@ -1301,7 +1458,10 @@ workflows: "#; let result = load_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("Circular workflow reference"), "Expected circular reference from hooks, got: {err}" @@ -1339,7 +1499,10 @@ workflows: "#; let result = load_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("Circular workflow reference"), "Expected circular reference from ensure hooks, got: {err}" @@ -1371,7 +1534,10 @@ workflows: "#; let result = load_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("Circular workflow reference"), "Expected circular reference from parallel blocks, got: {err}" @@ -1394,7 +1560,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("Unknown step type") && err.contains("terraform"), "Expected unknown step type error, got: {err}" @@ -1408,7 +1577,10 @@ fn load_workflow_from_nonexistent_file_returns_io_error() { let path = std::path::Path::new("/tmp/nonexistent_wfe_test_file.yaml"); let result = wfe_yaml::load_workflow(path, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("IO error") || err.contains("No such file"), "Expected IO error, got: {err}" @@ -1435,7 +1607,11 @@ workflow: equals: true "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); - assert!(result.is_ok(), "Field path to known input should pass, got: {:?}", result.err()); + assert!( + result.is_ok(), + "Field path to known input should pass, got: {:?}", + result.err() + ); } #[test] @@ -1458,7 +1634,11 @@ workflow: equals: success "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); - assert!(result.is_ok(), "Field path to known output should pass, got: {:?}", result.err()); + assert!( + result.is_ok(), + "Field path to known output should pass, got: {:?}", + result.err() + ); } #[test] @@ -1480,7 +1660,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("unknown input field") && err.contains("nonexistent"), "Expected unknown input field error, got: {err}" @@ -1508,7 +1691,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("unknown output field") && err.contains("missing"), "Expected unknown output field error, got: {err}" @@ -1534,7 +1720,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("gt/gte/lt/lte") && err.contains("number/integer"), "Expected type mismatch error, got: {err}" @@ -1559,7 +1748,11 @@ workflow: gt: 5 "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); - assert!(result.is_ok(), "gt on number should pass, got: {:?}", result.err()); + assert!( + result.is_ok(), + "gt on number should pass, got: {:?}", + result.err() + ); } #[test] @@ -1581,7 +1774,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("contains") && err.contains("string/list"), "Expected type mismatch error for contains, got: {err}" @@ -1606,7 +1802,11 @@ workflow: contains: needle "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); - assert!(result.is_ok(), "contains on string should pass, got: {:?}", result.err()); + assert!( + result.is_ok(), + "contains on string should pass, got: {:?}", + result.err() + ); } #[test] @@ -1627,7 +1827,11 @@ workflow: contains: release "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); - assert!(result.is_ok(), "contains on list should pass, got: {:?}", result.err()); + assert!( + result.is_ok(), + "contains on list should pass, got: {:?}", + result.err() + ); } #[test] @@ -1649,7 +1853,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("is_null/is_not_null") && err.contains("optional"), "Expected type mismatch error for is_null, got: {err}" @@ -1674,7 +1881,11 @@ workflow: is_null: true "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); - assert!(result.is_ok(), "is_null on optional should pass, got: {:?}", result.err()); + assert!( + result.is_ok(), + "is_null on optional should pass, got: {:?}", + result.err() + ); } #[test] @@ -1693,7 +1904,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("never produced") && err.contains("result"), "Expected unused output error, got: {err}" @@ -1717,7 +1931,11 @@ workflow: - name: result "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); - assert!(result.is_ok(), "Output produced by step should pass, got: {:?}", result.err()); + assert!( + result.is_ok(), + "Output produced by step should pass, got: {:?}", + result.err() + ); } #[test] @@ -1734,7 +1952,11 @@ workflow: run: echo hi "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); - assert!(result.is_ok(), "No outputs schema should not cause error, got: {:?}", result.err()); + assert!( + result.is_ok(), + "No outputs schema should not cause error, got: {:?}", + result.err() + ); } #[test] @@ -1780,7 +2002,10 @@ workflow: "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("inputs") && err.contains("outputs"), "Expected error about invalid path segment, got: {err}" @@ -1826,9 +2051,12 @@ workflow: let main_path = dir.path().join("main.yaml"); std::fs::write(&main_path, &main_yaml).unwrap(); - let result = - wfe_yaml::load_workflow_with_includes(&main_yaml, &main_path, &HashMap::new()); - assert!(result.is_ok(), "Include single file should work, got: {:?}", result.err()); + let result = wfe_yaml::load_workflow_with_includes(&main_yaml, &main_path, &HashMap::new()); + assert!( + result.is_ok(), + "Include single file should work, got: {:?}", + result.err() + ); let workflows = result.unwrap(); assert_eq!(workflows.len(), 2); let ids: Vec<&str> = workflows.iter().map(|w| w.definition.id.as_str()).collect(); @@ -1887,9 +2115,12 @@ workflow: let main_path = dir.path().join("main.yaml"); std::fs::write(&main_path, main_yaml).unwrap(); - let result = - wfe_yaml::load_workflow_with_includes(main_yaml, &main_path, &HashMap::new()); - assert!(result.is_ok(), "Include multiple files should work, got: {:?}", result.err()); + let result = wfe_yaml::load_workflow_with_includes(main_yaml, &main_path, &HashMap::new()); + assert!( + result.is_ok(), + "Include multiple files should work, got: {:?}", + result.err() + ); let workflows = result.unwrap(); assert_eq!(workflows.len(), 3); } @@ -1931,9 +2162,12 @@ workflow: let main_path = dir.path().join("main.yaml"); std::fs::write(&main_path, main_yaml).unwrap(); - let result = - wfe_yaml::load_workflow_with_includes(main_yaml, &main_path, &HashMap::new()); - assert!(result.is_ok(), "Override should work, got: {:?}", result.err()); + let result = wfe_yaml::load_workflow_with_includes(main_yaml, &main_path, &HashMap::new()); + assert!( + result.is_ok(), + "Override should work, got: {:?}", + result.err() + ); let workflows = result.unwrap(); // Only 1 workflow since main takes precedence over included assert_eq!(workflows.len(), 1); @@ -1972,10 +2206,12 @@ workflow: let main_path = dir.path().join("main.yaml"); std::fs::write(&main_path, main_yaml).unwrap(); - let result = - wfe_yaml::load_workflow_with_includes(main_yaml, &main_path, &HashMap::new()); + let result = wfe_yaml::load_workflow_with_includes(main_yaml, &main_path, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("nonexistent") || err.contains("not found") || err.contains("No such file"), "Expected file not found error, got: {err}" @@ -2018,10 +2254,12 @@ workflow: let a_path = dir.path().join("a.yaml"); - let result = - wfe_yaml::load_workflow_with_includes(a_yaml, &a_path, &HashMap::new()); + let result = wfe_yaml::load_workflow_with_includes(a_yaml, &a_path, &HashMap::new()); assert!(result.is_err()); - let err = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") }; + let err = match result { + Err(e) => e.to_string(), + Ok(_) => panic!("expected error"), + }; assert!( err.contains("Circular include"), "Expected circular include error, got: {err}" @@ -2053,7 +2291,11 @@ workflow: equals: true "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); - assert!(result.is_ok(), "equals should work on all types, got: {:?}", result.err()); + assert!( + result.is_ok(), + "equals should work on all types, got: {:?}", + result.err() + ); } #[test] @@ -2074,7 +2316,11 @@ workflow: gte: 10 "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); - assert!(result.is_ok(), "gte on integer should pass, got: {:?}", result.err()); + assert!( + result.is_ok(), + "gte on integer should pass, got: {:?}", + result.err() + ); } #[test] @@ -2096,5 +2342,9 @@ workflow: gt: 5 "#; let result = load_single_workflow_from_str(yaml, &HashMap::new()); - assert!(result.is_ok(), "any type should allow gt, got: {:?}", result.err()); + assert!( + result.is_ok(), + "any type should allow gt, got: {:?}", + result.err() + ); } diff --git a/wfe/examples/pizza.rs b/wfe/examples/pizza.rs index bdbdeb3..dd85be6 100644 --- a/wfe/examples/pizza.rs +++ b/wfe/examples/pizza.rs @@ -43,11 +43,11 @@ use async_trait::async_trait; use serde::{Deserialize, Serialize}; use serde_json::json; +use wfe::WorkflowHostBuilder; use wfe::builder::WorkflowBuilder; use wfe::models::*; -use wfe::traits::step::{StepBody, StepExecutionContext}; use wfe::test_support::*; -use wfe::WorkflowHostBuilder; +use wfe::traits::step::{StepBody, StepExecutionContext}; // ============================================================================= // Workflow Data @@ -217,10 +217,7 @@ impl StepBody for AddToppings { async fn run(&mut self, ctx: &StepExecutionContext<'_>) -> wfe::Result { if let Some(item) = ctx.item { let pizza: Pizza = serde_json::from_value(item.clone()).unwrap_or_default(); - println!( - "[AddToppings] Layering: {}", - pizza.toppings.join(", ") - ); + println!("[AddToppings] Layering: {}", pizza.toppings.join(", ")); if let Some(ref instructions) = pizza.special_instructions { println!("[AddToppings] Special: {}", instructions); } @@ -334,7 +331,10 @@ impl StepBody for DispatchDriver { let order: PizzaOrder = serde_json::from_value(ctx.workflow.data.clone())?; println!( "[DispatchDriver] Driver en route to {}", - order.delivery_address.as_deref().unwrap_or("unknown address") + order + .delivery_address + .as_deref() + .unwrap_or("unknown address") ); Ok(ExecutionResult::next()) } @@ -347,10 +347,7 @@ struct RingCounterBell; impl StepBody for RingCounterBell { async fn run(&mut self, ctx: &StepExecutionContext<'_>) -> wfe::Result { let order: PizzaOrder = serde_json::from_value(ctx.workflow.data.clone())?; - println!( - "[RingCounterBell] DING! Order for {}!", - order.customer_name - ); + println!("[RingCounterBell] DING! Order for {}!", order.customer_name); Ok(ExecutionResult::next()) } } @@ -386,54 +383,51 @@ fn build_pizza_workflow() -> WorkflowDefinition { WorkflowBuilder::::new() // 1. Validate the order .start_with::() - .name("Validate Order") - + .name("Validate Order") // 2. Charge payment (saga: refund if anything fails downstream) .then::() - .name("Charge Payment") - .compensate_with::() - + .name("Charge Payment") + .compensate_with::() // 3. Prep toppings in parallel - .parallel(|p| p - .branch(|b| { b.add_step(std::any::type_name::()); }) - .branch(|b| { b.add_step(std::any::type_name::()); }) - .branch(|b| { b.add_step(std::any::type_name::()); }) - ) - + .parallel(|p| { + p.branch(|b| { + b.add_step(std::any::type_name::()); + }) + .branch(|b| { + b.add_step(std::any::type_name::()); + }) + .branch(|b| { + b.add_step(std::any::type_name::()); + }) + }) // 4. Assemble each pizza (with quality check that retries) .then::() - .name("Stretch Dough") + .name("Stretch Dough") .then::() - .name("Add Toppings") + .name("Add Toppings") .then::() - .name("Quality Check") - .on_error(ErrorBehavior::Retry { - interval: Duration::from_millis(100), - max_retries: 3, - }) - + .name("Quality Check") + .on_error(ErrorBehavior::Retry { + interval: Duration::from_millis(100), + max_retries: 3, + }) // 5. Fire into the oven .then::() - .name("Fire Oven") - + .name("Fire Oven") // 6. Wait for oven timer (external event) .wait_for("oven.timer", "oven-1") - .name("Wait for Oven Timer") - + .name("Wait for Oven Timer") // 7. Let pizzas cool (delay) .delay(Duration::from_millis(50)) - .name("Cooling Rest") - + .name("Cooling Rest") // 8. Delivery decision .then::() - .name("Check Delivery") + .name("Check Delivery") .then::() - .name("Dispatch or Pickup") - + .name("Dispatch or Pickup") // 9. Done! .then::() - .name("Complete Order") - + .name("Complete Order") .end_workflow() .build("pizza-workflow", 1) } @@ -542,7 +536,10 @@ async fn main() -> std::result::Result<(), Box> { match instance.status { WorkflowStatus::Complete | WorkflowStatus::Terminated => break instance, _ if tokio::time::Instant::now() > deadline => { - println!("\nWorkflow still running after timeout. Status: {:?}", instance.status); + println!( + "\nWorkflow still running after timeout. Status: {:?}", + instance.status + ); break instance; } _ => tokio::time::sleep(Duration::from_millis(100)).await, diff --git a/wfe/examples/run_pipeline.rs b/wfe/examples/run_pipeline.rs index ae5e7cf..452b687 100644 --- a/wfe/examples/run_pipeline.rs +++ b/wfe/examples/run_pipeline.rs @@ -18,9 +18,9 @@ use std::time::Duration; use serde_json::json; -use wfe::models::WorkflowStatus; -use wfe::test_support::{InMemoryLockProvider, InMemoryQueueProvider, InMemoryPersistenceProvider}; use wfe::WorkflowHostBuilder; +use wfe::models::WorkflowStatus; +use wfe::test_support::{InMemoryLockProvider, InMemoryPersistenceProvider, InMemoryQueueProvider}; #[tokio::main] async fn main() -> Result<(), Box> { @@ -30,7 +30,7 @@ async fn main() -> Result<(), Box> { .with_timer(tracing_subscriber::fmt::time::uptime()) .with_env_filter( std::env::var("RUST_LOG") - .unwrap_or_else(|_| "wfe_core=info,wfe=info,run_pipeline=info".into()) + .unwrap_or_else(|_| "wfe_core=info,wfe=info,run_pipeline=info".into()), ) .init(); @@ -40,9 +40,7 @@ async fn main() -> Result<(), Box> { .expect("usage: run_pipeline "); // Read config from WFE_CONFIG env var (JSON map), merged over sensible defaults. - let cwd = std::env::current_dir()? - .to_string_lossy() - .to_string(); + let cwd = std::env::current_dir()?.to_string_lossy().to_string(); // Defaults for every ((var)) referenced in the YAML. let mut config: HashMap = HashMap::from([ @@ -151,7 +149,13 @@ async fn main() -> Result<(), Box> { // Print workflow data (contains outputs from all steps). if let Some(obj) = final_instance.data.as_object() { println!("\nKey outputs:"); - for key in ["version", "all_tests_passed", "coverage", "published", "released"] { + for key in [ + "version", + "all_tests_passed", + "coverage", + "published", + "released", + ] { if let Some(val) = obj.get(key) { println!(" {key}: {val}"); } diff --git a/wfe/src/host_builder.rs b/wfe/src/host_builder.rs index 9d14ad8..a06d489 100644 --- a/wfe/src/host_builder.rs +++ b/wfe/src/host_builder.rs @@ -3,12 +3,12 @@ use std::sync::Arc; use tokio::sync::RwLock; use tokio_util::sync::CancellationToken; +use wfe_core::WfeError; use wfe_core::executor::{StepRegistry, WorkflowExecutor}; use wfe_core::traits::{ DistributedLockProvider, LifecyclePublisher, PersistenceProvider, QueueProvider, SearchIndex, ServiceProvider, }; -use wfe_core::WfeError; use crate::host::WorkflowHost; use crate::registry::InMemoryWorkflowRegistry; @@ -86,13 +86,20 @@ impl WorkflowHostBuilder { /// Returns an error if persistence, lock_provider, or queue_provider have not been set. pub fn build(self) -> wfe_core::Result { let persistence = self.persistence.ok_or_else(|| { - WfeError::Other("PersistenceProvider is required. Call .use_persistence() before .build().".into()) + WfeError::Other( + "PersistenceProvider is required. Call .use_persistence() before .build().".into(), + ) })?; let lock_provider = self.lock_provider.ok_or_else(|| { - WfeError::Other("DistributedLockProvider is required. Call .use_lock_provider() before .build().".into()) + WfeError::Other( + "DistributedLockProvider is required. Call .use_lock_provider() before .build()." + .into(), + ) })?; let queue_provider = self.queue_provider.ok_or_else(|| { - WfeError::Other("QueueProvider is required. Call .use_queue_provider() before .build().".into()) + WfeError::Other( + "QueueProvider is required. Call .use_queue_provider() before .build().".into(), + ) })?; let mut executor = WorkflowExecutor::new( diff --git a/wfe/src/purger.rs b/wfe/src/purger.rs index 78081d2..e04752c 100644 --- a/wfe/src/purger.rs +++ b/wfe/src/purger.rs @@ -1,8 +1,8 @@ use chrono::{DateTime, Utc}; +use wfe_core::Result; use wfe_core::models::WorkflowStatus; use wfe_core::traits::PersistenceProvider; -use wfe_core::Result; /// Purge workflows matching a given status that were created before `older_than`. /// diff --git a/wfe/tests/e2e_compensation.rs b/wfe/tests/e2e_compensation.rs index 93e0597..ade6ceb 100644 --- a/wfe/tests/e2e_compensation.rs +++ b/wfe/tests/e2e_compensation.rs @@ -3,17 +3,15 @@ use std::time::Duration; use async_trait::async_trait; +use wfe::WorkflowHostBuilder; use wfe::models::{ - ErrorBehavior, ExecutionResult, PointerStatus, StepOutcome, WorkflowDefinition, - WorkflowStep, + ErrorBehavior, ExecutionResult, PointerStatus, StepOutcome, WorkflowDefinition, WorkflowStep, }; use wfe::traits::step::{StepBody, StepExecutionContext}; -use wfe::WorkflowHostBuilder; use wfe_core::test_support::{ InMemoryLockProvider, InMemoryPersistenceProvider, InMemoryQueueProvider, }; - /// Step 1: succeeds normally. #[derive(Default)] struct SucceedingStep; @@ -32,9 +30,7 @@ struct FailingStep; #[async_trait] impl StepBody for FailingStep { async fn run(&mut self, _ctx: &StepExecutionContext<'_>) -> wfe_core::Result { - Err(wfe_core::WfeError::StepExecution( - "Step2 failed".into(), - )) + Err(wfe_core::WfeError::StepExecution("Step2 failed".into())) } } @@ -86,7 +82,8 @@ async fn compensation_step_runs_on_failure() { .use_persistence(persistence.clone() as Arc) .use_lock_provider(lock as Arc) .use_queue_provider(queue as Arc) - .build().unwrap(); + .build() + .unwrap(); let def = build_compensation_definition(); host.register_step::().await; diff --git a/wfe/tests/e2e_delay.rs b/wfe/tests/e2e_delay.rs index cbf156c..669b6c5 100644 --- a/wfe/tests/e2e_delay.rs +++ b/wfe/tests/e2e_delay.rs @@ -3,16 +3,13 @@ use std::time::Duration; use async_trait::async_trait; -use wfe::models::{ - ExecutionResult, StepOutcome, WorkflowDefinition, WorkflowStatus, WorkflowStep, -}; +use wfe::models::{ExecutionResult, StepOutcome, WorkflowDefinition, WorkflowStatus, WorkflowStep}; use wfe::traits::step::{StepBody, StepExecutionContext}; use wfe::{WorkflowHostBuilder, run_workflow_sync}; use wfe_core::test_support::{ InMemoryLockProvider, InMemoryPersistenceProvider, InMemoryQueueProvider, }; - /// A step that sleeps for a very short duration (10ms), then proceeds. /// Tracks whether it has already slept via persistence_data. #[derive(Default)] @@ -74,7 +71,8 @@ async fn delay_step_completes_after_sleep() { .use_persistence(persistence.clone() as Arc) .use_lock_provider(lock as Arc) .use_queue_provider(queue as Arc) - .build().unwrap(); + .build() + .unwrap(); let def = build_delay_definition(); host.register_step::().await; @@ -100,7 +98,10 @@ async fn delay_step_completes_after_sleep() { .iter() .filter(|p| p.status == wfe::models::PointerStatus::Complete) .count(); - assert_eq!(complete_count, 2, "Expected both delay step and after-delay step to complete"); + assert_eq!( + complete_count, 2, + "Expected both delay step and after-delay step to complete" + ); host.stop().await; } diff --git a/wfe/tests/e2e_error_handling.rs b/wfe/tests/e2e_error_handling.rs index fe2799a..1eb9ce1 100644 --- a/wfe/tests/e2e_error_handling.rs +++ b/wfe/tests/e2e_error_handling.rs @@ -4,8 +4,8 @@ use std::time::Duration; use async_trait::async_trait; use wfe::models::{ - ErrorBehavior, ExecutionResult, PointerStatus, StepOutcome, WorkflowDefinition, - WorkflowStatus, WorkflowStep, + ErrorBehavior, ExecutionResult, PointerStatus, StepOutcome, WorkflowDefinition, WorkflowStatus, + WorkflowStep, }; use wfe::traits::step::{StepBody, StepExecutionContext}; use wfe::{WorkflowHostBuilder, run_workflow_sync}; @@ -13,7 +13,6 @@ use wfe_core::test_support::{ InMemoryLockProvider, InMemoryPersistenceProvider, InMemoryQueueProvider, }; - /// A step that fails on the first attempt but succeeds on retry. /// Uses retry_count on the execution pointer to track attempts. #[derive(Default)] diff --git a/wfe/tests/e2e_events.rs b/wfe/tests/e2e_events.rs index 0f172e4..27da539 100644 --- a/wfe/tests/e2e_events.rs +++ b/wfe/tests/e2e_events.rs @@ -4,16 +4,15 @@ use std::time::Duration; use async_trait::async_trait; use chrono::Utc; +use wfe::WorkflowHostBuilder; use wfe::models::{ ExecutionResult, PointerStatus, StepOutcome, WorkflowDefinition, WorkflowStatus, WorkflowStep, }; use wfe::traits::step::{StepBody, StepExecutionContext}; -use wfe::WorkflowHostBuilder; use wfe_core::test_support::{ InMemoryLockProvider, InMemoryPersistenceProvider, InMemoryQueueProvider, }; - /// A step that waits for "approval" event with key "request-1". #[derive(Default)] struct WaitForApprovalStep; @@ -68,7 +67,8 @@ async fn event_workflow_waits_then_resumes_on_publish() { .use_persistence(persistence.clone() as Arc) .use_lock_provider(lock as Arc) .use_queue_provider(queue as Arc) - .build().unwrap(); + .build() + .unwrap(); let def = build_event_definition(); host.register_step::().await; @@ -134,7 +134,10 @@ async fn event_workflow_waits_then_resumes_on_publish() { .execution_pointers .iter() .find(|p| p.event_data.is_some()); - assert!(wait_pointer.is_some(), "Expected event_data to be set on the waiting pointer"); + assert!( + wait_pointer.is_some(), + "Expected event_data to be set on the waiting pointer" + ); let event_data = wait_pointer.unwrap().event_data.as_ref().unwrap(); assert_eq!(event_data, &serde_json::json!({"approved": true})); diff --git a/wfe/tests/e2e_foreach.rs b/wfe/tests/e2e_foreach.rs index 5ea40f1..101b752 100644 --- a/wfe/tests/e2e_foreach.rs +++ b/wfe/tests/e2e_foreach.rs @@ -99,7 +99,8 @@ async fn foreach_processes_all_items() { .use_persistence(persistence as Arc) .use_lock_provider(lock as Arc) .use_queue_provider(queue as Arc) - .build().unwrap(); + .build() + .unwrap(); host.register_step::().await; host.register_step::().await; diff --git a/wfe/tests/e2e_linear.rs b/wfe/tests/e2e_linear.rs index 6362f42..421347f 100644 --- a/wfe/tests/e2e_linear.rs +++ b/wfe/tests/e2e_linear.rs @@ -53,7 +53,8 @@ async fn linear_three_step_workflow_completes() { .use_persistence(persistence.clone() as Arc) .use_lock_provider(lock as Arc) .use_queue_provider(queue as Arc) - .build().unwrap(); + .build() + .unwrap(); host.register_step::().await; host.register_workflow_definition(def).await; diff --git a/wfe/tests/e2e_parallel.rs b/wfe/tests/e2e_parallel.rs index e6e6a2b..799e685 100644 --- a/wfe/tests/e2e_parallel.rs +++ b/wfe/tests/e2e_parallel.rs @@ -13,7 +13,6 @@ use wfe_core::test_support::{ InMemoryLockProvider, InMemoryPersistenceProvider, InMemoryQueueProvider, }; - /// Initial step before parallel. #[derive(Default)] struct StartStep; @@ -117,7 +116,8 @@ async fn parallel_branches_both_complete() { .use_persistence(persistence.clone() as Arc) .use_lock_provider(lock as Arc) .use_queue_provider(queue as Arc) - .build().unwrap(); + .build() + .unwrap(); host.register_step::().await; host.register_step::().await; @@ -144,7 +144,10 @@ async fn parallel_branches_both_complete() { .iter() .filter(|p| p.status == PointerStatus::Complete && !p.scope.is_empty()) .count(); - assert_eq!(branch_completions, 2, "Expected both parallel branches to complete"); + assert_eq!( + branch_completions, 2, + "Expected both parallel branches to complete" + ); host.stop().await; } diff --git a/wfe/tests/e2e_versioning.rs b/wfe/tests/e2e_versioning.rs index dda260b..8b17e87 100644 --- a/wfe/tests/e2e_versioning.rs +++ b/wfe/tests/e2e_versioning.rs @@ -12,7 +12,6 @@ use wfe_core::test_support::{ InMemoryLockProvider, InMemoryPersistenceProvider, InMemoryQueueProvider, }; - /// Version 1 uses a single step. #[derive(Default)] struct V1Step; @@ -75,14 +74,17 @@ async fn version_1_uses_v1_definition() { .use_persistence(persistence.clone() as Arc) .use_lock_provider(lock as Arc) .use_queue_provider(queue as Arc) - .build().unwrap(); + .build() + .unwrap(); host.register_step::().await; host.register_step::().await; host.register_step::().await; - host.register_workflow_definition(build_v1_definition()).await; - host.register_workflow_definition(build_v2_definition()).await; + host.register_workflow_definition(build_v1_definition()) + .await; + host.register_workflow_definition(build_v2_definition()) + .await; host.start().await.unwrap(); @@ -121,14 +123,17 @@ async fn version_2_uses_v2_definition() { .use_persistence(persistence.clone() as Arc) .use_lock_provider(lock as Arc) .use_queue_provider(queue as Arc) - .build().unwrap(); + .build() + .unwrap(); host.register_step::().await; host.register_step::().await; host.register_step::().await; - host.register_workflow_definition(build_v1_definition()).await; - host.register_workflow_definition(build_v2_definition()).await; + host.register_workflow_definition(build_v1_definition()) + .await; + host.register_workflow_definition(build_v2_definition()) + .await; host.start().await.unwrap(); @@ -167,14 +172,17 @@ async fn both_versions_coexist_and_run_independently() { .use_persistence(persistence.clone() as Arc) .use_lock_provider(lock as Arc) .use_queue_provider(queue as Arc) - .build().unwrap(); + .build() + .unwrap(); host.register_step::().await; host.register_step::().await; host.register_step::().await; - host.register_workflow_definition(build_v1_definition()).await; - host.register_workflow_definition(build_v2_definition()).await; + host.register_workflow_definition(build_v1_definition()) + .await; + host.register_workflow_definition(build_v2_definition()) + .await; host.start().await.unwrap(); @@ -197,8 +205,7 @@ async fn both_versions_coexist_and_run_independently() { } let inst_v1 = host.get_workflow(&id_v1).await.unwrap(); let inst_v2 = host.get_workflow(&id_v2).await.unwrap(); - if inst_v1.status == WorkflowStatus::Complete - && inst_v2.status == WorkflowStatus::Complete + if inst_v1.status == WorkflowStatus::Complete && inst_v2.status == WorkflowStatus::Complete { break; } diff --git a/wfe/tests/e2e_while.rs b/wfe/tests/e2e_while.rs index e243a4f..3744082 100644 --- a/wfe/tests/e2e_while.rs +++ b/wfe/tests/e2e_while.rs @@ -112,7 +112,8 @@ async fn while_loop_runs_three_iterations() { .use_persistence(persistence as Arc) .use_lock_provider(lock as Arc) .use_queue_provider(queue as Arc) - .build().unwrap(); + .build() + .unwrap(); host.register_step::().await; host.register_step::().await; diff --git a/wfe/tests/host_tests.rs b/wfe/tests/host_tests.rs index 99cb749..29f2952 100644 --- a/wfe/tests/host_tests.rs +++ b/wfe/tests/host_tests.rs @@ -8,12 +8,12 @@ use wfe::models::{ ExecutionResult, PointerStatus, StepOutcome, WorkflowDefinition, WorkflowInstance, WorkflowStatus, WorkflowStep, }; -use wfe::traits::step::{StepBody, StepExecutionContext}; use wfe::traits::search::{Page, SearchFilter, SearchIndex, WorkflowSearchResult}; +use wfe::traits::step::{StepBody, StepExecutionContext}; use wfe::{WorkflowHost, WorkflowHostBuilder}; use wfe_core::test_support::{ - InMemoryLockProvider, InMemoryLifecyclePublisher, InMemoryPersistenceProvider, + InMemoryLifecyclePublisher, InMemoryLockProvider, InMemoryPersistenceProvider, InMemoryQueueProvider, }; @@ -89,7 +89,8 @@ fn build_host() -> (WorkflowHost, Arc) { .use_persistence(persistence.clone() as Arc) .use_lock_provider(lock as Arc) .use_queue_provider(queue as Arc) - .build().unwrap(); + .build() + .unwrap(); (host, persistence) } @@ -109,7 +110,8 @@ fn build_host_with_lifecycle() -> ( .use_lock_provider(lock as Arc) .use_queue_provider(queue as Arc) .use_lifecycle(lifecycle.clone() as Arc) - .build().unwrap(); + .build() + .unwrap(); (host, persistence, lifecycle) } @@ -125,7 +127,8 @@ fn build_host_with_search() -> (WorkflowHost, Arc) .use_lock_provider(lock as Arc) .use_queue_provider(queue as Arc) .use_search(search as Arc) - .build().unwrap(); + .build() + .unwrap(); (host, persistence) } @@ -147,7 +150,8 @@ fn build_host_full() -> ( .use_queue_provider(queue as Arc) .use_lifecycle(lifecycle.clone() as Arc) .use_search(search as Arc) - .build().unwrap(); + .build() + .unwrap(); (host, persistence, lifecycle) } @@ -475,11 +479,7 @@ async fn host_register_workflow_via_builder() { let def = host .register_workflow::( - &|builder| { - builder - .start_with::() - .end_workflow() - }, + &|builder| builder.start_with::().end_workflow(), "builder-workflow", 1, ) @@ -598,12 +598,7 @@ async fn host_terminate_complete_workflow_returns_false() { #[tokio::test] async fn purger_stub_returns_ok() { let persistence = InMemoryPersistenceProvider::new(); - let result = wfe::purge_workflows( - &persistence, - WorkflowStatus::Complete, - Utc::now(), - ) - .await; + let result = wfe::purge_workflows(&persistence, WorkflowStatus::Complete, Utc::now()).await; assert!(result.is_ok()); } diff --git a/wfe/tests/nested_workflow_tests.rs b/wfe/tests/nested_workflow_tests.rs index 5b5482f..270d866 100644 --- a/wfe/tests/nested_workflow_tests.rs +++ b/wfe/tests/nested_workflow_tests.rs @@ -4,11 +4,9 @@ use std::time::Duration; use async_trait::async_trait; use serde_json::json; -use wfe::models::{ - ExecutionResult, StepOutcome, WorkflowDefinition, WorkflowStatus, WorkflowStep, -}; -use wfe::traits::step::{StepBody, StepExecutionContext}; use wfe::WorkflowHostBuilder; +use wfe::models::{ExecutionResult, StepOutcome, WorkflowDefinition, WorkflowStatus, WorkflowStep}; +use wfe::traits::step::{StepBody, StepExecutionContext}; use wfe_core::primitives::sub_workflow::SubWorkflowStep; use wfe_core::test_support::{ InMemoryLockProvider, InMemoryPersistenceProvider, InMemoryQueueProvider,