sui_tool/db_shell/
proxy.rs1use anyhow::{Context, bail};
8use reqwest::blocking::Client;
9use serde::Deserialize;
10
11use crate::db_shell::{
12 backend::{Backend, DirEntry},
13 vfs::VfsPath,
14};
15
16pub struct ProxyBackend {
17 client: Client,
18 base_url: String,
19}
20
21impl ProxyBackend {
22 pub fn new(admin_url: &str) -> anyhow::Result<Self> {
23 let client = Client::builder()
24 .timeout(std::time::Duration::from_secs(30))
25 .build()
26 .context("failed to build HTTP client")?;
27 Ok(Self {
28 client,
29 base_url: admin_url.trim_end_matches('/').to_string(),
30 })
31 }
32
33 fn ls_impl(&self, path: &VfsPath, limit: usize, cursor: bool) -> anyhow::Result<Vec<DirEntry>> {
34 #[derive(Deserialize)]
35 struct Entry {
36 name: String,
37 is_dir: bool,
38 }
39
40 let url = format!("{}/db-shell/ls", self.base_url);
41 let resp = self
42 .client
43 .get(&url)
44 .query(&[
45 ("path", path.to_string()),
46 ("limit", limit.to_string()),
47 ("cursor", cursor.to_string()),
48 ])
49 .send()
50 .context("ls request failed")?;
51
52 if !resp.status().is_success() {
53 let status = resp.status();
54 let body = resp.text().unwrap_or_default();
55 bail!("ls failed ({status}): {body}");
56 }
57
58 let entries: Vec<Entry> = resp.json().context("failed to parse ls response")?;
59 Ok(entries
60 .into_iter()
61 .map(|e| DirEntry {
62 name: e.name,
63 is_dir: e.is_dir,
64 })
65 .collect())
66 }
67
68 fn read_impl(&self, path: &VfsPath, format: &str) -> anyhow::Result<Vec<u8>> {
69 let url = format!("{}/db-shell/read", self.base_url);
70 let resp = self
71 .client
72 .get(&url)
73 .query(&[("path", path.to_string()), ("format", format.to_string())])
74 .send()
75 .context("read request failed")?;
76
77 if !resp.status().is_success() {
78 let status = resp.status();
79 let body = resp.text().unwrap_or_default();
80 bail!("read failed ({status}): {body}");
81 }
82
83 Ok(resp
84 .bytes()
85 .context("failed to read response body")?
86 .to_vec())
87 }
88}
89
90impl Backend for ProxyBackend {
91 fn ls_children(&self, path: &VfsPath, limit: usize) -> anyhow::Result<Vec<DirEntry>> {
92 self.ls_impl(path, limit, false)
93 }
94
95 fn ls_cursor(&self, path: &VfsPath, limit: usize) -> anyhow::Result<Vec<DirEntry>> {
96 self.ls_impl(path, limit, true)
97 }
98
99 fn read_json(&self, path: &VfsPath) -> anyhow::Result<serde_json::Value> {
100 let bytes = self.read_impl(path, "json")?;
101 serde_json::from_slice(&bytes).context("failed to parse JSON response")
102 }
103
104 fn read_debug(&self, path: &VfsPath) -> anyhow::Result<String> {
105 let bytes = self.read_impl(path, "debug")?;
106 String::from_utf8(bytes).context("debug response is not valid UTF-8")
107 }
108
109 fn read_bcs(&self, path: &VfsPath) -> anyhow::Result<Vec<u8>> {
110 let bytes = self.read_impl(path, "raw-bcs")?;
111 Ok(bytes)
112 }
113
114 fn delete(&self, path: &VfsPath) -> anyhow::Result<()> {
115 let url = format!("{}/db-shell/delete", self.base_url);
116 let resp = self
117 .client
118 .delete(&url)
119 .query(&[("path", path.to_string())])
120 .send()
121 .context("delete request failed")?;
122
123 if !resp.status().is_success() {
124 let status = resp.status();
125 let body = resp.text().unwrap_or_default();
126 bail!("delete failed ({status}): {body}");
127 }
128 Ok(())
129 }
130}