fix(chainfire): Use wildcard route for KV endpoints
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 <noreply@anthropic.com>
This commit is contained in:
parent
4bfe75a1d7
commit
26a4443a78
1 changed files with 91 additions and 1 deletions
|
|
@ -134,7 +134,8 @@ pub struct PrefixQuery {
|
||||||
/// Build the REST API router
|
/// Build the REST API router
|
||||||
pub fn build_router(state: RestApiState) -> Router {
|
pub fn build_router(state: RestApiState) -> Router {
|
||||||
Router::new()
|
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/kv", get(list_kv))
|
||||||
.route("/api/v1/cluster/status", get(cluster_status))
|
.route("/api/v1/cluster/status", get(cluster_status))
|
||||||
.route("/api/v1/cluster/members", post(add_member))
|
.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<RestApiState>,
|
||||||
|
Path(key): Path<String>,
|
||||||
|
) -> Result<Json<SuccessResponse<GetResponse>>, (StatusCode, Json<ErrorResponse>)> {
|
||||||
|
// 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<RestApiState>,
|
||||||
|
Path(key): Path<String>,
|
||||||
|
Json(req): Json<PutRequest>,
|
||||||
|
) -> Result<(StatusCode, Json<SuccessResponse<serde_json::Value>>), (StatusCode, Json<ErrorResponse>)> {
|
||||||
|
// 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<RestApiState>,
|
||||||
|
Path(key): Path<String>,
|
||||||
|
) -> Result<(StatusCode, Json<SuccessResponse<serde_json::Value>>), (StatusCode, Json<ErrorResponse>)> {
|
||||||
|
// 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
|
/// GET /api/v1/kv?prefix={prefix} - Range scan
|
||||||
async fn list_kv(
|
async fn list_kv(
|
||||||
State(state): State<RestApiState>,
|
State(state): State<RestApiState>,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue