Skip to content
This repository was archived by the owner on Feb 25, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 40 additions & 58 deletions components/chainhook-cli/src/service/http_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use chainhook_sdk::{
};
use hiro_system_kit::slog;
use redis::{Commands, Connection};
use rocket::serde::json::{json, Json, Value as JsonValue};
use rocket::{http::Status, response::status::Custom, serde::json::{json, Json, Value as JsonValue}};
use rocket::State;
use rocket::{
config::{self, Config, LogLevel},
Expand Down Expand Up @@ -70,33 +70,47 @@ pub async fn start_predicate_api_server(
Ok(predicate_api_shutdown)
}

fn success_response(result: JsonValue) -> Result<Json<JsonValue>, Custom<Json<JsonValue>>> {
Ok(Json(json!({
"status": 200,
"result": result,
})))
}

fn error_response(
message: String,
status: Status,
) -> Result<Json<JsonValue>, Custom<Json<JsonValue>>> {
Err(Custom(
status,
Json(json!({
"status": status.code,
"result": message,
})),
))
}

#[openapi(tag = "Health Check")]
#[get("/ping")]
fn handle_ping(ctx: &State<Context>) -> Json<JsonValue> {
fn handle_ping(ctx: &State<Context>) -> Result<Json<JsonValue>, Custom<Json<JsonValue>>> {
ctx.try_log(|logger| slog::info!(logger, "Handling HTTP GET /ping"));
Json(json!({
"status": 200,
"result": "chainhook service up and running",
}))
success_response("chainhook service up and running".into())
}

#[openapi(tag = "Managing Predicates")]
#[get("/v1/chainhooks", format = "application/json")]
fn handle_get_predicates(
api_config: &State<PredicatesApiConfig>,
ctx: &State<Context>,
) -> Json<JsonValue> {
) -> Result<Json<JsonValue>, Custom<Json<JsonValue>>> {
ctx.try_log(|logger| slog::info!(logger, "Handling HTTP GET /v1/chainhooks"));
match open_readwrite_predicates_db_conn(api_config) {
Ok(mut predicates_db_conn) => {
let predicates = match get_entries_from_predicates_db(&mut predicates_db_conn, ctx) {
Ok(predicates) => predicates,
Err(e) => {
ctx.try_log(|logger| slog::warn!(logger, "unable to retrieve predicates: {e}"));
return Json(json!({
"status": 500,
"message": "unable to retrieve predicates",
}));
return error_response("unable to retrieve predicates".into(), Status::InternalServerError);
}
};

Expand All @@ -105,15 +119,9 @@ fn handle_get_predicates(
.map(|(p, s)| serialized_predicate_with_status(p, s))
.collect::<Vec<_>>();

Json(json!({
"status": 200,
"result": serialized_predicates
}))
success_response(serialized_predicates.into())
}
Err(e) => Json(json!({
"status": 500,
"message": e,
})),
Err(e) => error_response(e.to_string(), Status::InternalServerError),
}
}

Expand All @@ -124,22 +132,16 @@ fn handle_create_predicate(
api_config: &State<PredicatesApiConfig>,
background_job_tx: &State<Arc<Mutex<Sender<ObserverCommand>>>>,
ctx: &State<Context>,
) -> Json<JsonValue> {
) -> Result<Json<JsonValue>, Custom<Json<JsonValue>>> {
ctx.try_log(|logger| slog::info!(logger, "Handling HTTP POST /v1/chainhooks"));
let predicate = match predicate {
Err(e) => {
return Json(json!({
"status": 422,
"error": e.to_string(),
}))
return error_response(e.to_string(), Status::UnprocessableEntity);
}
Ok(predicate) => {
let predicate = predicate.into_inner();
if let Err(e) = predicate.validate() {
return Json(json!({
"status": 422,
"error": e,
}));
return error_response(e.to_string(), Status::UnprocessableEntity);
}
predicate
}
Expand All @@ -153,10 +155,7 @@ fn handle_create_predicate(
&mut predicates_db_conn,
ctx,
) {
return Json(json!({
"status": 409,
"error": "Predicate uuid already in use",
}))
return error_response("Predicate uuid already in use".into(), Status::Conflict);
}
}

Expand All @@ -165,10 +164,7 @@ fn handle_create_predicate(
let _ = tx.send(ObserverCommand::RegisterPredicate(predicate));
};

Json(json!({
"status": 200,
"result": predicate_uuid,
}))
success_response(predicate_uuid.into())
}

#[openapi(tag = "Managing Predicates")]
Expand All @@ -177,7 +173,7 @@ fn handle_get_predicate(
predicate_uuid: String,
api_config: &State<PredicatesApiConfig>,
ctx: &State<Context>,
) -> Json<JsonValue> {
) -> Result<Json<JsonValue>, Custom<Json<JsonValue>>> {
ctx.try_log(|logger| {
slog::info!(
logger,
Expand All @@ -195,21 +191,13 @@ fn handle_get_predicate(
) {
Ok(Some(predicate_with_status)) => predicate_with_status,
_ => {
return Json(json!({
"status": 404,
}))
return error_response("Predicate not found".into(), Status::NotFound);
}
};
let result = serialized_predicate_with_status(&predicate, &status);
Json(json!({
"status": 200,
"result": result
}))
success_response(result)
}
Err(e) => Json(json!({
"status": 500,
"message": e,
})),
Err(e) => error_response(e.to_string(), Status::InternalServerError),
}
}

Expand All @@ -219,7 +207,7 @@ fn handle_delete_stacks_predicate(
predicate_uuid: String,
background_job_tx: &State<Arc<Mutex<Sender<ObserverCommand>>>>,
ctx: &State<Context>,
) -> Json<JsonValue> {
) -> Result<Json<JsonValue>, Custom<Json<JsonValue>>> {
ctx.try_log(|logger| {
slog::info!(
logger,
Expand All @@ -233,10 +221,7 @@ fn handle_delete_stacks_predicate(
let _ = tx.send(ObserverCommand::DeregisterStacksPredicate(predicate_uuid));
};

Json(json!({
"status": 200,
"result": "Ok",
}))
success_response("Ok".into())
}

#[openapi(tag = "Managing Predicates")]
Expand All @@ -245,7 +230,7 @@ fn handle_delete_bitcoin_predicate(
predicate_uuid: String,
background_job_tx: &State<Arc<Mutex<Sender<ObserverCommand>>>>,
ctx: &State<Context>,
) -> Json<JsonValue> {
) -> Result<Json<JsonValue>, Custom<Json<JsonValue>>> {
ctx.try_log(|logger| {
slog::info!(
logger,
Expand All @@ -259,10 +244,7 @@ fn handle_delete_bitcoin_predicate(
let _ = tx.send(ObserverCommand::DeregisterBitcoinPredicate(predicate_uuid));
};

Json(json!({
"status": 200,
"result": "Ok",
}))
success_response("Ok".into())
}

pub fn get_entry_from_predicates_db(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,14 @@ pub async fn get_predicate_status(uuid: &str, port: u16) -> Result<PredicateStat
return serde_json::from_value(status.clone())
.map_err(|e| format!("failed to parse status {}", e));
}
None => return Err("no status field on get predicate result".to_string()),
None => {
attempts += 1;
if attempts == 10 {
return Err("no status field on get predicate result".to_string());
} else {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
},
},
None => {
attempts += 1;
Expand Down
17 changes: 0 additions & 17 deletions components/chainhook-cli/src/service/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use chainhook_sdk::types::Chain;
use chainhook_sdk::utils::Context;
use rocket::serde::json::Value as JsonValue;
use rocket::Shutdown;
use std::fs::{self};
use std::net::TcpListener;
use std::path::PathBuf;
use std::thread::sleep;
Expand All @@ -29,8 +28,6 @@ use crate::service::tests::helpers::mock_stacks_node::create_burn_fork_at;
use crate::service::{PredicateStatus, PredicateStatus::*, ScanningData, StreamingData};
use crate::storage::{get_all_unconfirmed_blocks, open_readonly_stacks_db_conn};

use super::http_api::document_predicate_api_server;

pub mod helpers;
mod observer_tests;
mod runloop_tests;
Expand Down Expand Up @@ -832,20 +829,6 @@ async fn register_predicate_responds_409_if_uuid_in_use() -> Result<(), String>
Ok(())
}

#[test]
fn it_generates_open_api_spec() {
let new_spec = document_predicate_api_server().unwrap();

let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("../../docs/chainhook-openapi.json");
let current_spec = fs::read_to_string(path).unwrap();

assert_eq!(
current_spec, new_spec,
"breaking change detected: open api spec has been updated"
)
}

#[tokio::test]
#[cfg_attr(not(feature = "redis_tests"), ignore)]
async fn it_seeds_block_pool_on_startup() -> Result<(), String> {
Expand Down
Loading