use super::contracts_remember_schema::{
SchemaCheck, fingerprint_of, refuse_unknown, resolved_against,
};
use super::{
ArgMessage, EXIT_CONTRACT_ERROR, arg_failure, cached_schema, op_failure, parse_claim_id,
unobserved_fingerprint,
};
use crate::cli::{ClaimKindArg, ForgetReasonArg};
use crate::commands::output::{emit, failure_message};
use crate::contracts::args::{build_payload, parse_qualified};
use crate::contracts::{RememberOutcome, forget, remember as remember_op};
use crate::render::{RenderFormat, TerminalEvent};
use saya_store::{ForgetReason, SqliteStateStore};
use saya_types::{
ClaimStatus, DatabaseObjectKind, DatabaseObjectRef, KnowledgeState, ProfileIdentity,
};
pub(super) struct RememberRequest<'a> {
pub table: &'a str,
pub kind: ClaimKindArg,
pub value: &'a str,
pub column: Option<&'a str>,
pub reason: Option<&'a str>,
}
pub(super) struct RememberContext<'a> {
pub store: &'a SqliteStateStore,
pub format: RenderFormat,
pub profile_name: &'a str,
pub identity: &'a ProfileIdentity,
}
pub(super) async fn remember(
request: RememberRequest<'_>,
context: RememberContext<'_>,
) -> Result<i32, Box<dyn std::error::Error>> {
let RememberRequest {
table,
kind,
value,
column,
reason,
} = request;
let RememberContext {
store,
format,
profile_name,
identity,
} = context;
let qualified = match parse_qualified(table) {
Ok(q) => q,
Err(_) => return arg_failure(ArgMessage::MalformedTable, format),
};
let payload = match build_payload(kind, value, column, reason) {
Ok(payload) => payload,
Err(_) => return arg_failure(ArgMessage::BadValue, format),
};
let object = match DatabaseObjectRef::new(
identity.clone(),
&qualified.catalog,
&qualified.schema,
&qualified.object,
DatabaseObjectKind::Table,
) {
Ok(object) => object,
Err(_) => return arg_failure(ArgMessage::MalformedTable, format),
};
let cached = cached_schema(store, identity).await;
let fingerprint = match resolved_against(&cached, &object) {
SchemaCheck::Found(table) => fingerprint_of(table),
SchemaCheck::Absent => return refuse_unknown(&object, profile_name, format),
SchemaCheck::NoSchema => unobserved_fingerprint(),
};
let outcome = match remember_op(store, &object, &payload, fingerprint).await {
Ok(outcome) => outcome,
Err(error) => return op_failure(error, format),
};
let (claim_id, action, status, previous) = match outcome {
RememberOutcome::Stored { id } => (id, "remembered", ClaimStatus::Confirmed, None),
RememberOutcome::Duplicate { id, state } => {
(id, "duplicate", status_from_state(state), None)
}
RememberOutcome::Replaced { id, previous } => {
(id, "replaced", ClaimStatus::Confirmed, Some(previous))
}
};
emit(
TerminalEvent::ContractRemembered {
claim_id: claim_id.as_str().to_string(),
object: format!(
"{}.{}.{}",
qualified.catalog, qualified.schema, qualified.object
),
kind: kind.as_str().to_string(),
value: value.to_string(),
column: column.map(str::to_string),
previous,
action: action.into(),
status: status.as_str().into(),
},
format,
);
Ok(0)
}
fn status_from_state(state: KnowledgeState) -> ClaimStatus {
match state {
KnowledgeState::Active => ClaimStatus::Confirmed,
KnowledgeState::Pending => ClaimStatus::Candidate,
KnowledgeState::Dismissed => ClaimStatus::Forgotten,
_ => ClaimStatus::Forgotten,
}
}
pub(super) async fn forget_claim(
store: &SqliteStateStore,
format: RenderFormat,
claim_id: &str,
reason: ForgetReasonArg,
) -> Result<i32, Box<dyn std::error::Error>> {
let id = match parse_claim_id(claim_id) {
Ok(id) => id,
Err(message) => return failure_message(EXIT_CONTRACT_ERROR, message, format),
};
if let Err(error) = forget(store, &id, forget_reason(reason)).await {
return op_failure(error, format);
}
emit(
TerminalEvent::ContractChanged {
claim_id: id.as_str().to_string(),
action: "forgotten".into(),
status: "forgotten".into(),
},
format,
);
Ok(0)
}
fn forget_reason(arg: ForgetReasonArg) -> ForgetReason {
match arg {
ForgetReasonArg::UserRequest => ForgetReason::UserRequest,
ForgetReasonArg::Incorrect => ForgetReason::Incorrect,
ForgetReasonArg::Obsolete => ForgetReason::Obsolete,
ForgetReasonArg::Privacy => ForgetReason::Privacy,
}
}