fraiseql_functions/host/
mod.rs1use std::future::Future;
4
5use fraiseql_error::Result;
6
7use crate::types::{EventPayload, LogEntry, LogLevel};
8
9#[cfg(feature = "host-live")]
10pub mod live;
11
12#[cfg(feature = "host-live")]
13pub mod factory;
14
15#[derive(Debug, Clone)]
17pub struct HttpResponse {
18 pub status: u16,
20 pub headers: Vec<(String, String)>,
22 pub body: Vec<u8>,
24}
25
26#[allow(clippy::trait_duplication_in_bounds)] #[trait_variant::make(SendHostContext: Send)]
35pub trait HostContext: Send + Sync {
36 fn query(
42 &self,
43 graphql: &str,
44 variables: serde_json::Value,
45 ) -> impl Future<Output = Result<serde_json::Value>> + Send;
46
47 fn sql_query(
53 &self,
54 sql: &str,
55 params: &[serde_json::Value],
56 ) -> impl Future<Output = Result<Vec<serde_json::Value>>> + Send;
57
58 fn http_request(
64 &self,
65 method: &str,
66 url: &str,
67 headers: &[(String, String)],
68 body: Option<&[u8]>,
69 ) -> impl Future<Output = Result<HttpResponse>> + Send;
70
71 fn storage_get(&self, bucket: &str, key: &str) -> impl Future<Output = Result<Vec<u8>>> + Send;
77
78 fn storage_put(
84 &self,
85 bucket: &str,
86 key: &str,
87 body: &[u8],
88 content_type: &str,
89 ) -> impl Future<Output = Result<()>> + Send;
90
91 fn auth_context(&self) -> Result<serde_json::Value>;
97
98 fn env_var(&self, name: &str) -> Result<Option<String>>;
106
107 fn event_payload(&self) -> &EventPayload;
109
110 fn log(&self, level: LogLevel, message: &str);
112}
113
114pub struct NoopHostContext {
118 event_payload: EventPayload,
119 logs: std::sync::Arc<std::sync::Mutex<Vec<LogEntry>>>,
120}
121
122impl NoopHostContext {
123 #[must_use]
125 pub fn new(event_payload: EventPayload) -> Self {
126 Self {
127 event_payload,
128 logs: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())),
129 }
130 }
131
132 #[must_use]
138 pub fn captured_logs(&self) -> Vec<LogEntry> {
139 self.logs.lock().expect("log mutex poisoned").clone()
140 }
141}
142
143impl HostContext for NoopHostContext {
144 async fn query(
145 &self,
146 _graphql: &str,
147 _variables: serde_json::Value,
148 ) -> Result<serde_json::Value> {
149 Err(fraiseql_error::FraiseQLError::Unsupported {
150 message: "HostContext::query not implemented".to_string(),
151 })
152 }
153
154 async fn sql_query(
155 &self,
156 _sql: &str,
157 _params: &[serde_json::Value],
158 ) -> Result<Vec<serde_json::Value>> {
159 Err(fraiseql_error::FraiseQLError::Unsupported {
160 message: "HostContext::sql_query not implemented".to_string(),
161 })
162 }
163
164 async fn http_request(
165 &self,
166 _method: &str,
167 _url: &str,
168 _headers: &[(String, String)],
169 _body: Option<&[u8]>,
170 ) -> Result<HttpResponse> {
171 Err(fraiseql_error::FraiseQLError::Unsupported {
172 message: "HostContext::http_request not implemented".to_string(),
173 })
174 }
175
176 async fn storage_get(&self, _bucket: &str, _key: &str) -> Result<Vec<u8>> {
177 Err(fraiseql_error::FraiseQLError::Unsupported {
178 message: "HostContext::storage_get not implemented".to_string(),
179 })
180 }
181
182 async fn storage_put(
183 &self,
184 _bucket: &str,
185 _key: &str,
186 _body: &[u8],
187 _content_type: &str,
188 ) -> Result<()> {
189 Err(fraiseql_error::FraiseQLError::Unsupported {
190 message: "HostContext::storage_put not implemented".to_string(),
191 })
192 }
193
194 fn auth_context(&self) -> Result<serde_json::Value> {
195 Err(fraiseql_error::FraiseQLError::Unsupported {
196 message: "HostContext::auth_context not implemented".to_string(),
197 })
198 }
199
200 fn env_var(&self, _name: &str) -> Result<Option<String>> {
201 Ok(None)
202 }
203
204 fn event_payload(&self) -> &EventPayload {
205 &self.event_payload
206 }
207
208 fn log(&self, level: LogLevel, message: &str) {
209 let entry = LogEntry {
210 level,
211 message: message.to_string(),
212 timestamp: chrono::Utc::now(),
213 };
214 self.logs.lock().expect("log mutex poisoned").push(entry);
215 }
216}
217
218#[cfg(test)]
219mod tests;