use anyhow::Result;
use http::header::{ACCEPT, CONTENT_TYPE};
use reblessive::tree::Stk;
use tracing::trace;
use uuid::Uuid;
use super::args::Optional;
use crate::api::err::ApiError;
use crate::api::format as api_format;
use crate::api::invocation::process_api_request_with_stack;
use crate::api::request::ApiRequest;
use crate::api::response::ApiResponse;
use crate::catalog::ApiDefinition;
use crate::catalog::providers::ApiProvider;
use crate::ctx::{Context, FrozenContext};
use crate::dbs::Options;
use crate::doc::CursorDoc;
use crate::err::Error;
use crate::fnc::args::FromPublic;
use crate::val::{Closure, Duration, Value};
pub mod req;
pub mod res;
pub async fn invoke(
(stk, ctx, opt): (&mut Stk, &FrozenContext, &Options),
(path, Optional(req)): (String, Optional<FromPublic<ApiRequest>>),
) -> Result<Value> {
let request_id = Uuid::new_v4().to_string();
let mut req = req.map(|x| x.0).unwrap_or_default();
req.request_id.clone_from(&request_id);
trace!(request_id = %request_id, path = %path, "fnc::api::invoke called");
let (ns, db) = ctx.expect_ns_db_ids(opt).await?;
let apis = ctx.tx().all_db_apis(ns, db, None).await?;
if !path.starts_with('/') {
return Err(anyhow::Error::from(Error::InvalidPath(
"Segment should start with /".to_string(),
)));
}
let segments: Vec<&str> = path.split('/').filter(|x| !x.is_empty()).collect();
if !req.headers.contains_key(CONTENT_TYPE) {
req.headers.insert(CONTENT_TYPE, api_format::NATIVE.try_into()?);
}
if !req.headers.contains_key(ACCEPT) {
req.headers.insert(ACCEPT, "application/vnd.surrealdb.native;q=0.9, */*;q=0.8".try_into()?);
}
let mut value: Value =
if let Some((api, params)) = ApiDefinition::find_definition(&apis, &segments, req.method) {
req.params = params.try_into()?;
process_api_request_with_stack(stk, ctx, opt, api, req).await?.into()
} else {
trace!(request_id = %request_id, path = %path, "No API definition found for path");
ApiResponse::from_error(ApiError::NotFound, request_id).into()
};
let Value::Object(ref mut obj) = value else {
fail!("ApiResponse converts into an object");
};
obj.remove("context");
Ok(value)
}
pub async fn timeout(
(stk, ctx, opt, doc): (&mut Stk, &FrozenContext, &Options, Option<&CursorDoc>),
(req, next, timeout): (Value, Box<Closure>, Duration),
) -> Result<Value> {
let mut ctx = Context::new_isolated(ctx);
ctx.add_timeout(*timeout)?;
let ctx = &ctx.freeze();
next.invoke(stk, ctx, opt, doc, vec![req]).await
}