use std::sync::Arc;
use jammi_ai::session::InferenceSession;
use jammi_db::error::JammiError;
use jammi_db::trigger::TriggerError;
use jammi_db::TenantId;
use jammi_wire::{attach_error_detail, attach_trigger_detail};
use tonic::{Code, Request, Status};
use crate::grpc::session::SessionTenant;
pub fn session_tenant<T>(request: &Request<T>) -> Option<TenantId> {
request
.extensions()
.get::<SessionTenant>()
.and_then(|s| s.0)
}
pub async fn scoped<F, Fut, T, E>(
session: &Arc<InferenceSession>,
tenant: Option<TenantId>,
f: F,
) -> Result<T, E>
where
F: FnOnce() -> Fut,
Fut: std::future::Future<Output = Result<T, E>>,
{
match tenant {
Some(t) => session.with_tenant_scoped(t, |_scope| f()).await,
None => f().await,
}
}
pub fn require_nonempty(value: &str, field: &str) -> Result<(), Status> {
if value.is_empty() {
Err(Status::invalid_argument(format!("{field} is required")))
} else {
Ok(())
}
}
pub fn map_engine_error(err: JammiError) -> Status {
let (code, message) = match &err {
JammiError::Source { source_id, message } => (
Code::InvalidArgument,
format!("source {source_id}: {message}"),
),
JammiError::Model { model_id, message } => (
Code::InvalidArgument,
format!("model {model_id}: {message}"),
),
JammiError::Tenant(detail) => (Code::InvalidArgument, format!("tenant: {detail}")),
JammiError::Config(detail) => (Code::InvalidArgument, format!("config: {detail}")),
JammiError::Schema { .. } => (Code::InvalidArgument, err.to_string()),
JammiError::Eval(detail) => (Code::InvalidArgument, format!("eval: {detail}")),
JammiError::Inference(detail) => (Code::Internal, format!("inference: {detail}")),
other => (Code::Internal, other.to_string()),
};
attach_error_detail(code, message, &err)
}
pub fn map_trigger_error(err: TriggerError) -> Status {
let (code, message) = match &err {
TriggerError::TopicNotFound(name) => (Code::NotFound, name.clone()),
TriggerError::BatchSchemaMismatch(detail) => (Code::InvalidArgument, detail.clone()),
TriggerError::SchemaConflict { topic, detail } => (
Code::FailedPrecondition,
format!("schema conflict on {topic}: {detail}"),
),
TriggerError::UnsupportedSchemaType { column, data_type } => (
Code::InvalidArgument,
format!("unsupported topic schema type for '{column}': {data_type}"),
),
TriggerError::PublishTenantMismatch {
topic,
topic_tenant,
publish_tenant,
} => (
Code::PermissionDenied,
format!(
"publish tenant mismatch on topic '{topic}': topic_tenant={topic_tenant:?}, publish_tenant={publish_tenant:?}"
),
),
TriggerError::PredicateParse(detail) | TriggerError::PredicateUnsupported(detail) => {
(Code::InvalidArgument, format!("predicate: {detail}"))
}
TriggerError::PredicateEval(detail) => (Code::Internal, format!("predicate: {detail}")),
TriggerError::OffsetEvicted(n) => {
(Code::FailedPrecondition, format!("offset {n} evicted"))
}
TriggerError::BackingTable(e) => (Code::Internal, format!("backing table: {e}")),
TriggerError::Backend(e) => (Code::Internal, format!("backend: {e}")),
TriggerError::Driver(detail) => (Code::Unavailable, format!("broker: {detail}")),
TriggerError::Catalog(detail) => (Code::Internal, format!("catalog: {detail}")),
};
attach_trigger_detail(code, message, &err)
}