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(
41 reqwest::Method::POST,
42 "/api/admin/shutdown",
43 rest_url,
44 api_key,
45 5,
46 )
47 .await?;
48 print_json(resp)?;
49 eprintln!("# daemon shutdown requested; `ps` / systemd 状态即可确认最终退出");
50 Ok(())
51}
52
53pub async fn run_reload(rest_url: Option<&str>, api_key: Option<&str>) -> Result<()> {
55 let resp = request(
56 reqwest::Method::POST,
57 "/api/admin/reload",
58 rest_url,
59 api_key,
60 5,
61 )
62 .await?;
63 print_json(resp)?;
64 eprintln!("# 客户端应重新调 /api/unlock-trade 才能下单");
65 Ok(())
66}
67
68async fn request(
70 method: reqwest::Method,
71 path: &str,
72 rest_url: Option<&str>,
73 api_key: Option<&str>,
74 timeout_secs: u64,
75) -> Result<String> {
76 let base = resolve_rest_url(rest_url);
77 let url = format!("{}{}", base.trim_end_matches('/'), path);
78 let client = reqwest::Client::builder()
79 .timeout(std::time::Duration::from_secs(timeout_secs))
80 .build()
81 .context("build reqwest client")?;
82 let mut req = client.request(method.clone(), &url);
83 if let Some(key) = api_key {
84 req = req.bearer_auth(key);
85 }
86 let resp = req
87 .send()
88 .await
89 .with_context(|| format!("{method} {url} failed"))?;
90 let status = resp.status();
91 let body = resp.text().await.context("read response body")?;
92 if !status.is_success() {
93 anyhow::bail!(
94 "{} {} failed: HTTP {} — {}",
95 method,
96 path,
97 status.as_u16(),
98 body.chars().take(400).collect::<String>()
99 );
100 }
101 Ok(body)
102}
103
104fn print_json(body: String) -> Result<()> {
105 let parsed: serde_json::Value =
106 serde_json::from_str(&body).with_context(|| format!("response not JSON: {body}"))?;
107 let pretty = serde_json::to_string_pretty(&parsed)?;
108 println!("{}", pretty);
109 Ok(())
110}
111
112fn resolve_rest_url(cli_override: Option<&str>) -> String {
114 if let Some(u) = cli_override {
115 return u.to_string();
116 }
117 if let Ok(env_u) = std::env::var("FUTU_REST_URL")
118 && !env_u.is_empty()
119 {
120 return env_u;
121 }
122 DEFAULT_REST_URL.to_string()
123}
124
125#[cfg(test)]
126mod tests;