use super::{ContractConflictView, ContractQueueItemView, ContractView, Rendered};
const CLAIM_ID_PREFIX: usize = 6;
fn abbreviate_id(id: &str) -> String {
if id.len() > CLAIM_ID_PREFIX + 1 {
format!("{}…", &id[..CLAIM_ID_PREFIX])
} else {
id.to_string()
}
}
pub(super) fn list(contracts: &[ContractView]) -> Rendered {
if contracts.is_empty() {
return Rendered {
stdout: "No contracts found.\n".into(),
stderr: String::new(),
};
}
let mut stdout = String::new();
for contract in contracts {
stdout.push_str(&stanza(contract, false));
}
Rendered {
stdout,
stderr: String::new(),
}
}
pub(super) fn show(contract: &ContractView) -> Rendered {
let mut stdout = stanza(contract, true);
for conflict in &contract.conflicts {
stdout.push_str(&conflict_line(conflict));
}
if contract.truncated {
stdout.push_str(" [partial contract — some claims were omitted]\n");
}
Rendered {
stdout,
stderr: String::new(),
}
}
pub(super) fn changed(claim_id: &str, action: &str, status: &str) -> Rendered {
let line = match action {
"duplicate" => match status {
"forgotten" => format!("duplicate of {claim_id} — previously forgotten\n"),
other => format!("duplicate of {claim_id} — already exists ({other})\n"),
},
_ => format!("{action} {claim_id} ({status})\n"),
};
Rendered {
stdout: line,
stderr: String::new(),
}
}
pub(super) fn remembered(
object: &str,
kind: &str,
value: &str,
column: Option<&str>,
previous: Option<&str>,
action: &str,
status: &str,
) -> Rendered {
let col = match column {
Some(col) => format!(" (col: {col})"),
None => String::new(),
};
let line = match action {
"replaced" => {
let prev = previous.unwrap_or("");
format!("replaced {kind} for {object}{col}: \"{prev}\" -> \"{value}\"\n")
}
"duplicate" => match status {
"forgotten" => {
format!("duplicate of {kind} {value} for {object}{col} — previously forgotten\n")
}
other => {
format!(
"duplicate of {kind} {value} for {object}{col} — already exists ({other})\n"
)
}
},
_ => format!("remembered {kind} {value} for {object}{col} ({status})\n"),
};
Rendered {
stdout: line,
stderr: String::new(),
}
}
pub(super) fn queue(items: &[ContractQueueItemView]) -> Rendered {
if items.is_empty() {
return Rendered {
stdout: "No candidates awaiting review.\n".into(),
stderr: String::new(),
};
}
let mut stdout = String::new();
for item in items {
stdout.push_str(&format!(
"{id} {status} {kind} {value}{column} {object} [{state}]{note} evidence {count} (profile: {profile})\n",
id = item.claim_id,
status = item.status,
kind = item.kind,
value = item.value,
column = column_suffix(item.column.as_deref()),
object = item.object,
state = item.schema_state,
note = schema_state_note(&item.schema_state),
count = item.evidence_count,
profile = item.profile,
));
}
Rendered {
stdout,
stderr: String::new(),
}
}
fn column_suffix(column: Option<&str>) -> String {
match column {
Some(column) => format!(" col:{column}"),
None => String::new(),
}
}
fn stanza(contract: &ContractView, with_reason: bool) -> String {
let mut out = String::new();
out.push_str(&format!(
"{object} [{state}]{profile}{state_note}\n",
object = contract.object,
state = contract.schema_state,
profile = profile_suffix(&contract.profile),
state_note = schema_state_note(&contract.schema_state),
));
for claim in &contract.claims {
out.push_str(&format!(
" {id} {kind} {status} {origin} {value}\n",
id = abbreviate_id(&claim.claim_id),
kind = claim.kind,
status = claim.status,
origin = claim.origin,
value = claim.value,
));
if with_reason && let Some(reason) = claim.reason.as_deref().filter(|r| !r.is_empty()) {
out.push_str(&format!(" because: {reason}\n"));
}
}
out
}
fn profile_suffix(profile: &str) -> String {
format!(" (profile: {profile})")
}
fn schema_state_note(state: &str) -> &'static str {
match state {
"needs_review" => " — the schema changed since the claim was made",
"stale" => " — a column it depends on is gone",
"live_schema_unavailable" => " — the schema could not be read",
_ => "",
}
}
fn conflict_line(conflict: &ContractConflictView) -> String {
let claimers = conflict
.claim_ids
.iter()
.map(|id| abbreviate_id(id))
.collect::<Vec<_>>()
.join(", ");
format!(" conflict: {} claimed by {}\n", conflict.kind, claimers)
}