1use anyhow::{Context, Result};
13
14use crate::output::OutputFormat;
15
16const DEFAULT_REST_URL: &str = "http://127.0.0.1:22222";
18
19pub async fn run_status(
21 rest_url: Option<&str>,
22 api_key: Option<&str>,
23 _output: OutputFormat,
24) -> Result<()> {
25 let resp = request(
26 reqwest::Method::GET,
27 "/api/admin/status",
28 rest_url,
29 api_key,
30 5,
31 )
32 .await?;
33 print_json(resp)
34}
35
36pub async fn run_shutdown(rest_url: Option<&str>, api_key: Option<&str>) -> Result<()> {
38 let resp = request(
40 reqwest::Method::POST,
41 "/api/admin/shutdown",
42 rest_url,
43 api_key,
44 5,
45 )
46 .await?;
47 print_json(resp)?;
48 eprintln!("# daemon will exit in 1 second; `ps` / systemd 状态即可确认");
49 Ok(())
50}
51
52pub async fn run_reload(rest_url: Option<&str>, api_key: Option<&str>) -> Result<()> {
54 let resp = request(
55 reqwest::Method::POST,
56 "/api/admin/reload",
57 rest_url,
58 api_key,
59 5,
60 )
61 .await?;
62 print_json(resp)?;
63 eprintln!("# 客户端应重新调 /api/unlock-trade 才能下单");
64 Ok(())
65}
66
67async fn request(
69 method: reqwest::Method,
70 path: &str,
71 rest_url: Option<&str>,
72 api_key: Option<&str>,
73 timeout_secs: u64,
74) -> Result<String> {
75 let base = resolve_rest_url(rest_url);
76 let url = format!("{}{}", base.trim_end_matches('/'), path);
77 let client = reqwest::Client::builder()
78 .timeout(std::time::Duration::from_secs(timeout_secs))
79 .build()
80 .context("build reqwest client")?;
81 let mut req = client.request(method.clone(), &url);
82 if let Some(key) = api_key {
83 req = req.bearer_auth(key);
84 }
85 let resp = req
86 .send()
87 .await
88 .with_context(|| format!("{method} {url} failed"))?;
89 let status = resp.status();
90 let body = resp.text().await.context("read response body")?;
91 if !status.is_success() {
92 anyhow::bail!(
93 "{} {} failed: HTTP {} — {}",
94 method,
95 path,
96 status.as_u16(),
97 body.chars().take(400).collect::<String>()
98 );
99 }
100 Ok(body)
101}
102
103fn print_json(body: String) -> Result<()> {
104 let parsed: serde_json::Value =
105 serde_json::from_str(&body).with_context(|| format!("response not JSON: {body}"))?;
106 let pretty = serde_json::to_string_pretty(&parsed)?;
107 println!("{}", pretty);
108 Ok(())
109}
110
111fn resolve_rest_url(cli_override: Option<&str>) -> String {
113 if let Some(u) = cli_override {
114 return u.to_string();
115 }
116 if let Ok(env_u) = std::env::var("FUTU_REST_URL")
117 && !env_u.is_empty()
118 {
119 return env_u;
120 }
121 DEFAULT_REST_URL.to_string()
122}
123
124#[cfg(test)]
125mod tests;