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.
This commit is contained in:
2026-04-07 18:44:21 +01:00
parent 3915bcc1ec
commit 02a574b24e
102 changed files with 2467 additions and 1307 deletions

View File

@@ -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<T>(request: &Request<T>) -> Result<&str, Status> {
}
/// Map JWK key algorithm to jsonwebtoken Algorithm.
fn key_algorithm_to_jwt_algorithm(
ka: jsonwebtoken::jwk::KeyAlgorithm,
) -> Option<Algorithm> {
fn key_algorithm_to_jwt_algorithm(ka: jsonwebtoken::jwk::KeyAlgorithm) -> Option<Algorithm> {
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<jsonwebtoken::jwk::Jwk>, 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);

View File

@@ -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"
);
}
}

View File

@@ -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]

View File

@@ -304,8 +304,8 @@ mod tests {
// ── OpenSearch integration tests ────────────────────────────────
fn opensearch_url() -> Option<String> {
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)

View File

@@ -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();

View File

@@ -152,9 +152,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
wfe_service = wfe_service.with_log_search(index);
}
let (health_reporter, health_service) = tonic_health::server::health_reporter();
health_reporter
.set_serving::<WfeServer<WfeService>>()
.await;
health_reporter.set_serving::<WfeServer<WfeService>>().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<dyn std::error::Error>> {
// 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"),
)
}

View File

@@ -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('.') {