fix: add --all to cargo fmt in script/test and autoformat codebase
cargo fmt without --all fails with "Failed to find targets" in workspace repos. This was blocking every story's gates. Also ran cargo fmt --all to fix all existing formatting issues. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+112
-34
@@ -13,7 +13,7 @@ use poem::web::Data;
|
||||
use poem::{Body, Request, Response};
|
||||
use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
use serde_json::{Value, json};
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
@@ -41,8 +41,7 @@ impl GatewayConfig {
|
||||
pub fn load(path: &Path) -> Result<Self, String> {
|
||||
let contents = std::fs::read_to_string(path)
|
||||
.map_err(|e| format!("cannot read {}: {e}", path.display()))?;
|
||||
toml::from_str(&contents)
|
||||
.map_err(|e| format!("invalid projects.toml: {e}"))
|
||||
toml::from_str(&contents).map_err(|e| format!("invalid projects.toml: {e}"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,11 +116,21 @@ struct JsonRpcError {
|
||||
|
||||
impl JsonRpcResponse {
|
||||
fn success(id: Option<Value>, result: Value) -> Self {
|
||||
Self { jsonrpc: "2.0", id, result: Some(result), error: None }
|
||||
Self {
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
result: Some(result),
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn error(id: Option<Value>, code: i64, message: String) -> Self {
|
||||
Self { jsonrpc: "2.0", id, result: None, error: Some(JsonRpcError { code, message }) }
|
||||
Self {
|
||||
jsonrpc: "2.0",
|
||||
id,
|
||||
result: None,
|
||||
error: Some(JsonRpcError { code, message }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,22 +156,32 @@ pub async fn gateway_mcp_post_handler(
|
||||
let content_type = req.header("content-type").unwrap_or("");
|
||||
if !content_type.is_empty() && !content_type.contains("application/json") {
|
||||
return to_json_response(JsonRpcResponse::error(
|
||||
None, -32700, "Unsupported Content-Type; expected application/json".into(),
|
||||
None,
|
||||
-32700,
|
||||
"Unsupported Content-Type; expected application/json".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let bytes = match body.into_bytes().await {
|
||||
Ok(b) => b,
|
||||
Err(_) => return to_json_response(JsonRpcResponse::error(None, -32700, "Parse error".into())),
|
||||
Err(_) => {
|
||||
return to_json_response(JsonRpcResponse::error(None, -32700, "Parse error".into()));
|
||||
}
|
||||
};
|
||||
|
||||
let rpc: JsonRpcRequest = match serde_json::from_slice(&bytes) {
|
||||
Ok(r) => r,
|
||||
Err(_) => return to_json_response(JsonRpcResponse::error(None, -32700, "Parse error".into())),
|
||||
Err(_) => {
|
||||
return to_json_response(JsonRpcResponse::error(None, -32700, "Parse error".into()));
|
||||
}
|
||||
};
|
||||
|
||||
if rpc.jsonrpc != "2.0" {
|
||||
return to_json_response(JsonRpcResponse::error(rpc.id, -32600, "Invalid JSON-RPC version".into()));
|
||||
return to_json_response(JsonRpcResponse::error(
|
||||
rpc.id,
|
||||
-32600,
|
||||
"Invalid JSON-RPC version".into(),
|
||||
));
|
||||
}
|
||||
|
||||
// Accept notifications silently.
|
||||
@@ -185,7 +204,8 @@ pub async fn gateway_mcp_post_handler(
|
||||
}
|
||||
}
|
||||
"tools/call" => {
|
||||
let tool_name = rpc.params
|
||||
let tool_name = rpc
|
||||
.params
|
||||
.get("name")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("");
|
||||
@@ -200,7 +220,9 @@ pub async fn gateway_mcp_post_handler(
|
||||
.header("Content-Type", "application/json")
|
||||
.body(Body::from(resp_body)),
|
||||
Err(e) => to_json_response(JsonRpcResponse::error(
|
||||
rpc.id, -32603, format!("proxy error: {e}"),
|
||||
rpc.id,
|
||||
-32603,
|
||||
format!("proxy error: {e}"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -213,7 +235,9 @@ pub async fn gateway_mcp_post_handler(
|
||||
.header("Content-Type", "application/json")
|
||||
.body(Body::from(resp_body)),
|
||||
Err(e) => to_json_response(JsonRpcResponse::error(
|
||||
rpc.id, -32603, format!("proxy error: {e}"),
|
||||
rpc.id,
|
||||
-32603,
|
||||
format!("proxy error: {e}"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@@ -295,14 +319,17 @@ async fn merge_tools_list(
|
||||
"params": {}
|
||||
});
|
||||
|
||||
let resp = state.client
|
||||
let resp = state
|
||||
.client
|
||||
.post(&mcp_url)
|
||||
.json(&rpc_body)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("failed to reach {mcp_url}: {e}"))?;
|
||||
|
||||
let resp_json: Value = resp.json().await
|
||||
let resp_json: Value = resp
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| format!("invalid JSON from upstream: {e}"))?;
|
||||
|
||||
let mut tools: Vec<Value> = resp_json
|
||||
@@ -320,14 +347,12 @@ async fn merge_tools_list(
|
||||
}
|
||||
|
||||
/// Proxy a raw MCP request body to the active project's container.
|
||||
async fn proxy_mcp_call(
|
||||
state: &GatewayState,
|
||||
request_bytes: &[u8],
|
||||
) -> Result<Vec<u8>, String> {
|
||||
async fn proxy_mcp_call(state: &GatewayState, request_bytes: &[u8]) -> Result<Vec<u8>, String> {
|
||||
let url = state.active_url().await?;
|
||||
let mcp_url = format!("{}/mcp", url.trim_end_matches('/'));
|
||||
|
||||
let resp = state.client
|
||||
let resp = state
|
||||
.client
|
||||
.post(&mcp_url)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(request_bytes.to_vec())
|
||||
@@ -374,8 +399,12 @@ async fn handle_switch_project(params: &Value, state: &GatewayState) -> JsonRpcR
|
||||
if !state.config.projects.contains_key(project) {
|
||||
let available: Vec<&str> = state.config.projects.keys().map(|s| s.as_str()).collect();
|
||||
return JsonRpcResponse::error(
|
||||
None, -32602,
|
||||
format!("unknown project '{project}'. Available: {}", available.join(", ")),
|
||||
None,
|
||||
-32602,
|
||||
format!(
|
||||
"unknown project '{project}'. Available: {}",
|
||||
available.join(", ")
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -431,7 +460,9 @@ async fn handle_gateway_status(state: &GatewayState) -> JsonRpcResponse {
|
||||
}),
|
||||
)
|
||||
}
|
||||
Err(e) => JsonRpcResponse::error(None, -32603, format!("invalid upstream response: {e}")),
|
||||
Err(e) => {
|
||||
JsonRpcResponse::error(None, -32603, format!("invalid upstream response: {e}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => JsonRpcResponse::error(None, -32603, format!("failed to reach {mcp_url}: {e}")),
|
||||
@@ -500,7 +531,11 @@ pub async fn gateway_health_handler(state: Data<&Arc<GatewayState>>) -> Response
|
||||
"projects": statuses,
|
||||
});
|
||||
|
||||
let status = if all_healthy { StatusCode::OK } else { StatusCode::SERVICE_UNAVAILABLE };
|
||||
let status = if all_healthy {
|
||||
StatusCode::OK
|
||||
} else {
|
||||
StatusCode::SERVICE_UNAVAILABLE
|
||||
};
|
||||
Response::builder()
|
||||
.status(status)
|
||||
.header("Content-Type", "application/json")
|
||||
@@ -519,7 +554,13 @@ pub async fn run(config_path: &Path, port: u16) -> Result<(), std::io::Error> {
|
||||
crate::slog!("[gateway] Starting gateway on port {port}, active project: {active}");
|
||||
crate::slog!(
|
||||
"[gateway] Registered projects: {}",
|
||||
state_arc.config.projects.keys().cloned().collect::<Vec<_>>().join(", ")
|
||||
state_arc
|
||||
.config
|
||||
.projects
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
);
|
||||
|
||||
let route = poem::Route::new()
|
||||
@@ -569,15 +610,27 @@ url = "http://localhost:3002"
|
||||
|
||||
#[test]
|
||||
fn gateway_state_rejects_empty_config() {
|
||||
let config = GatewayConfig { projects: BTreeMap::new() };
|
||||
let config = GatewayConfig {
|
||||
projects: BTreeMap::new(),
|
||||
};
|
||||
assert!(GatewayState::new(config).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gateway_state_sets_first_project_active() {
|
||||
let mut projects = BTreeMap::new();
|
||||
projects.insert("alpha".into(), ProjectEntry { url: "http://a:3001".into() });
|
||||
projects.insert("beta".into(), ProjectEntry { url: "http://b:3002".into() });
|
||||
projects.insert(
|
||||
"alpha".into(),
|
||||
ProjectEntry {
|
||||
url: "http://a:3001".into(),
|
||||
},
|
||||
);
|
||||
projects.insert(
|
||||
"beta".into(),
|
||||
ProjectEntry {
|
||||
url: "http://b:3002".into(),
|
||||
},
|
||||
);
|
||||
let config = GatewayConfig { projects };
|
||||
let state = GatewayState::new(config).unwrap();
|
||||
let active = state.active_project.blocking_read().clone();
|
||||
@@ -587,7 +640,8 @@ url = "http://localhost:3002"
|
||||
#[test]
|
||||
fn gateway_tool_definitions_has_expected_tools() {
|
||||
let defs = gateway_tool_definitions();
|
||||
let names: Vec<&str> = defs.iter()
|
||||
let names: Vec<&str> = defs
|
||||
.iter()
|
||||
.filter_map(|d| d.get("name").and_then(|n| n.as_str()))
|
||||
.collect();
|
||||
assert!(names.contains(&"switch_project"));
|
||||
@@ -598,8 +652,18 @@ url = "http://localhost:3002"
|
||||
#[tokio::test]
|
||||
async fn switch_project_to_known_project() {
|
||||
let mut projects = BTreeMap::new();
|
||||
projects.insert("alpha".into(), ProjectEntry { url: "http://a:3001".into() });
|
||||
projects.insert("beta".into(), ProjectEntry { url: "http://b:3002".into() });
|
||||
projects.insert(
|
||||
"alpha".into(),
|
||||
ProjectEntry {
|
||||
url: "http://a:3001".into(),
|
||||
},
|
||||
);
|
||||
projects.insert(
|
||||
"beta".into(),
|
||||
ProjectEntry {
|
||||
url: "http://b:3002".into(),
|
||||
},
|
||||
);
|
||||
let config = GatewayConfig { projects };
|
||||
let state = GatewayState::new(config).unwrap();
|
||||
|
||||
@@ -614,7 +678,12 @@ url = "http://localhost:3002"
|
||||
#[tokio::test]
|
||||
async fn switch_project_to_unknown_project_fails() {
|
||||
let mut projects = BTreeMap::new();
|
||||
projects.insert("alpha".into(), ProjectEntry { url: "http://a:3001".into() });
|
||||
projects.insert(
|
||||
"alpha".into(),
|
||||
ProjectEntry {
|
||||
url: "http://a:3001".into(),
|
||||
},
|
||||
);
|
||||
let config = GatewayConfig { projects };
|
||||
let state = GatewayState::new(config).unwrap();
|
||||
|
||||
@@ -626,7 +695,12 @@ url = "http://localhost:3002"
|
||||
#[tokio::test]
|
||||
async fn active_url_returns_correct_url() {
|
||||
let mut projects = BTreeMap::new();
|
||||
projects.insert("myproj".into(), ProjectEntry { url: "http://my:3001".into() });
|
||||
projects.insert(
|
||||
"myproj".into(),
|
||||
ProjectEntry {
|
||||
url: "http://my:3001".into(),
|
||||
},
|
||||
);
|
||||
let config = GatewayConfig { projects };
|
||||
let state = GatewayState::new(config).unwrap();
|
||||
|
||||
@@ -654,10 +728,14 @@ url = "http://localhost:3002"
|
||||
fn load_config_from_file() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let path = dir.path().join("projects.toml");
|
||||
std::fs::write(&path, r#"
|
||||
std::fs::write(
|
||||
&path,
|
||||
r#"
|
||||
[projects.test]
|
||||
url = "http://localhost:9999"
|
||||
"#).unwrap();
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let config = GatewayConfig::load(&path).unwrap();
|
||||
assert_eq!(config.projects.len(), 1);
|
||||
|
||||
Reference in New Issue
Block a user