use crate::connection::ConnectionRegistry;
use crate::contracts::{ContractClaim, ContractSchemaState, RetrievedContract};
use saya_types::ClaimPayload;
use std::collections::{HashMap, HashSet};
use std::fmt::Write;
pub(super) fn name_by_identity(registry: &ConnectionRegistry) -> HashMap<String, String> {
registry
.entries()
.iter()
.filter_map(|(name, entry)| {
entry
.profile_id
.as_deref()
.map(|id| (id.to_string(), name.to_string()))
})
.collect()
}
pub(super) fn render_body(
contracts: &[RetrievedContract],
name_of: &HashMap<String, String>,
) -> String {
let mut out = String::new();
for contract in contracts {
let profile_name = name_of
.get(contract.object.profile().as_str())
.map(String::as_str)
.unwrap_or("");
let state = schema_state_token(contract.schema_state);
let _ = writeln!(
out,
"{obj} [{state}] (profile: {profile}){stale}",
obj = contract.object.qualified_name(),
state = state,
profile = profile_name,
stale = stale_note(contract.schema_state),
);
let _ = writeln!(out, " {CONFIRMED_DIRECTIVE}");
let disputed: HashSet<String> = super::dispute::disputed_ids(&contract.conflicts);
for claim in &contract.claims {
let is_disputed = disputed.contains(claim.id.as_str());
let line = claim_line(claim, is_disputed);
if !line.is_empty() {
let _ = writeln!(out, " {line}");
}
if let Some(reason) = claim_reason(&claim.value) {
let _ = writeln!(out, " because: {reason}");
}
}
out.push_str(&super::dispute::conflict_lines(contract));
}
out
}
pub(super) const CONFIRMED_DIRECTIVE: &str = "Confirmed claims below bind: use them as given, and say in the answer when you depart from one.";
fn claim_line(claim: &ContractClaim, is_disputed: bool) -> String {
let payload = &claim.value;
let marker = authority_marker(claim.status, is_disputed);
let (column, value) = claim_value(payload);
match (column.as_deref(), value.is_empty()) {
(Some(col), false) => format!("{marker}{} {col}: {value}", payload.kind()),
(Some(_), true) => format!("{marker}{}", payload.kind()),
(None, false) => format!("{marker}{} {value}", payload.kind()),
(None, true) => format!("{marker}{}", payload.kind()),
}
}
pub(crate) fn claim_reason(payload: &ClaimPayload) -> Option<&str> {
match payload {
ClaimPayload::DefaultTimeColumn { reason, .. } => reason.as_deref(),
ClaimPayload::TableGrain { reason, .. } => reason.as_deref(),
ClaimPayload::ColumnRole { reason, .. } => reason.as_deref(),
_ => None,
}
}
pub(crate) fn claim_value(payload: &ClaimPayload) -> (Option<String>, String) {
match payload {
ClaimPayload::TableDescription { text, .. } => (None, text.clone()),
ClaimPayload::TableAlias { alias, .. } => (None, alias.clone()),
ClaimPayload::TableGrain { description, .. } => (None, description.clone()),
ClaimPayload::ColumnDescription { column, text, .. } => {
(Some(column.clone()), text.clone())
}
ClaimPayload::ColumnRole { column, role, .. } => {
(Some(column.clone()), role.as_str().to_string())
}
ClaimPayload::DefaultTimeColumn { column, .. } => (None, column.clone()),
_ => (None, String::new()),
}
}
fn authority_marker(status: saya_types::ClaimStatus, is_disputed: bool) -> &'static str {
if is_disputed {
return super::dispute::DISPUTE_MARKER;
}
match status {
saya_types::ClaimStatus::Candidate => "[candidate — unconfirmed] ",
saya_types::ClaimStatus::Confirmed => "[confirmed] ",
saya_types::ClaimStatus::Rejected
| saya_types::ClaimStatus::Stale
| saya_types::ClaimStatus::Contradicted
| saya_types::ClaimStatus::Forgotten => "",
_ => "",
}
}
fn schema_state_token(state: ContractSchemaState) -> &'static str {
match state {
ContractSchemaState::Current => "current",
ContractSchemaState::NeedsReview => "needs_review",
ContractSchemaState::Stale => "stale",
ContractSchemaState::LiveSchemaUnavailable => "live_schema_unavailable",
}
}
fn stale_note(state: ContractSchemaState) -> &'static str {
match state {
ContractSchemaState::Stale => " — possibly out of date: a column it depends on is gone",
_ => "",
}
}