#![allow(
clippy::inline_always,
clippy::needless_pass_by_value,
clippy::future_not_send
)]
use std::{cell::RefCell, rc::Rc, sync::Arc};
use deno_core::{JsBuffer, OpState, ToJsBuffer, error::AnyError, op2};
use crate::host::dyn_context::DynHostContext;
pub(crate) struct DenoHostContext(pub Arc<dyn DynHostContext>);
fn require_host(state: &OpState) -> Result<Arc<dyn DynHostContext>, AnyError> {
state.try_borrow::<DenoHostContext>().map(|h| Arc::clone(&h.0)).ok_or_else(|| {
deno_core::anyhow::anyhow!(
"host context unavailable: this function was invoked without an I/O host context \
(use invoke_with_context)"
)
})
}
fn require_host_rc(state: &Rc<RefCell<OpState>>) -> Result<Arc<dyn DynHostContext>, AnyError> {
require_host(&state.borrow())
}
fn op_error(error: fraiseql_error::FraiseQLError) -> AnyError {
if error.is_client_error() {
deno_core::anyhow::anyhow!("{} {error}", crate::types::PERMANENT_ERROR_MARKER)
} else {
AnyError::new(error)
}
}
#[derive(serde::Serialize)]
struct HttpResponseJs {
status: u16,
headers: Vec<(String, String)>,
body: ToJsBuffer,
}
#[op2(async)]
#[string]
pub(crate) async fn fraiseql_query(
state: Rc<RefCell<OpState>>,
#[string] graphql: String,
#[string] variables: String,
) -> Result<String, AnyError> {
let host = require_host_rc(&state)?;
let vars = parse_json_arg(&variables, "variables")?;
let result = host.query(&graphql, vars).await.map_err(op_error)?;
serde_json::to_string(&result)
.map_err(|e| deno_core::anyhow::anyhow!("failed to serialise query result: {e}"))
}
#[op2(async)]
#[string]
pub(crate) async fn fraiseql_sql_query(
state: Rc<RefCell<OpState>>,
#[string] sql: String,
#[string] params: String,
) -> Result<String, AnyError> {
let host = require_host_rc(&state)?;
let params_value = parse_json_arg(¶ms, "params")?;
let params_vec = match params_value {
serde_json::Value::Array(items) => items,
serde_json::Value::Null => Vec::new(),
other => vec![other],
};
let rows = host.sql_query(&sql, ¶ms_vec).await.map_err(op_error)?;
serde_json::to_string(&rows)
.map_err(|e| deno_core::anyhow::anyhow!("failed to serialise sql result: {e}"))
}
#[op2(async)]
#[serde]
pub(crate) async fn fraiseql_http_request(
state: Rc<RefCell<OpState>>,
#[string] method: String,
#[string] url: String,
#[serde] headers: Vec<(String, String)>,
#[buffer] body: Option<JsBuffer>,
) -> Result<HttpResponseJs, AnyError> {
let host = require_host_rc(&state)?;
let body_vec: Option<Vec<u8>> = body.map(|b| b.to_vec());
let resp = host
.http_request(&method, &url, &headers, body_vec.as_deref())
.await
.map_err(op_error)?;
Ok(HttpResponseJs {
status: resp.status,
headers: resp.headers,
body: ToJsBuffer::from(resp.body),
})
}
#[op2(async)]
#[buffer]
pub(crate) async fn fraiseql_storage_get(
state: Rc<RefCell<OpState>>,
#[string] bucket: String,
#[string] key: String,
) -> Result<Vec<u8>, AnyError> {
let host = require_host_rc(&state)?;
host.storage_get(&bucket, &key).await.map_err(op_error)
}
#[op2(async)]
pub(crate) async fn fraiseql_storage_put(
state: Rc<RefCell<OpState>>,
#[string] bucket: String,
#[string] key: String,
#[buffer] body: JsBuffer,
#[string] content_type: String,
) -> Result<(), AnyError> {
let host = require_host_rc(&state)?;
let body = body.to_vec();
host.storage_put(&bucket, &key, &body, &content_type).await.map_err(op_error)
}
#[op2(async)]
#[string]
pub(crate) async fn fraiseql_send_email(
state: Rc<RefCell<OpState>>,
#[string] request: String,
) -> Result<String, AnyError> {
let host = require_host_rc(&state)?;
let req: crate::outbound::SendEmailRequest = serde_json::from_str(&request)
.map_err(|e| deno_core::anyhow::anyhow!("invalid send_email request JSON: {e}"))?;
let response = host.send_email(&req).await.map_err(op_error)?;
serde_json::to_string(&response)
.map_err(|e| deno_core::anyhow::anyhow!("failed to serialise send_email response: {e}"))
}
#[op2]
#[string]
pub(crate) fn fraiseql_auth_context(state: &OpState) -> Result<String, AnyError> {
let host = require_host(state)?;
let ctx = host.auth_context().map_err(op_error)?;
serde_json::to_string(&ctx)
.map_err(|e| deno_core::anyhow::anyhow!("failed to serialise auth context: {e}"))
}
#[op2]
#[string]
pub(crate) fn fraiseql_env_var(
state: &OpState,
#[string] name: String,
) -> Result<Option<String>, AnyError> {
let host = require_host(state)?;
host.env_var(&name).map_err(op_error)
}
#[op2]
#[string]
pub(crate) fn fraiseql_idempotency_token(state: &OpState) -> Result<Option<String>, AnyError> {
let host = require_host(state)?;
Ok(host.idempotency_token())
}
#[op2(async)]
#[string]
pub(crate) async fn fraiseql_cursor_get(state: Rc<RefCell<OpState>>) -> Result<String, AnyError> {
let host = require_host_rc(&state)?;
let value = host.cursor().await.map_err(op_error)?;
serde_json::to_string(&value)
.map_err(|e| deno_core::anyhow::anyhow!("failed to serialise cursor: {e}"))
}
#[op2(async)]
pub(crate) async fn fraiseql_cursor_advance(
state: Rc<RefCell<OpState>>,
#[string] value: String,
) -> Result<(), AnyError> {
let host = require_host_rc(&state)?;
let parsed = parse_json_arg(&value, "cursor")?;
host.advance_cursor(parsed).await.map_err(op_error)
}
fn parse_json_arg(raw: &str, what: &str) -> Result<serde_json::Value, AnyError> {
if raw.trim().is_empty() {
return Ok(serde_json::Value::Object(serde_json::Map::new()));
}
serde_json::from_str(raw).map_err(|e| deno_core::anyhow::anyhow!("invalid {what} JSON: {e}"))
}