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]
115 .iter()
116 .any(|earlier| take::to(*earlier) == named)
117 {
118 bail!("two ways of taking are named {named}");
119 }
120 if take::from(named) != Some(way) {
121 bail!("{named} is not read back as itself");
122 }
123 if way.summary().is_empty() {
124 bail!("{named} does not say what it does");
125 }
126 }
127 if take::from("nothing anybody would call a way of taking").is_some() {
128 bail!("a word naming no way of taking names one");
129 }
130
131 for kind in attachment_kind::ALL.iter().copied() {
132 if !Take::Everything.accepts(kind) {
133 bail!(
134 "taking everything leaves {} behind",
135 attachment_kind::to(kind)
136 );
137 }
138 let ways = [Take::Videos, Take::Images]
141 .into_iter()
142 .filter(|way| way.accepts(kind))
143 .count();
144 if ways != 1 {
145 bail!(
146 "{} is taken by {ways} ways rather than one",
147 attachment_kind::to(kind)
148 );
149 }
150 if attachment_kind::from(attachment_kind::to(kind)) != Some(kind) {
151 bail!("{} is not read back as itself", attachment_kind::to(kind));
152 }
153 }
154 Ok(())
155}