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:
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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"),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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('.') {
|
||||
|
||||
Reference in New Issue
Block a user