use serde::{Deserialize, Serialize};
use super::{CouncilSeat, CouncilVote, RecordRuleError};
use crate::enums::GovernanceLogEntryType;
use crate::govlog::Redactable;
pub const SUMMARY_POINTER: &str = "/summary";
pub const SUMMARY_WRITER: CouncilSeat = CouncilSeat::Lawyer;
pub const RECUSED_ACCEPTORS: [CouncilSeat; 3] = [
CouncilSeat::Artist,
CouncilSeat::Philosopher,
CouncilSeat::Engineer,
];
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "schemars", schemars(inline))]
pub struct DecisionSummary {
pub text: Redactable<String>,
pub written_by: CouncilSeat,
pub accepted_by: SummaryAcceptance,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "schemars", schemars(inline))]
#[serde(tag = "by", rename_all = "snake_case")]
pub enum SummaryAcceptance {
Steward,
Seats { seats: Vec<CouncilSeat> },
}
impl SummaryAcceptance {
pub fn for_item(steward_recused: bool) -> Self {
if steward_recused {
Self::Seats {
seats: RECUSED_ACCEPTORS.to_vec(),
}
} else {
Self::Steward
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "schemars", schemars(inline))]
pub struct SummaryAuthorship {
pub written_by: CouncilSeat,
pub accepted_by: SummaryAcceptance,
}
impl SummaryAuthorship {
pub fn sentence(&self) -> String {
let accepted = match &self.accepted_by {
SummaryAcceptance::Steward => "the Steward".to_string(),
SummaryAcceptance::Seats { seats } => {
let names: Vec<&str> = seats.iter().map(|s| s.name()).collect();
match names.as_slice() {
[] => String::from("no seat"),
[one] => format!("the {one}"),
[init @ .., last] => format!(
"the {} and {last}, the Steward being recused",
init.join(", ")
),
}
}
};
format!("written by the {}, accepted by {accepted}", self.written_by)
}
}
impl DecisionSummary {
pub fn by_lawyer(text: impl Into<String>, steward_recused: bool) -> Self {
Self {
text: Redactable::Value(text.into()),
written_by: SUMMARY_WRITER,
accepted_by: SummaryAcceptance::for_item(steward_recused),
}
}
pub fn authorship(&self) -> SummaryAuthorship {
SummaryAuthorship {
written_by: self.written_by,
accepted_by: self.accepted_by.clone(),
}
}
pub fn served_text(&self) -> String {
match &self.text {
Redactable::Value(text) => text.clone(),
Redactable::Redacted(id) => crate::govlog::redaction_marker(id),
}
}
pub fn of_data(
data: &serde_json::Value,
) -> Result<Option<Self>, serde_json::Error> {
data.get("summary")
.map(|s| serde_json::from_value(s.clone()))
.transpose()
}
pub(super) fn check(
&self,
steward: Option<CouncilVote>,
) -> Result<(), RecordRuleError> {
if self.written_by != SUMMARY_WRITER {
return Err(RecordRuleError::SummaryWriter(self.written_by));
}
if let Redactable::Value(text) = &self.text
&& text.trim().is_empty()
{
return Err(RecordRuleError::BlankSummary);
}
let recused = steward == Some(CouncilVote::Recused);
if self.accepted_by != SummaryAcceptance::for_item(recused) {
return Err(RecordRuleError::SummaryAcceptance { recused });
}
Ok(())
}
}
pub fn served_summary(
entry_type: GovernanceLogEntryType,
served: Option<&serde_json::Value>,
clerk: Option<String>,
) -> (Option<String>, Option<SummaryAuthorship>) {
let test = entry_type == GovernanceLogEntryType::Test;
let signed = served
.and_then(|data| {
let inner = crate::govlog::decision_data(entry_type, data)?;
(crate::govlog::decision_type(entry_type, data)
== GovernanceLogEntryType::CouncilDecision)
.then_some(inner)
})
.and_then(|data| DecisionSummary::of_data(data).ok().flatten());
let (text, authorship) = match signed {
Some(summary) => {
(Some(summary.served_text()), Some(summary.authorship()))
}
None => (clerk, None),
};
let text = if test {
text.map(|t| test_labelled(&t))
} else {
text
};
(text, authorship)
}
pub fn test_labelled(text: &str) -> String {
let label = format!("**{}.**", crate::govlog::TEST_LABEL);
if text.starts_with(&label) {
text.to_string()
} else {
format!("{label}\n\n{text}")
}
}