helix_rs/
lib.rs

1extern crate helix_macros;
2
3pub use helix_macros::helix_node;
4
5use reqwest::{Client, StatusCode};
6use serde::{Deserialize, Serialize};
7use thiserror::Error;
8
9#[derive(Debug, Clone)]
10pub struct HelixDB {
11    port: Option<u16>,
12    client: Client,
13    endpoint: String,
14    api_key: Option<String>,
15}
16
17#[derive(Debug, Error)]
18pub enum HelixError {
19    #[error("Error communicating with server: {0}")]
20    ReqwestError(#[from] reqwest::Error),
21    #[error("Got Error from server: {details}")]
22    RemoteError { details: String },
23}
24
25// This trait allows users to implement their own client if needed
26pub trait HelixDBClient {
27    type Err: std::error::Error;
28
29    fn new(endpoint: Option<&str>, port: Option<u16>, api_key: Option<&str>) -> Self;
30    fn query<T, R>(
31        &self,
32        endpoint: &str,
33        data: &T,
34    ) -> impl std::future::Future<Output = Result<R, Self::Err>> + Send
35    where
36        T: Serialize + Sync,
37        R: for<'de> Deserialize<'de>;
38}
39
40impl HelixDBClient for HelixDB {
41    type Err = HelixError;
42    fn new(endpoint: Option<&str>, port: Option<u16>, api_key: Option<&str>) -> Self {
43        Self {
44            port: port,
45            client: Client::new(),
46            endpoint: endpoint.unwrap_or("http://localhost").to_string(),
47            api_key: api_key.map(|key| key.to_string()),
48        }
49    }
50
51    async fn query<T, R>(&self, endpoint: &str, data: &T) -> Result<R, HelixError>
52    where
53        T: Serialize + Sync,
54        R: for<'de> Deserialize<'de>,
55    {
56        let port = match self.port {
57            Some(port) => format!(":{}", port),
58            None => "".to_string(),
59        };
60
61        let url = format!("{}{}/{}", self.endpoint, port, endpoint);
62
63        let mut request = self.client.post(&url).json(data);
64
65        // Add API key header if provided
66        if let Some(ref api_key) = self.api_key {
67            request = request.header("x-api-key", api_key);
68        }
69
70        let response = request.send().await?;
71
72        match response.status() {
73            StatusCode::OK => response.json().await.map_err(Into::into),
74            // code => Err(HelixError::RemoteError{details: response.text().await.unwrap_or(code.canonical_reason().unwrap_or(format!()))}),
75            code => match response.text().await {
76                Ok(t) => Err(HelixError::RemoteError { details: t }),
77                Err(_) => match code.canonical_reason() {
78                    Some(r) => Err(HelixError::RemoteError {
79                        details: r.to_string(),
80                    }),
81                    None => Err(HelixError::RemoteError {
82                        details: format!("unkown error with code: {code}"),
83                    }),
84                },
85            },
86        }
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::helix_node;
93    use super::*;
94
95    #[helix_node]
96    struct User {
97        name: String,
98        age: i32,
99    }
100
101    #[tokio::test]
102    async fn test_basic_query() {
103        let client = HelixDB::new(None, None, None);
104
105        // Example test structure
106        #[derive(Serialize)]
107        struct UserInput {
108            name: String,
109            age: i32,
110        }
111
112        #[derive(Deserialize)]
113        struct UserOutput {
114            id: String,
115            name: String,
116            age: i32,
117        }
118
119        let input = UserInput {
120            name: "John".to_string(),
121            age: 20,
122        };
123
124        #[derive(Deserialize)]
125        struct Result {
126            pub user: UserOutput,
127        }
128
129        // Note: This test will fail unless HelixDB is running locally
130        let _result: Result = client.query("add_user", &input).await.unwrap();
131    }
132
133    #[tokio::test]
134    async fn test_query_with_api_key() {
135        let client = HelixDB::new(None, None, Some("test-api-key"));
136
137        // Example test structure
138        #[derive(Serialize)]
139        struct UserInput {
140            name: String,
141            age: i32,
142        }
143
144        #[derive(Deserialize)]
145        struct UserOutput {
146            id: String,
147            name: String,
148            age: i32,
149        }
150
151        let input = UserInput {
152            name: "Jane".to_string(),
153            age: 25,
154        };
155
156        #[derive(Deserialize)]
157        struct Result {
158            pub user: UserOutput,
159        }
160
161        // Note: This test will fail unless HelixDB is running locally with API key support
162        let _result: Result = client.query("add_user", &input).await.unwrap();
163    }
164}