1use crate::*;
8use alux_ext::ext;
9use anyhow::{Result, bail};
10
11const LAW_STATUS: &str = "1234567890123456789";
13
14#[ext(name = XLaws)]
16pub impl<This> This
17where
18 This: XRequestAlg + XRequestViewAlg,
19{
20 fn x_laws(&self) -> Result<()> {
35 self.check_address_laws()?;
36 self.check_request_laws()?;
37 check_taking_laws()
38 }
39
40 fn check_address_laws(&self) -> Result<()> {
46 for host in X_HOSTS {
47 for shape in [
48 format!("https://{host}/somebody/status/{LAW_STATUS}"),
49 format!("http://{host}/somebody/status/{LAW_STATUS}"),
50 format!("https://{host}/somebody/statuses/{LAW_STATUS}"),
51 format!("https://{host}/somebody/status/{LAW_STATUS}?s=20&t=abc"),
52 format!("https://{host}/somebody/status/{LAW_STATUS}/photo/1"),
53 format!(" https://{host}/somebody/status/{LAW_STATUS} "),
54 ] {
55 if status_id(&shape).as_deref() != Some(LAW_STATUS) {
56 bail!("{shape} does not name the tweet it addresses");
57 }
58 }
59 }
60 for shape in [
61 "https://example.test/somebody/status/1234567890123456789",
62 "https://x.com/somebody",
63 "https://x.com/somebody/status/",
64 "https://x.com/somebody/status/not-a-number",
65 "https://x.com.example.test/somebody/status/1234567890123456789",
66 "/home/dev/x.com/somebody/status/1234567890123456789",
67 "https://www.youtube.com/watch?v=VIDEO_ID",
68 ] {
69 if let Some(named) = status_id(shape) {
70 bail!("{shape} names the tweet {named}, and addresses no tweet");
71 }
72 }
73 Ok(())
74 }
75
76 fn check_request_laws(&self) -> Result<()> {
82 let asked = self.status_request(LAW_STATUS);
83 let asked_again = self.status_request(LAW_STATUS);
84 let address = self.request_address(&asked).to_owned();
85 if address != self.request_address(&asked_again) {
86 bail!("asking twice about one tweet asks two different things");
87 }
88 if !address.starts_with(READING_ADDRESS) {
89 bail!("a tweet is asked about somewhere other than where tweets are read");
90 }
91 if !address.contains(&format!("id={LAW_STATUS}")) {
92 bail!("asking about a tweet does not name which tweet");
93 }
94 let stated = reading_token(LAW_STATUS);
96 if stated.is_empty() || !address.contains(&format!("token={stated}")) {
97 bail!("asking about a tweet carries no token");
98 }
99 if reading_token("1") == reading_token("2") {
100 bail!("two tweets are asked about with one token");
101 }
102 Ok(())
103 }
104}
105
106fn check_taking_laws() -> Result<()> {
112 for (place, way) in take::ALL.iter().copied().enumerate() {
113 let named = take::to(way);
114 if take::ALL[..place].iter().any(|earlier| take::to(*earlier) == named) {
115 bail!("two ways of taking are named {named}");
116 }
117 if take::from(named) != Some(way) {
118 bail!("{named} is not read back as itself");
119 }
120 if way.summary().is_empty() {
121 bail!("{named} does not say what it does");
122 }
123 }
124 if take::from("nothing anybody would call a way of taking").is_some() {
125 bail!("a word naming no way of taking names one");
126 }
127
128 for kind in attachment_kind::ALL.iter().copied() {
129 if !Take::Everything.accepts(kind) {
130 bail!("taking everything leaves {} behind", attachment_kind::to(kind));
131 }
132 let ways = [Take::Videos, Take::Images].into_iter().filter(|way| way.accepts(kind)).count();
135 if ways != 1 {
136 bail!("{} is taken by {ways} ways rather than one", attachment_kind::to(kind));
137 }
138 if attachment_kind::from(attachment_kind::to(kind)) != Some(kind) {
139 bail!("{} is not read back as itself", attachment_kind::to(kind));
140 }
141 }
142 Ok(())
143}