use super::op_error::ContractOpError;
use super::view::ContractClaim;
use crate::contracts::availability::{SchemaAvailability, SchemaFreshness};
use saya_store::{KnowledgeItem, KnowledgeItemStore, SchemaStore, SqliteStateStore};
use saya_types::{
BindingValidity, ClaimId, DatabaseObjectRef, KnowledgeState, SchemaBinding, SchemaFingerprint,
Table,
};
pub(crate) async fn confirm(
store: &SqliteStateStore,
id: &ClaimId,
) -> Result<ContractClaim, ContractOpError> {
let item = store
.get_knowledge_item(id.as_str())
.await?
.ok_or(ContractOpError::NotFound)?;
if matches!(item.state, KnowledgeState::Dismissed) {
return Err(ContractOpError::Conflict);
}
let availability = schema_availability_for(store, item.object.profile().as_str()).await;
match usable_table_schema(&availability) {
None => match item.state {
KnowledgeState::Pending => {
store
.update_knowledge_item_state(id.as_str(), KnowledgeState::Active)
.await?;
}
_ => return Err(ContractOpError::SchemaUnavailable),
},
Some(schema) => {
let live_table = find_table(&item.object, schema).cloned();
let live_table = live_table.ok_or(ContractOpError::ObjectGone)?;
let binding = binding_to_validate(&item)?;
if binding.validate(&live_table) == BindingValidity::Invalid {
return Err(ContractOpError::ColumnGone);
}
let fresh_binding =
serde_json::to_string(&binding).map_err(|_| ContractOpError::Unavailable)?;
let fingerprint = SchemaFingerprint::of_table(item.object.kind(), &live_table);
store
.revalidate_knowledge_item(id.as_str(), fingerprint, fresh_binding)
.await?;
}
}
let item = store
.get_knowledge_item(id.as_str())
.await?
.ok_or(ContractOpError::NotFound)?;
ContractClaim::from_knowledge_item(&item).ok_or(ContractOpError::Unavailable)
}
pub(crate) async fn reject(
store: &SqliteStateStore,
id: &ClaimId,
) -> Result<ContractClaim, ContractOpError> {
let item = store
.get_knowledge_item(id.as_str())
.await?
.ok_or(ContractOpError::NotFound)?;
if item.state != KnowledgeState::Pending {
return Err(ContractOpError::Conflict);
}
store
.update_knowledge_item_state(id.as_str(), KnowledgeState::Dismissed)
.await?;
let item = store
.get_knowledge_item(id.as_str())
.await?
.ok_or(ContractOpError::NotFound)?;
ContractClaim::from_knowledge_item(&item).ok_or(ContractOpError::Unavailable)
}
pub(crate) async fn forget(
store: &SqliteStateStore,
id: &ClaimId,
_reason: saya_store::ForgetReason,
) -> Result<(), ContractOpError> {
store.forget_knowledge_item(id.as_str()).await?;
Ok(())
}
async fn schema_availability_for(store: &SqliteStateStore, profile_id: &str) -> SchemaAvailability {
match store.get_schema(profile_id).await {
Ok(Some(cached)) => SchemaAvailability::available(cached.schema, cached.updated_unix_ms),
Ok(None) => SchemaAvailability::Missing,
Err(_) => SchemaAvailability::Unavailable,
}
}
fn usable_table_schema(availability: &SchemaAvailability) -> Option<&saya_types::SchemaTree> {
match availability.live_table_schema(SchemaFreshness::Unbounded) {
Some(schema) if !schema.databases.is_empty() => Some(schema),
_ => None,
}
}
fn find_table<'s>(
object: &DatabaseObjectRef,
schema: &'s saya_types::SchemaTree,
) -> Option<&'s Table> {
schema.find_table(object.catalog(), object.schema(), object.object())
}
fn binding_to_validate(item: &KnowledgeItem) -> Result<SchemaBinding, ContractOpError> {
if let Ok(binding) = serde_json::from_str::<SchemaBinding>(&item.schema_binding_json) {
return Ok(binding);
}
SchemaBinding::derive(&item.slot, &item.value).ok_or(ContractOpError::Invalid)
}