hatidata_cli/sync.rs
1use anyhow::Result;
2use serde::{Deserialize, Serialize};
3
4/// Response from the control plane sync API.
5#[derive(Debug, Serialize, Deserialize)]
6pub struct SyncResponse {
7 pub success: bool,
8 pub message: String,
9 pub rows_synced: Option<u64>,
10}
11
12/// Remote table schema returned by the control plane.
13#[derive(Debug, Serialize, Deserialize)]
14pub struct TableSchema {
15 pub name: String,
16 pub columns: Vec<ColumnSchema>,
17}
18
19/// Column schema information.
20#[derive(Debug, Serialize, Deserialize)]
21pub struct ColumnSchema {
22 pub name: String,
23 pub data_type: String,
24 pub nullable: bool,
25}
26
27/// Client for syncing data between local DuckDB and the HatiData control plane.
28pub struct SyncClient {
29 _client: reqwest::Client,
30 _endpoint: String,
31 _api_key: String,
32}
33
34impl SyncClient {
35 /// Create a new sync client.
36 pub fn new(endpoint: &str, api_key: &str) -> Self {
37 Self {
38 _client: reqwest::Client::new(),
39 _endpoint: endpoint.trim_end_matches('/').to_string(),
40 _api_key: api_key.to_string(),
41 }
42 }
43
44 /// Push a table's Parquet data to the remote control plane.
45 ///
46 /// Calls `POST /v1/sync/push` with multipart form data.
47 #[allow(unused_variables)]
48 pub async fn push_table(
49 &self,
50 table_name: &str,
51 parquet_data: Vec<u8>,
52 ) -> Result<SyncResponse> {
53 // TODO: Implement actual HTTP upload to control plane
54 // The request should be:
55 // POST {endpoint}/v1/sync/push
56 // Authorization: Bearer {api_key}
57 // Content-Type: multipart/form-data
58 // Body: table_name + parquet file
59 //
60 // let form = reqwest::multipart::Form::new()
61 // .text("table_name", table_name.to_string())
62 // .part("data", reqwest::multipart::Part::bytes(parquet_data)
63 // .file_name(format!("{table_name}.parquet"))
64 // .mime_str("application/octet-stream")?);
65 //
66 // let response = self._client
67 // .post(format!("{}/v1/sync/push", self._endpoint))
68 // .bearer_auth(&self._api_key)
69 // .multipart(form)
70 // .send()
71 // .await?
72 // .json::<SyncResponse>()
73 // .await?;
74
75 Ok(SyncResponse {
76 success: false,
77 message: "Push not yet implemented — waiting for control plane /v1/sync/push endpoint"
78 .to_string(),
79 rows_synced: None,
80 })
81 }
82
83 /// Pull the list of table schemas from the remote control plane.
84 #[allow(unused_variables)]
85 pub async fn pull_schema(&self) -> Result<Vec<TableSchema>> {
86 // TODO: Implement actual HTTP call to control plane
87 // GET {endpoint}/v1/sync/schema
88 // Authorization: Bearer {api_key}
89 //
90 // let response = self._client
91 // .get(format!("{}/v1/sync/schema", self._endpoint))
92 // .bearer_auth(&self._api_key)
93 // .send()
94 // .await?
95 // .json::<Vec<TableSchema>>()
96 // .await?;
97
98 Ok(Vec::new())
99 }
100
101 /// Pull a single table's data as Parquet bytes.
102 #[allow(unused_variables)]
103 pub async fn pull_table(&self, table_name: &str) -> Result<Vec<u8>> {
104 // TODO: Implement actual HTTP call to control plane
105 // GET {endpoint}/v1/sync/pull/{table_name}
106 // Authorization: Bearer {api_key}
107 // Accept: application/octet-stream
108 //
109 // let response = self._client
110 // .get(format!("{}/v1/sync/pull/{table_name}", self._endpoint))
111 // .bearer_auth(&self._api_key)
112 // .send()
113 // .await?
114 // .bytes()
115 // .await?;
116
117 Ok(Vec::new())
118 }
119}