From 26a4443a78ab72c7712f79d4fbc6b7b4fffbd295 Mon Sep 17 00:00:00 2001
From: centra
Date: Fri, 19 Dec 2025 17:04:39 +0900
Subject: [PATCH] fix(chainfire): Use wildcard route for KV endpoints
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Replace :key with *key pattern to avoid axum route conflict.
Adds wildcard handler functions for GET/PUT/DELETE that properly
handle both simple keys and namespaced keys with slashes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5
---
chainfire/crates/chainfire-server/src/rest.rs | 92 ++++++++++++++++++-
1 file changed, 91 insertions(+), 1 deletion(-)
diff --git a/chainfire/crates/chainfire-server/src/rest.rs b/chainfire/crates/chainfire-server/src/rest.rs
index c805a75..bd06595 100644
--- a/chainfire/crates/chainfire-server/src/rest.rs
+++ b/chainfire/crates/chainfire-server/src/rest.rs
@@ -134,7 +134,8 @@ pub struct PrefixQuery {
/// Build the REST API router
pub fn build_router(state: RestApiState) -> Router {
Router::new()
- .route("/api/v1/kv/:key", get(get_kv).put(put_kv).delete(delete_kv))
+ // Wildcard route handles all keys (with or without slashes)
+ .route("/api/v1/kv/*key", get(get_kv_wildcard).put(put_kv_wildcard).delete(delete_kv_wildcard))
.route("/api/v1/kv", get(list_kv))
.route("/api/v1/cluster/status", get(cluster_status))
.route("/api/v1/cluster/members", post(add_member))
@@ -222,6 +223,95 @@ async fn delete_kv(
))
}
+/// GET /api/v1/kv/*key - Get value (wildcard for all keys)
+async fn get_kv_wildcard(
+ State(state): State,
+ Path(key): Path,
+) -> Result>, (StatusCode, Json)> {
+ // Use key as-is for simple keys, prepend / for namespaced keys
+ // Keys like "testkey" stay as "testkey", keys like "flaredb/stores/1" become "/flaredb/stores/1"
+ let full_key = if key.contains('/') {
+ format!("/{}", key)
+ } else {
+ key.clone()
+ };
+ let sm = state.raft.state_machine();
+ let key_bytes = full_key.as_bytes().to_vec();
+
+ let results = sm.kv()
+ .get(&key_bytes)
+ .map_err(|e| error_response(StatusCode::INTERNAL_SERVER_ERROR, "INTERNAL_ERROR", &e.to_string()))?;
+
+ let value = results
+ .into_iter()
+ .next()
+ .ok_or_else(|| error_response(StatusCode::NOT_FOUND, "NOT_FOUND", "Key not found"))?;
+
+ Ok(Json(SuccessResponse::new(GetResponse {
+ key: full_key,
+ value: String::from_utf8_lossy(&value.value).to_string(),
+ })))
+}
+
+/// PUT /api/v1/kv/*key - Put value (wildcard for all keys)
+async fn put_kv_wildcard(
+ State(state): State,
+ Path(key): Path,
+ Json(req): Json,
+) -> Result<(StatusCode, Json>), (StatusCode, Json)> {
+ // Use key as-is for simple keys, prepend / for namespaced keys
+ let full_key = if key.contains('/') {
+ format!("/{}", key)
+ } else {
+ key.clone()
+ };
+ let command = RaftCommand::Put {
+ key: full_key.as_bytes().to_vec(),
+ value: req.value.as_bytes().to_vec(),
+ lease_id: None,
+ prev_kv: false,
+ };
+
+ state
+ .raft
+ .client_write(command)
+ .await
+ .map_err(|e| error_response(StatusCode::INTERNAL_SERVER_ERROR, "INTERNAL_ERROR", &e.to_string()))?;
+
+ Ok((
+ StatusCode::OK,
+ Json(SuccessResponse::new(serde_json::json!({ "key": full_key, "success": true }))),
+ ))
+}
+
+/// DELETE /api/v1/kv/*key - Delete key (wildcard for all keys)
+async fn delete_kv_wildcard(
+ State(state): State,
+ Path(key): Path,
+) -> Result<(StatusCode, Json>), (StatusCode, Json)> {
+ // Use key as-is for simple keys, prepend / for namespaced keys
+ let full_key = if key.contains('/') {
+ format!("/{}", key)
+ } else {
+ key.clone()
+ };
+ let command = RaftCommand::Delete {
+ key: full_key.as_bytes().to_vec(),
+ prev_kv: false,
+ };
+
+ state
+ .raft
+ .client_write(command)
+ .await
+ .map_err(|e| error_response(StatusCode::INTERNAL_SERVER_ERROR, "INTERNAL_ERROR", &e.to_string()))?;
+
+ Ok((
+ StatusCode::OK,
+ Json(SuccessResponse::new(serde_json::json!({ "key": full_key, "success": true }))),
+ ))
+}
+
/// GET /api/v1/kv?prefix={prefix} - Range scan
async fn list_kv(
State(state): State,