1use std::fmt;
9
10use serde::{Deserialize, Serialize};
11
12use crate::address::Address;
13use crate::error::{AxCode, AxError};
14
15const CITY: &str = "city";
17
18const PERSON: &str = "person";
23
24#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
36pub enum Who {
37 City,
39 Person,
41 Resident(Address),
43}
44
45impl Who {
46 pub fn resident(addr: Address) -> Result<Who, AxError> {
54 if addr.as_str() == CITY || addr.as_str() == PERSON {
55 return Err(AxError::failure(
56 AxCode::InvalidArgs,
57 "attribute a ledger line",
58 addr.as_str(),
59 )
60 .with_recovery(
61 "give this resident an address other than `city` or `person`: both \
62 words name another party the ledger attributes lines to",
63 ));
64 }
65 Ok(Who::Resident(addr))
66 }
67
68 #[must_use]
70 pub fn as_str(&self) -> &str {
71 match self {
72 Who::City => CITY,
73 Who::Person => PERSON,
74 Who::Resident(addr) => addr.as_str(),
75 }
76 }
77
78 pub fn parse(raw: &str) -> Result<Who, AxError> {
84 match raw {
85 CITY => Ok(Who::City),
86 PERSON => Ok(Who::Person),
87 other => Ok(Who::Resident(Address::parse(other)?)),
88 }
89 }
90}
91
92impl fmt::Display for Who {
93 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94 f.write_str(self.as_str())
95 }
96}
97
98impl Serialize for Who {
99 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
100 serializer.serialize_str(self.as_str())
101 }
102}
103
104impl<'de> Deserialize<'de> for Who {
105 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
106 let raw = String::deserialize(deserializer)?;
107 Who::parse(&raw).map_err(serde::de::Error::custom)
108 }
109}
110
111#[cfg(test)]
112#[allow(
113 clippy::unwrap_used,
114 clippy::expect_used,
115 clippy::panic,
116 clippy::indexing_slicing,
117 reason = "test code"
118)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn the_three_parties_write_the_words_the_histories_already_hold() {
124 let resident = Who::resident(Address::parse("lobby/scribe.md").unwrap()).unwrap();
125 assert_eq!(Who::City.to_string(), "city");
126 assert_eq!(Who::Person.to_string(), "person");
127 assert_eq!(resident.to_string(), "lobby/scribe.md");
128 for party in [Who::City, Who::Person, resident] {
129 assert_eq!(Who::parse(party.as_str()).unwrap(), party);
130 assert_eq!(
131 serde_json::to_string(&party).unwrap(),
132 format!("\"{party}\"")
133 );
134 }
135 }
136
137 #[test]
138 fn a_resident_may_not_answer_to_either_reserved_word() {
139 for reserved in ["city", "person"] {
140 let err = Who::resident(Address::parse(reserved).unwrap()).unwrap_err();
141 assert_eq!(err.code(), &AxCode::InvalidArgs);
142 }
143 }
144
145 #[test]
146 fn a_word_that_is_not_an_address_is_refused_rather_than_guessed() {
147 let err = Who::parse("../elsewhere").unwrap_err();
148 assert_eq!(err.code(), &AxCode::InvalidArgs);
149 }
150}