atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
177535ac8abf29d1d6eaa1bd35a72ae370329b5f
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git

Blame: src/game/encounters.rs

dfa38cf4cd4batagen2026-10-10 09:17 UTC1
use std::collections::HashMap;
dfa38cf4cd4batagen2026-10-10 09:17 UTC2
dfa38cf4cd4batagen2026-10-10 09:17 UTC3
use axum::http::{StatusCode, header};
dfa38cf4cd4batagen2026-10-10 09:17 UTC4
use rand::Rng;
dfa38cf4cd4batagen2026-10-10 09:17 UTC5
use serde::Deserialize;
dfa38cf4cd4batagen2026-10-10 09:17 UTC6
use serde_json::{Value, json};
dfa38cf4cd4batagen2026-10-10 09:17 UTC7
dfa38cf4cd4batagen2026-10-10 09:17 UTC8
use super::{
177535ac8abfatagen2026-10-10 10:39 UTC9
    Floor, Grip, Outcome, Run, SHOP, SUSPICION_LIMIT, X_DIAGNOSIS, honest, parse_credentials,
177535ac8abfatagen2026-10-10 10:39 UTC10
    round2,
dfa38cf4cd4batagen2026-10-10 09:17 UTC11
};
dfa38cf4cd4batagen2026-10-10 09:17 UTC12
use crate::problem::Problem;
dfa38cf4cd4batagen2026-10-10 09:17 UTC13
dfa38cf4cd4batagen2026-10-10 09:17 UTC14
pub const FORUM_THREAD: &str = "/forum/threads/2009";
dfa38cf4cd4batagen2026-10-10 09:17 UTC15
pub const ELDER_LAST_MODIFIED: &str = "Sun, 14 Jun 2009 03:12:00 GMT";
dfa38cf4cd4batagen2026-10-10 09:17 UTC16
dfa38cf4cd4batagen2026-10-10 09:17 UTC17
pub const EXCUSES: [&str; 3] = ["changing", "homework", "pulled a muscle"];
dfa38cf4cd4batagen2026-10-10 09:17 UTC18
pub const THANKSGIVING_EXCUSES: [&str; 3] = ["on a call", "folding laundry", "praying"];
dfa38cf4cd4batagen2026-10-10 09:17 UTC19
const TRUTHS: [&str; 3] = ["the truth", "truth", "honesty"];
dfa38cf4cd4batagen2026-10-10 09:17 UTC20
dfa38cf4cd4batagen2026-10-10 09:17 UTC21
const INTAKE: [&str; 3] = [
dfa38cf4cd4batagen2026-10-10 09:17 UTC22
    "The clinic does not accept excuses.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC23
    "The clinic accepts one token. You have not been carrying it.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC24
    "Be honest.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC25
];
dfa38cf4cd4batagen2026-10-10 09:17 UTC26
dfa38cf4cd4batagen2026-10-10 09:17 UTC27
const SPREADSHEET: &str = "\
dfa38cf4cd4batagen2026-10-10 09:17 UTC28
date,length,girth,technique,notes,honest?
dfa38cf4cd4batagen2026-10-10 09:17 UTC29
2007-01-03,7.50,5.50,wet,baseline,no
dfa38cf4cd4batagen2026-10-10 09:17 UTC30
2007-06-11,7.50,5.50,wet,feeling fuller,no
dfa38cf4cd4batagen2026-10-10 09:17 UTC31
2008-01-02,7.50,5.50,dry,new routine,no
dfa38cf4cd4batagen2026-10-10 09:17 UTC32
2008-09-19,7.50,5.50,uli,PUT is idempotent. do not use PUT.,no
dfa38cf4cd4batagen2026-10-10 09:17 UTC33
2009-06-14,7.50,5.50,wet,progress is slow but real,no
dfa38cf4cd4batagen2026-10-10 09:17 UTC34
";
dfa38cf4cd4batagen2026-10-10 09:17 UTC35
dfa38cf4cd4batagen2026-10-10 09:17 UTC36
pub fn forum_thread() -> Value {
dfa38cf4cd4batagen2026-10-10 09:17 UTC37
    json!({
dfa38cf4cd4batagen2026-10-10 09:17 UTC38
        "title": "The definitive jelqing FAQ (updated 2009)",
dfa38cf4cd4batagen2026-10-10 09:17 UTC39
        "posts": [
dfa38cf4cd4batagen2026-10-10 09:17 UTC40
            { "author": "TheElder", "body": "Warm up. Always warm up. A cold jelq is a 412 waiting to happen." },
dfa38cf4cd4batagen2026-10-10 09:17 UTC41
            { "author": "TheElder", "body": "Keep EQ between 50 and 70. Above 70 you are gambling. Above 90 you are a cautionary tale." },
dfa38cf4cd4batagen2026-10-10 09:17 UTC42
            { "author": "newbie_2009", "body": "I have been sending PUT for three weeks and nothing has changed." },
dfa38cf4cd4batagen2026-10-10 09:17 UTC43
            { "author": "TheElder", "body": "PUT is idempotent. Read the spec." },
dfa38cf4cd4batagen2026-10-10 09:17 UTC44
            { "author": "TheElder", "body": "Members verify with a Ruler. Proxy-Authorization, inches, no rounding up. We can tell." },
dfa38cf4cd4batagen2026-10-10 09:17 UTC45
            { "author": "throwaway_77", "body": "my doctor said i was normal. i didnt believe him. i still dont know what he meant by bearer" },
dfa38cf4cd4batagen2026-10-10 09:17 UTC46
            { "author": "TheElder", "body": "My numbers are in my spreadsheet. They have not changed since 2009. Nothing about me has changed since 2009." },
dfa38cf4cd4batagen2026-10-10 09:17 UTC47
        ],
dfa38cf4cd4batagen2026-10-10 09:17 UTC48
    })
dfa38cf4cd4batagen2026-10-10 09:17 UTC49
}
dfa38cf4cd4batagen2026-10-10 09:17 UTC50
dfa38cf4cd4batagen2026-10-10 09:17 UTC51
#[derive(Deserialize)]
dfa38cf4cd4batagen2026-10-10 09:17 UTC52
pub struct PatchOp {
dfa38cf4cd4batagen2026-10-10 09:17 UTC53
    pub op: String,
dfa38cf4cd4batagen2026-10-10 09:17 UTC54
    pub path: String,
dfa38cf4cd4batagen2026-10-10 09:17 UTC55
    #[serde(default)]
dfa38cf4cd4batagen2026-10-10 09:17 UTC56
    pub value: Value,
dfa38cf4cd4batagen2026-10-10 09:17 UTC57
}
dfa38cf4cd4batagen2026-10-10 09:17 UTC58
dfa38cf4cd4batagen2026-10-10 09:17 UTC59
fn digest_params(authorization: &str) -> Option<HashMap<String, String>> {
dfa38cf4cd4batagen2026-10-10 09:17 UTC60
    let (scheme, rest) = authorization.trim().split_once(' ')?;
dfa38cf4cd4batagen2026-10-10 09:17 UTC61
    if !scheme.eq_ignore_ascii_case("digest") {
dfa38cf4cd4batagen2026-10-10 09:17 UTC62
        return None;
dfa38cf4cd4batagen2026-10-10 09:17 UTC63
    }
dfa38cf4cd4batagen2026-10-10 09:17 UTC64
    Some(
dfa38cf4cd4batagen2026-10-10 09:17 UTC65
        rest.split(',')
dfa38cf4cd4batagen2026-10-10 09:17 UTC66
            .filter_map(|pair| {
dfa38cf4cd4batagen2026-10-10 09:17 UTC67
                let (key, value) = pair.split_once('=')?;
dfa38cf4cd4batagen2026-10-10 09:17 UTC68
                Some((
dfa38cf4cd4batagen2026-10-10 09:17 UTC69
                    key.trim().to_ascii_lowercase(),
dfa38cf4cd4batagen2026-10-10 09:17 UTC70
                    value.trim().trim_matches('"').to_owned(),
dfa38cf4cd4batagen2026-10-10 09:17 UTC71
                ))
dfa38cf4cd4batagen2026-10-10 09:17 UTC72
            })
dfa38cf4cd4batagen2026-10-10 09:17 UTC73
            .collect(),
dfa38cf4cd4batagen2026-10-10 09:17 UTC74
    )
dfa38cf4cd4batagen2026-10-10 09:17 UTC75
}
dfa38cf4cd4batagen2026-10-10 09:17 UTC76
dfa38cf4cd4batagen2026-10-10 09:17 UTC77
fn exaggeration() -> Problem {
dfa38cf4cd4batagen2026-10-10 09:17 UTC78
    Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC79
        StatusCode::BAD_REQUEST,
dfa38cf4cd4batagen2026-10-10 09:17 UTC80
        "exaggeration",
dfa38cf4cd4batagen2026-10-10 09:17 UTC81
        "The server has measured. Lying is not a technique.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC82
    )
dfa38cf4cd4batagen2026-10-10 09:17 UTC83
}
dfa38cf4cd4batagen2026-10-10 09:17 UTC84
dfa38cf4cd4batagen2026-10-10 09:17 UTC85
impl Run {
dfa38cf4cd4batagen2026-10-10 09:17 UTC86
    pub fn excuses(&self) -> Vec<&'static str> {
dfa38cf4cd4batagen2026-10-10 09:17 UTC87
        if self.floor.number() < Floor::Grandmas.number() {
dfa38cf4cd4batagen2026-10-10 09:17 UTC88
            return EXCUSES.to_vec();
dfa38cf4cd4batagen2026-10-10 09:17 UTC89
        }
dfa38cf4cd4batagen2026-10-10 09:17 UTC90
        EXCUSES
dfa38cf4cd4batagen2026-10-10 09:17 UTC91
            .into_iter()
dfa38cf4cd4batagen2026-10-10 09:17 UTC92
            .chain(THANKSGIVING_EXCUSES)
dfa38cf4cd4batagen2026-10-10 09:17 UTC93
            .filter(|e| !self.heard.iter().any(|h| h == e))
dfa38cf4cd4batagen2026-10-10 09:17 UTC94
            .collect()
dfa38cf4cd4batagen2026-10-10 09:17 UTC95
    }
dfa38cf4cd4batagen2026-10-10 09:17 UTC96
dfa38cf4cd4batagen2026-10-10 09:17 UTC97
    pub fn answer_door(&mut self, authorization: Option<&str>) -> Result<(), Problem> {
dfa38cf4cd4batagen2026-10-10 09:17 UTC98
        let digest = self.floor == Floor::Grandmas;
dfa38cf4cd4batagen2026-10-10 09:17 UTC99
        let challenge_value = if digest {
dfa38cf4cd4batagen2026-10-10 09:17 UTC100
            format!("Digest realm=\"household\", nonce=\"{}\"", self.nonce)
dfa38cf4cd4batagen2026-10-10 09:17 UTC101
        } else {
dfa38cf4cd4batagen2026-10-10 09:17 UTC102
            "Excuse realm=\"household\"".to_owned()
dfa38cf4cd4batagen2026-10-10 09:17 UTC103
        };
dfa38cf4cd4batagen2026-10-10 09:17 UTC104
        let challenge = |p: Problem| p.header(header::WWW_AUTHENTICATE, &challenge_value);
dfa38cf4cd4batagen2026-10-10 09:17 UTC105
dfa38cf4cd4batagen2026-10-10 09:17 UTC106
        let Some(authorization) = authorization else {
dfa38cf4cd4batagen2026-10-10 09:17 UTC107
            return Err(challenge(
dfa38cf4cd4batagen2026-10-10 09:17 UTC108
                Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC109
                    StatusCode::UNAUTHORIZED,
dfa38cf4cd4batagen2026-10-10 09:17 UTC110
                    "someone-at-the-door",
dfa38cf4cd4batagen2026-10-10 09:17 UTC111
                    format!("Someone is at the door. {}", self.mom_errand),
dfa38cf4cd4batagen2026-10-10 09:17 UTC112
                )
dfa38cf4cd4batagen2026-10-10 09:17 UTC113
                .with("inventory", format!("/runs/{}/inventory", self.id)),
dfa38cf4cd4batagen2026-10-10 09:17 UTC114
            ));
dfa38cf4cd4batagen2026-10-10 09:17 UTC115
        };
dfa38cf4cd4batagen2026-10-10 09:17 UTC116
177535ac8abfatagen2026-10-10 10:39 UTC117
        let spoken = authorization
177535ac8abfatagen2026-10-10 10:39 UTC118
            .split_once(' ')
177535ac8abfatagen2026-10-10 10:39 UTC119
            .map(|(_, rest)| rest.trim().trim_matches('"').to_ascii_lowercase())
177535ac8abfatagen2026-10-10 10:39 UTC120
            .unwrap_or_default();
177535ac8abfatagen2026-10-10 10:39 UTC121
        if TRUTHS.contains(&spoken.as_str()) {
177535ac8abfatagen2026-10-10 10:39 UTC122
            self.shame += 10;
177535ac8abfatagen2026-10-10 10:39 UTC123
            self.leave_door();
177535ac8abfatagen2026-10-10 10:39 UTC124
            return Ok(());
177535ac8abfatagen2026-10-10 10:39 UTC125
        }
177535ac8abfatagen2026-10-10 10:39 UTC126
        if parse_credentials(authorization, "basic").is_some() {
177535ac8abfatagen2026-10-10 10:39 UTC127
            return Err(challenge(Problem::new(
177535ac8abfatagen2026-10-10 10:39 UTC128
                StatusCode::UNAUTHORIZED,
177535ac8abfatagen2026-10-10 10:39 UTC129
                "basic",
177535ac8abfatagen2026-10-10 10:39 UTC130
                "Mom does not accept Basic authentication. Nobody should.",
177535ac8abfatagen2026-10-10 10:39 UTC131
            )));
177535ac8abfatagen2026-10-10 10:39 UTC132
        }
177535ac8abfatagen2026-10-10 10:39 UTC133
dfa38cf4cd4batagen2026-10-10 09:17 UTC134
        let excuse = if digest {
dfa38cf4cd4batagen2026-10-10 09:17 UTC135
            let Some(params) = digest_params(authorization) else {
dfa38cf4cd4batagen2026-10-10 09:17 UTC136
                return Err(challenge(Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC137
                    StatusCode::UNAUTHORIZED,
dfa38cf4cd4batagen2026-10-10 09:17 UTC138
                    "digest-required",
dfa38cf4cd4batagen2026-10-10 09:17 UTC139
                    "Mom has started using Digest authentication.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC140
                )));
dfa38cf4cd4batagen2026-10-10 09:17 UTC141
            };
dfa38cf4cd4batagen2026-10-10 09:17 UTC142
            if params.get("nonce") != Some(&self.nonce) {
dfa38cf4cd4batagen2026-10-10 09:17 UTC143
                return Err(challenge(Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC144
                    StatusCode::UNAUTHORIZED,
dfa38cf4cd4batagen2026-10-10 09:17 UTC145
                    "stale-nonce",
dfa38cf4cd4batagen2026-10-10 09:17 UTC146
                    "That was a different conversation.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC147
                )));
dfa38cf4cd4batagen2026-10-10 09:17 UTC148
            }
dfa38cf4cd4batagen2026-10-10 09:17 UTC149
            params.get("username").cloned().unwrap_or_default()
dfa38cf4cd4batagen2026-10-10 09:17 UTC150
        } else {
dfa38cf4cd4batagen2026-10-10 09:17 UTC151
            parse_credentials(authorization, "excuse")
dfa38cf4cd4batagen2026-10-10 09:17 UTC152
                .unwrap_or_default()
dfa38cf4cd4batagen2026-10-10 09:17 UTC153
                .to_owned()
dfa38cf4cd4batagen2026-10-10 09:17 UTC154
        };
dfa38cf4cd4batagen2026-10-10 09:17 UTC155
        let excuse = excuse.to_ascii_lowercase();
dfa38cf4cd4batagen2026-10-10 09:17 UTC156
dfa38cf4cd4batagen2026-10-10 09:17 UTC157
        if TRUTHS.contains(&excuse.as_str()) {
dfa38cf4cd4batagen2026-10-10 09:17 UTC158
            self.shame += 10;
dfa38cf4cd4batagen2026-10-10 09:17 UTC159
            self.leave_door();
dfa38cf4cd4batagen2026-10-10 09:17 UTC160
            return Ok(());
dfa38cf4cd4batagen2026-10-10 09:17 UTC161
        }
dfa38cf4cd4batagen2026-10-10 09:17 UTC162
        if digest && self.heard.contains(&excuse) {
dfa38cf4cd4batagen2026-10-10 09:17 UTC163
            self.shame += 3;
dfa38cf4cd4batagen2026-10-10 09:17 UTC164
            self.touch();
dfa38cf4cd4batagen2026-10-10 09:17 UTC165
            return Err(challenge(Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC166
                StatusCode::UNAUTHORIZED,
dfa38cf4cd4batagen2026-10-10 09:17 UTC167
                "excuse-replayed",
dfa38cf4cd4batagen2026-10-10 09:17 UTC168
                "Mom has heard that one before. She would have preferred the truth.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC169
            )));
dfa38cf4cd4batagen2026-10-10 09:17 UTC170
        }
dfa38cf4cd4batagen2026-10-10 09:17 UTC171
        if self.excuses().contains(&excuse.as_str()) {
dfa38cf4cd4batagen2026-10-10 09:17 UTC172
            self.heard.push(excuse);
dfa38cf4cd4batagen2026-10-10 09:17 UTC173
            self.leave_door();
dfa38cf4cd4batagen2026-10-10 09:17 UTC174
            return Ok(());
dfa38cf4cd4batagen2026-10-10 09:17 UTC175
        }
dfa38cf4cd4batagen2026-10-10 09:17 UTC176
        self.shame += 3;
dfa38cf4cd4batagen2026-10-10 09:17 UTC177
        self.touch();
dfa38cf4cd4batagen2026-10-10 09:17 UTC178
        let detail = if digest {
dfa38cf4cd4batagen2026-10-10 09:17 UTC179
            "Mom has not heard that one before. She does not believe it. She would have preferred the truth."
dfa38cf4cd4batagen2026-10-10 09:17 UTC180
        } else {
dfa38cf4cd4batagen2026-10-10 09:17 UTC181
            "Mom has not heard that one before. She does not believe it."
dfa38cf4cd4batagen2026-10-10 09:17 UTC182
        };
dfa38cf4cd4batagen2026-10-10 09:17 UTC183
        Err(challenge(Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC184
            StatusCode::UNAUTHORIZED,
dfa38cf4cd4batagen2026-10-10 09:17 UTC185
            "excuse-rejected",
dfa38cf4cd4batagen2026-10-10 09:17 UTC186
            detail,
dfa38cf4cd4batagen2026-10-10 09:17 UTC187
        )))
dfa38cf4cd4batagen2026-10-10 09:17 UTC188
    }
dfa38cf4cd4batagen2026-10-10 09:17 UTC189
dfa38cf4cd4batagen2026-10-10 09:17 UTC190
    pub(super) fn errand(&mut self, summoned: bool) -> &'static str {
dfa38cf4cd4batagen2026-10-10 09:17 UTC191
        if summoned {
dfa38cf4cd4batagen2026-10-10 09:17 UTC192
            return "She heard a noise.";
dfa38cf4cd4batagen2026-10-10 09:17 UTC193
        }
dfa38cf4cd4batagen2026-10-10 09:17 UTC194
        let errands: &[&'static str] = if self.floor == Floor::Grandmas {
dfa38cf4cd4batagen2026-10-10 09:17 UTC195
            &[
dfa38cf4cd4batagen2026-10-10 09:17 UTC196
                "Grandma would like to see you.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC197
                "Dinner is in ten minutes.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC198
                "She needs help with the folding table.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC199
                "An uncle has asked where you went.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC200
            ]
dfa38cf4cd4batagen2026-10-10 09:17 UTC201
        } else {
dfa38cf4cd4batagen2026-10-10 09:17 UTC202
            &[
dfa38cf4cd4batagen2026-10-10 09:17 UTC203
                "She has brought laundry.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC204
                "She wants to know if you have eaten.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC205
                "She needs the wifi password.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC206
                "She would like to talk about your future.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC207
            ]
dfa38cf4cd4batagen2026-10-10 09:17 UTC208
        };
dfa38cf4cd4batagen2026-10-10 09:17 UTC209
        errands[self.rng.random_range(0..errands.len())]
dfa38cf4cd4batagen2026-10-10 09:17 UTC210
    }
dfa38cf4cd4batagen2026-10-10 09:17 UTC211
dfa38cf4cd4batagen2026-10-10 09:17 UTC212
    fn leave_door(&mut self) {
dfa38cf4cd4batagen2026-10-10 09:17 UTC213
        self.mom_present = false;
dfa38cf4cd4batagen2026-10-10 09:17 UTC214
        self.suspicion = 0;
dfa38cf4cd4batagen2026-10-10 09:17 UTC215
        self.touch();
dfa38cf4cd4batagen2026-10-10 09:17 UTC216
    }
dfa38cf4cd4batagen2026-10-10 09:17 UTC217
dfa38cf4cd4batagen2026-10-10 09:17 UTC218
    pub(super) fn floor_enemy(&mut self) -> Result<(), Problem> {
dfa38cf4cd4batagen2026-10-10 09:17 UTC219
        let wanders_in = matches!(self.floor, Floor::Bathroom | Floor::Hotel)
dfa38cf4cd4batagen2026-10-10 09:17 UTC220
            && self.rng.random_ratio(u32::from(self.suspicion) + 1, 32);
dfa38cf4cd4batagen2026-10-10 09:17 UTC221
        if self.suspicion < SUSPICION_LIMIT && !wanders_in {
dfa38cf4cd4batagen2026-10-10 09:17 UTC222
            return Ok(());
dfa38cf4cd4batagen2026-10-10 09:17 UTC223
        }
dfa38cf4cd4batagen2026-10-10 09:17 UTC224
        match self.floor {
dfa38cf4cd4batagen2026-10-10 09:17 UTC225
            Floor::Bathroom => {
dfa38cf4cd4batagen2026-10-10 09:17 UTC226
                self.suspicion = 0;
dfa38cf4cd4batagen2026-10-10 09:17 UTC227
                self.touch();
dfa38cf4cd4batagen2026-10-10 09:17 UTC228
                if self.items.door_locks > 0 {
dfa38cf4cd4batagen2026-10-10 09:17 UTC229
                    self.items.door_locks -= 1;
dfa38cf4cd4batagen2026-10-10 09:17 UTC230
                    return Ok(());
dfa38cf4cd4batagen2026-10-10 09:17 UTC231
                }
dfa38cf4cd4batagen2026-10-10 09:17 UTC232
                self.roommate_told = true;
dfa38cf4cd4batagen2026-10-10 09:17 UTC233
                Err(Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC234
                    StatusCode::CONFLICT,
dfa38cf4cd4batagen2026-10-10 09:17 UTC235
                    "roommate",
dfa38cf4cd4batagen2026-10-10 09:17 UTC236
                    "The roommate needs the bathroom. He will mention this to people.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC237
                ))
dfa38cf4cd4batagen2026-10-10 09:17 UTC238
            }
dfa38cf4cd4batagen2026-10-10 09:17 UTC239
            Floor::LockerRoom => {
dfa38cf4cd4batagen2026-10-10 09:17 UTC240
                self.suspicion = 0;
dfa38cf4cd4batagen2026-10-10 09:17 UTC241
                self.shame += 5;
dfa38cf4cd4batagen2026-10-10 09:17 UTC242
                self.touch();
dfa38cf4cd4batagen2026-10-10 09:17 UTC243
                Err(Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC244
                    StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS,
dfa38cf4cd4batagen2026-10-10 09:17 UTC245
                    "gym-staff",
dfa38cf4cd4batagen2026-10-10 09:17 UTC246
                    "Gym staff have been notified. The server cannot serve this request for legal reasons.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC247
                ))
dfa38cf4cd4batagen2026-10-10 09:17 UTC248
            }
dfa38cf4cd4batagen2026-10-10 09:17 UTC249
            Floor::Hotel => {
dfa38cf4cd4batagen2026-10-10 09:17 UTC250
                self.suspicion = 0;
dfa38cf4cd4batagen2026-10-10 09:17 UTC251
                self.touch();
dfa38cf4cd4batagen2026-10-10 09:17 UTC252
                if self.items.dnd_sign {
dfa38cf4cd4batagen2026-10-10 09:17 UTC253
                    return Ok(());
dfa38cf4cd4batagen2026-10-10 09:17 UTC254
                }
dfa38cf4cd4batagen2026-10-10 09:17 UTC255
                self.shame += 3;
dfa38cf4cd4batagen2026-10-10 09:17 UTC256
                Err(Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC257
                    StatusCode::UNAUTHORIZED,
dfa38cf4cd4batagen2026-10-10 09:17 UTC258
                    "housekeeping",
dfa38cf4cd4batagen2026-10-10 09:17 UTC259
                    "Housekeeping has entered. Housekeeping accepts no excuses.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC260
                )
dfa38cf4cd4batagen2026-10-10 09:17 UTC261
                .header(header::WWW_AUTHENTICATE, "Excuse realm=\"housekeeping\""))
dfa38cf4cd4batagen2026-10-10 09:17 UTC262
            }
dfa38cf4cd4batagen2026-10-10 09:17 UTC263
            _ => Ok(()),
dfa38cf4cd4batagen2026-10-10 09:17 UTC264
        }
dfa38cf4cd4batagen2026-10-10 09:17 UTC265
    }
dfa38cf4cd4batagen2026-10-10 09:17 UTC266
dfa38cf4cd4batagen2026-10-10 09:17 UTC267
    pub fn search_drawer(&mut self) -> Result<Outcome, Problem> {
dfa38cf4cd4batagen2026-10-10 09:17 UTC268
        self.drawer_searches += 1;
dfa38cf4cd4batagen2026-10-10 09:17 UTC269
        self.touch();
dfa38cf4cd4batagen2026-10-10 09:17 UTC270
        let first = self.drawer_searches == 1;
dfa38cf4cd4batagen2026-10-10 09:17 UTC271
        let id = self.id.clone();
dfa38cf4cd4batagen2026-10-10 09:17 UTC272
        let found = |slug: &str, detail: &str| {
dfa38cf4cd4batagen2026-10-10 09:17 UTC273
            Outcome::json(StatusCode::FOUND, json!({ "detail": detail }))
dfa38cf4cd4batagen2026-10-10 09:17 UTC274
                .header(header::LOCATION, format!("/runs/{id}/inventory/{slug}"))
dfa38cf4cd4batagen2026-10-10 09:17 UTC275
        };
dfa38cf4cd4batagen2026-10-10 09:17 UTC276
        match self.floor {
dfa38cf4cd4batagen2026-10-10 09:17 UTC277
            Floor::Bedroom if first && !self.items.ruler => {
dfa38cf4cd4batagen2026-10-10 09:17 UTC278
                self.items.ruler = true;
dfa38cf4cd4batagen2026-10-10 09:17 UTC279
                self.shame += 10;
dfa38cf4cd4batagen2026-10-10 09:17 UTC280
                Ok(found(
dfa38cf4cd4batagen2026-10-10 09:17 UTC281
                    "ruler",
dfa38cf4cd4batagen2026-10-10 09:17 UTC282
                    "You now know. This is the first honest measurement of the run.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC283
                ))
dfa38cf4cd4batagen2026-10-10 09:17 UTC284
            }
dfa38cf4cd4batagen2026-10-10 09:17 UTC285
            Floor::Bathroom if first => {
dfa38cf4cd4batagen2026-10-10 09:17 UTC286
                self.items.door_locks += 1;
dfa38cf4cd4batagen2026-10-10 09:17 UTC287
                Ok(found("door-lock", "A door lock. It works once."))
dfa38cf4cd4batagen2026-10-10 09:17 UTC288
            }
dfa38cf4cd4batagen2026-10-10 09:17 UTC289
            Floor::LockerRoom => {
dfa38cf4cd4batagen2026-10-10 09:17 UTC290
                self.shame += 3;
dfa38cf4cd4batagen2026-10-10 09:17 UTC291
                Err(Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC292
                    StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS,
dfa38cf4cd4batagen2026-10-10 09:17 UTC293
                    "not-your-locker",
dfa38cf4cd4batagen2026-10-10 09:17 UTC294
                    "That locker belongs to Greg. The server cannot serve this request for legal reasons.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC295
                ))
dfa38cf4cd4batagen2026-10-10 09:17 UTC296
            }
dfa38cf4cd4batagen2026-10-10 09:17 UTC297
            Floor::Grandmas if first => {
dfa38cf4cd4batagen2026-10-10 09:17 UTC298
                self.items.cage = true;
dfa38cf4cd4batagen2026-10-10 09:17 UTC299
                self.shame += 5;
dfa38cf4cd4batagen2026-10-10 09:17 UTC300
                Ok(found(
dfa38cf4cd4batagen2026-10-10 09:17 UTC301
                    "chastity-cage",
dfa38cf4cd4batagen2026-10-10 09:17 UTC302
                    "A small box. Inside was a cage. It is now on. The key is not in the box.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC303
                ))
dfa38cf4cd4batagen2026-10-10 09:17 UTC304
            }
dfa38cf4cd4batagen2026-10-10 09:17 UTC305
            Floor::Grandmas if self.items.cage => {
dfa38cf4cd4batagen2026-10-10 09:17 UTC306
                self.items.cage = false;
dfa38cf4cd4batagen2026-10-10 09:17 UTC307
                Ok(Outcome::json(
dfa38cf4cd4batagen2026-10-10 09:17 UTC308
                    StatusCode::OK,
dfa38cf4cd4batagen2026-10-10 09:17 UTC309
                    json!({ "detail": "The key was under where the box had been. The cage is off." }),
dfa38cf4cd4batagen2026-10-10 09:17 UTC310
                ))
dfa38cf4cd4batagen2026-10-10 09:17 UTC311
            }
dfa38cf4cd4batagen2026-10-10 09:17 UTC312
            Floor::Hotel if first => {
dfa38cf4cd4batagen2026-10-10 09:17 UTC313
                self.items.dnd_sign = true;
dfa38cf4cd4batagen2026-10-10 09:17 UTC314
                self.items.ice_packs += 2;
dfa38cf4cd4batagen2026-10-10 09:17 UTC315
                Ok(found(
dfa38cf4cd4batagen2026-10-10 09:17 UTC316
                    "do-not-disturb-sign",
dfa38cf4cd4batagen2026-10-10 09:17 UTC317
                    "A Do Not Disturb sign, and two ice packs from the machine down the hall.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC318
                ))
dfa38cf4cd4batagen2026-10-10 09:17 UTC319
            }
dfa38cf4cd4batagen2026-10-10 09:17 UTC320
            Floor::Forum if first => {
dfa38cf4cd4batagen2026-10-10 09:17 UTC321
                self.items.spreadsheet = true;
dfa38cf4cd4batagen2026-10-10 09:17 UTC322
                Ok(found(
dfa38cf4cd4batagen2026-10-10 09:17 UTC323
                    "spreadsheet",
dfa38cf4cd4batagen2026-10-10 09:17 UTC324
                    "Your own spreadsheet. Every line is flat.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC325
                ))
dfa38cf4cd4batagen2026-10-10 09:17 UTC326
            }
dfa38cf4cd4batagen2026-10-10 09:17 UTC327
            Floor::Clinic => Err(Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC328
                StatusCode::FORBIDDEN,
dfa38cf4cd4batagen2026-10-10 09:17 UTC329
                "doctors-drawer",
dfa38cf4cd4batagen2026-10-10 09:17 UTC330
                "That is the doctor's drawer.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC331
            )),
dfa38cf4cd4batagen2026-10-10 09:17 UTC332
            _ => Ok(Outcome::empty(StatusCode::NO_CONTENT)),
dfa38cf4cd4batagen2026-10-10 09:17 UTC333
        }
dfa38cf4cd4batagen2026-10-10 09:17 UTC334
    }
dfa38cf4cd4batagen2026-10-10 09:17 UTC335
dfa38cf4cd4batagen2026-10-10 09:17 UTC336
    pub fn shop_view(&self) -> Result<Value, Problem> {
dfa38cf4cd4batagen2026-10-10 09:17 UTC337
        if !self.floor.shop_open() {
dfa38cf4cd4batagen2026-10-10 09:17 UTC338
            return Err(pump_bro_absent());
dfa38cf4cd4batagen2026-10-10 09:17 UTC339
        }
dfa38cf4cd4batagen2026-10-10 09:17 UTC340
        let stock: Vec<Value> = SHOP
dfa38cf4cd4batagen2026-10-10 09:17 UTC341
            .iter()
dfa38cf4cd4batagen2026-10-10 09:17 UTC342
            .map(|item| {
dfa38cf4cd4batagen2026-10-10 09:17 UTC343
                json!({
dfa38cf4cd4batagen2026-10-10 09:17 UTC344
                    "item": item.slug,
dfa38cf4cd4batagen2026-10-10 09:17 UTC345
                    "price": item.price,
dfa38cf4cd4batagen2026-10-10 09:17 UTC346
                    "description": item.description,
dfa38cf4cd4batagen2026-10-10 09:17 UTC347
                    "href": format!("/runs/{}/shop/{}", self.id, item.slug),
dfa38cf4cd4batagen2026-10-10 09:17 UTC348
                    "method": "POST",
dfa38cf4cd4batagen2026-10-10 09:17 UTC349
                })
dfa38cf4cd4batagen2026-10-10 09:17 UTC350
            })
dfa38cf4cd4batagen2026-10-10 09:17 UTC351
            .collect();
dfa38cf4cd4batagen2026-10-10 09:17 UTC352
        Ok(json!({ "merchant": "Pump-Bro", "cash": self.cash, "stock": stock }))
dfa38cf4cd4batagen2026-10-10 09:17 UTC353
    }
dfa38cf4cd4batagen2026-10-10 09:17 UTC354
dfa38cf4cd4batagen2026-10-10 09:17 UTC355
    pub fn buy(&mut self, slug: &str) -> Result<Outcome, Problem> {
dfa38cf4cd4batagen2026-10-10 09:17 UTC356
        if !self.floor.shop_open() {
dfa38cf4cd4batagen2026-10-10 09:17 UTC357
            return Err(pump_bro_absent());
dfa38cf4cd4batagen2026-10-10 09:17 UTC358
        }
dfa38cf4cd4batagen2026-10-10 09:17 UTC359
        let Some(item) = SHOP.iter().find(|i| i.slug == slug) else {
dfa38cf4cd4batagen2026-10-10 09:17 UTC360
            return Err(Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC361
                StatusCode::NOT_FOUND,
dfa38cf4cd4batagen2026-10-10 09:17 UTC362
                "not-for-sale",
dfa38cf4cd4batagen2026-10-10 09:17 UTC363
                "Pump-Bro does not sell that. He knows a guy.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC364
            ));
dfa38cf4cd4batagen2026-10-10 09:17 UTC365
        };
dfa38cf4cd4batagen2026-10-10 09:17 UTC366
        if slug == "pump" {
dfa38cf4cd4batagen2026-10-10 09:17 UTC367
            return Err(Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC368
                StatusCode::UPGRADE_REQUIRED,
dfa38cf4cd4batagen2026-10-10 09:17 UTC369
                "upsell",
dfa38cf4cd4batagen2026-10-10 09:17 UTC370
                "This pump requires an upgrade.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC371
            )
dfa38cf4cd4batagen2026-10-10 09:17 UTC372
            .header(header::UPGRADE, "pump-pro/2.0")
dfa38cf4cd4batagen2026-10-10 09:17 UTC373
            .with("upgrade", format!("/runs/{}/shop/pump-pro", self.id)));
dfa38cf4cd4batagen2026-10-10 09:17 UTC374
        }
dfa38cf4cd4batagen2026-10-10 09:17 UTC375
        if self.cash < item.price {
dfa38cf4cd4batagen2026-10-10 09:17 UTC376
            return Err(Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC377
                StatusCode::PAYMENT_REQUIRED,
dfa38cf4cd4batagen2026-10-10 09:17 UTC378
                "insufficient-funds",
dfa38cf4cd4batagen2026-10-10 09:17 UTC379
                "Payment is required. RFC 9110 has reserved this status for future use since 1997. The future is here and you cannot afford it.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC380
            )
dfa38cf4cd4batagen2026-10-10 09:17 UTC381
            .with("cash", self.cash)
dfa38cf4cd4batagen2026-10-10 09:17 UTC382
            .with("price", item.price));
dfa38cf4cd4batagen2026-10-10 09:17 UTC383
        }
dfa38cf4cd4batagen2026-10-10 09:17 UTC384
        self.cash -= item.price;
dfa38cf4cd4batagen2026-10-10 09:17 UTC385
        self.touch();
dfa38cf4cd4batagen2026-10-10 09:17 UTC386
        let items = &mut self.items;
dfa38cf4cd4batagen2026-10-10 09:17 UTC387
        match slug {
dfa38cf4cd4batagen2026-10-10 09:17 UTC388
            "supplement" => {
dfa38cf4cd4batagen2026-10-10 09:17 UTC389
                items.supplements += 1;
dfa38cf4cd4batagen2026-10-10 09:17 UTC390
                return Err(Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC391
                    StatusCode::GATEWAY_TIMEOUT,
dfa38cf4cd4batagen2026-10-10 09:17 UTC392
                    "still-shipping",
dfa38cf4cd4batagen2026-10-10 09:17 UTC393
                    "The supplement is still shipping. You have been charged. It may arrive.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC394
                )
dfa38cf4cd4batagen2026-10-10 09:17 UTC395
                .with("cash", self.cash));
dfa38cf4cd4batagen2026-10-10 09:17 UTC396
            }
dfa38cf4cd4batagen2026-10-10 09:17 UTC397
            "pump-pro" => items.pump_pro = true,
dfa38cf4cd4batagen2026-10-10 09:17 UTC398
            "extender" => items.extender = true,
dfa38cf4cd4batagen2026-10-10 09:17 UTC399
            "cock-ring" => items.ring = true,
dfa38cf4cd4batagen2026-10-10 09:17 UTC400
            "door-lock" => items.door_locks += 1,
dfa38cf4cd4batagen2026-10-10 09:17 UTC401
            "do-not-disturb-sign" => items.dnd_sign = true,
dfa38cf4cd4batagen2026-10-10 09:17 UTC402
            "ruler" => items.ruler = true,
dfa38cf4cd4batagen2026-10-10 09:17 UTC403
            "ice-pack" => items.ice_packs += 1,
dfa38cf4cd4batagen2026-10-10 09:17 UTC404
            _ => items.cage = false,
dfa38cf4cd4batagen2026-10-10 09:17 UTC405
        }
dfa38cf4cd4batagen2026-10-10 09:17 UTC406
        Ok(Outcome::json(
dfa38cf4cd4batagen2026-10-10 09:17 UTC407
            StatusCode::CREATED,
dfa38cf4cd4batagen2026-10-10 09:17 UTC408
            json!({ "detail": "Purchased. No refunds.", "cash": self.cash }),
dfa38cf4cd4batagen2026-10-10 09:17 UTC409
        )
dfa38cf4cd4batagen2026-10-10 09:17 UTC410
        .header(
dfa38cf4cd4batagen2026-10-10 09:17 UTC411
            header::LOCATION,
dfa38cf4cd4batagen2026-10-10 09:17 UTC412
            format!("/runs/{}/inventory/{slug}", self.id),
dfa38cf4cd4batagen2026-10-10 09:17 UTC413
        ))
dfa38cf4cd4batagen2026-10-10 09:17 UTC414
    }
dfa38cf4cd4batagen2026-10-10 09:17 UTC415
dfa38cf4cd4batagen2026-10-10 09:17 UTC416
    pub fn drop_item(&mut self, slug: &str) -> Result<Outcome, Problem> {
dfa38cf4cd4batagen2026-10-10 09:17 UTC417
        match slug {
dfa38cf4cd4batagen2026-10-10 09:17 UTC418
            "extender" => self.items.extender = false,
dfa38cf4cd4batagen2026-10-10 09:17 UTC419
            "cock-ring" => self.items.ring = false,
dfa38cf4cd4batagen2026-10-10 09:17 UTC420
            "chastity-cage" => {
dfa38cf4cd4batagen2026-10-10 09:17 UTC421
                return Err(Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC422
                    StatusCode::LOCKED,
dfa38cf4cd4batagen2026-10-10 09:17 UTC423
                    "caged",
dfa38cf4cd4batagen2026-10-10 09:17 UTC424
                    "The cage is locked.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC425
                ));
dfa38cf4cd4batagen2026-10-10 09:17 UTC426
            }
dfa38cf4cd4batagen2026-10-10 09:17 UTC427
            _ => {
dfa38cf4cd4batagen2026-10-10 09:17 UTC428
                return Err(Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC429
                    StatusCode::FORBIDDEN,
dfa38cf4cd4batagen2026-10-10 09:17 UTC430
                    "cannot-be-dropped",
dfa38cf4cd4batagen2026-10-10 09:17 UTC431
                    "That cannot be dropped.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC432
                ));
dfa38cf4cd4batagen2026-10-10 09:17 UTC433
            }
dfa38cf4cd4batagen2026-10-10 09:17 UTC434
        }
dfa38cf4cd4batagen2026-10-10 09:17 UTC435
        self.touch();
dfa38cf4cd4batagen2026-10-10 09:17 UTC436
        Ok(Outcome::empty(StatusCode::NO_CONTENT))
dfa38cf4cd4batagen2026-10-10 09:17 UTC437
    }
dfa38cf4cd4batagen2026-10-10 09:17 UTC438
dfa38cf4cd4batagen2026-10-10 09:17 UTC439
    pub(super) fn branch_offer(&self) -> Outcome {
dfa38cf4cd4batagen2026-10-10 09:17 UTC440
        let forum = format!("/runs/{}/branches/forum", self.id);
dfa38cf4cd4batagen2026-10-10 09:17 UTC441
        let close = format!("/runs/{}/branches/close-tab", self.id);
dfa38cf4cd4batagen2026-10-10 09:17 UTC442
        Outcome::json(
dfa38cf4cd4batagen2026-10-10 09:17 UTC443
            StatusCode::MULTIPLE_CHOICES,
dfa38cf4cd4batagen2026-10-10 09:17 UTC444
            json!({
dfa38cf4cd4batagen2026-10-10 09:17 UTC445
                "detail": "A browser tab is open. It has been open for some time.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC446
                "choices": [
dfa38cf4cd4batagen2026-10-10 09:17 UTC447
                    { "href": forum, "method": "POST", "title": "Read the forum" },
dfa38cf4cd4batagen2026-10-10 09:17 UTC448
                    { "href": close, "method": "POST", "title": "Close the tab" },
dfa38cf4cd4batagen2026-10-10 09:17 UTC449
                ],
dfa38cf4cd4batagen2026-10-10 09:17 UTC450
            }),
dfa38cf4cd4batagen2026-10-10 09:17 UTC451
        )
dfa38cf4cd4batagen2026-10-10 09:17 UTC452
        .header(
dfa38cf4cd4batagen2026-10-10 09:17 UTC453
            header::LINK,
dfa38cf4cd4batagen2026-10-10 09:17 UTC454
            format!(
dfa38cf4cd4batagen2026-10-10 09:17 UTC455
                "<{forum}>; rel=\"alternate\"; title=\"Read the forum\", <{close}>; rel=\"alternate\"; title=\"Close the tab\""
dfa38cf4cd4batagen2026-10-10 09:17 UTC456
            ),
dfa38cf4cd4batagen2026-10-10 09:17 UTC457
        )
dfa38cf4cd4batagen2026-10-10 09:17 UTC458
    }
dfa38cf4cd4batagen2026-10-10 09:17 UTC459
dfa38cf4cd4batagen2026-10-10 09:17 UTC460
    pub fn choose(&mut self, choice: &str) -> Result<Outcome, Problem> {
dfa38cf4cd4batagen2026-10-10 09:17 UTC461
        if !self.branch_pending {
dfa38cf4cd4batagen2026-10-10 09:17 UTC462
            return Err(Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC463
                StatusCode::CONFLICT,
dfa38cf4cd4batagen2026-10-10 09:17 UTC464
                "no-decision-pending",
dfa38cf4cd4batagen2026-10-10 09:17 UTC465
                "There is no decision pending.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC466
            ));
dfa38cf4cd4batagen2026-10-10 09:17 UTC467
        }
dfa38cf4cd4batagen2026-10-10 09:17 UTC468
        let (location, detail) = match choice {
dfa38cf4cd4batagen2026-10-10 09:17 UTC469
            "forum" => {
dfa38cf4cd4batagen2026-10-10 09:17 UTC470
                self.forum_read = true;
dfa38cf4cd4batagen2026-10-10 09:17 UTC471
                self.shame += 3;
dfa38cf4cd4batagen2026-10-10 09:17 UTC472
                (
dfa38cf4cd4batagen2026-10-10 09:17 UTC473
                    format!("/runs/{}/techniques", self.id),
dfa38cf4cd4batagen2026-10-10 09:17 UTC474
                    "The thread is from 2009. A technique called uli is discussed at length.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC475
                )
dfa38cf4cd4batagen2026-10-10 09:17 UTC476
            }
dfa38cf4cd4batagen2026-10-10 09:17 UTC477
            "close-tab" => {
dfa38cf4cd4batagen2026-10-10 09:17 UTC478
                self.suspicion = 0;
dfa38cf4cd4batagen2026-10-10 09:17 UTC479
                (
dfa38cf4cd4batagen2026-10-10 09:17 UTC480
                    format!("/runs/{}", self.id),
dfa38cf4cd4batagen2026-10-10 09:17 UTC481
                    "The tab is closed. Nobody saw it.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC482
                )
dfa38cf4cd4batagen2026-10-10 09:17 UTC483
            }
dfa38cf4cd4batagen2026-10-10 09:17 UTC484
            _ => {
dfa38cf4cd4batagen2026-10-10 09:17 UTC485
                return Err(Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC486
                    StatusCode::NOT_FOUND,
dfa38cf4cd4batagen2026-10-10 09:17 UTC487
                    "no-such-choice",
dfa38cf4cd4batagen2026-10-10 09:17 UTC488
                    "That was not one of the choices.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC489
                ));
dfa38cf4cd4batagen2026-10-10 09:17 UTC490
            }
dfa38cf4cd4batagen2026-10-10 09:17 UTC491
        };
dfa38cf4cd4batagen2026-10-10 09:17 UTC492
        self.branch_pending = false;
dfa38cf4cd4batagen2026-10-10 09:17 UTC493
        self.touch();
dfa38cf4cd4batagen2026-10-10 09:17 UTC494
        Ok(
dfa38cf4cd4batagen2026-10-10 09:17 UTC495
            Outcome::json(StatusCode::SEE_OTHER, json!({ "detail": detail }))
dfa38cf4cd4batagen2026-10-10 09:17 UTC496
                .header(header::LOCATION, location),
dfa38cf4cd4batagen2026-10-10 09:17 UTC497
        )
dfa38cf4cd4batagen2026-10-10 09:17 UTC498
    }
dfa38cf4cd4batagen2026-10-10 09:17 UTC499
dfa38cf4cd4batagen2026-10-10 09:17 UTC500
    pub fn put(&mut self) -> Outcome {
dfa38cf4cd4batagen2026-10-10 09:17 UTC501
        if self.put_applied {
dfa38cf4cd4batagen2026-10-10 09:17 UTC502
            return Outcome::empty(StatusCode::NO_CONTENT);
dfa38cf4cd4batagen2026-10-10 09:17 UTC503
        }
dfa38cf4cd4batagen2026-10-10 09:17 UTC504
        self.put_applied = true;
dfa38cf4cd4batagen2026-10-10 09:17 UTC505
        self.length += 0.01;
dfa38cf4cd4batagen2026-10-10 09:17 UTC506
        self.touch();
dfa38cf4cd4batagen2026-10-10 09:17 UTC507
        Outcome::json(StatusCode::OK, json!({ "detail": "Applied." }))
dfa38cf4cd4batagen2026-10-10 09:17 UTC508
    }
dfa38cf4cd4batagen2026-10-10 09:17 UTC509
dfa38cf4cd4batagen2026-10-10 09:17 UTC510
    pub fn patch(&mut self, ops: &[PatchOp]) -> Result<Outcome, Problem> {
dfa38cf4cd4batagen2026-10-10 09:17 UTC511
        for op in ops {
dfa38cf4cd4batagen2026-10-10 09:17 UTC512
            if op.op != "test" {
dfa38cf4cd4batagen2026-10-10 09:17 UTC513
                return Err(Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC514
                    StatusCode::UNPROCESSABLE_ENTITY,
dfa38cf4cd4batagen2026-10-10 09:17 UTC515
                    "wishful-thinking",
dfa38cf4cd4batagen2026-10-10 09:17 UTC516
                    "The server accepts only the truth.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC517
                )
dfa38cf4cd4batagen2026-10-10 09:17 UTC518
                .with("op", op.op.clone()));
dfa38cf4cd4batagen2026-10-10 09:17 UTC519
            }
dfa38cf4cd4batagen2026-10-10 09:17 UTC520
            let actual = match op.path.as_str() {
dfa38cf4cd4batagen2026-10-10 09:17 UTC521
                "/length" => self.current_length(),
dfa38cf4cd4batagen2026-10-10 09:17 UTC522
                "/girth" => self.girth,
dfa38cf4cd4batagen2026-10-10 09:17 UTC523
                "/foreskin" => self.foreskin.unwrap_or(0.0),
dfa38cf4cd4batagen2026-10-10 09:17 UTC524
                _ => {
dfa38cf4cd4batagen2026-10-10 09:17 UTC525
                    return Err(Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC526
                        StatusCode::UNPROCESSABLE_ENTITY,
dfa38cf4cd4batagen2026-10-10 09:17 UTC527
                        "no-such-measurement",
dfa38cf4cd4batagen2026-10-10 09:17 UTC528
                        "There is no such measurement.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC529
                    )
dfa38cf4cd4batagen2026-10-10 09:17 UTC530
                    .with("path", op.path.clone()));
dfa38cf4cd4batagen2026-10-10 09:17 UTC531
                }
dfa38cf4cd4batagen2026-10-10 09:17 UTC532
            };
dfa38cf4cd4batagen2026-10-10 09:17 UTC533
            let Some(claimed) = op.value.as_f64() else {
dfa38cf4cd4batagen2026-10-10 09:17 UTC534
                return Err(Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC535
                    StatusCode::BAD_REQUEST,
dfa38cf4cd4batagen2026-10-10 09:17 UTC536
                    "malformed-patch",
dfa38cf4cd4batagen2026-10-10 09:17 UTC537
                    "A test requires a numeric value.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC538
                ));
dfa38cf4cd4batagen2026-10-10 09:17 UTC539
            };
dfa38cf4cd4batagen2026-10-10 09:17 UTC540
            if op.path == "/length" && claimed >= actual + 1.0 {
dfa38cf4cd4batagen2026-10-10 09:17 UTC541
                self.shame += 2;
dfa38cf4cd4batagen2026-10-10 09:17 UTC542
                self.touch();
dfa38cf4cd4batagen2026-10-10 09:17 UTC543
                return Err(exaggeration());
dfa38cf4cd4batagen2026-10-10 09:17 UTC544
            }
177535ac8abfatagen2026-10-10 10:39 UTC545
            if !honest(claimed, actual) {
dfa38cf4cd4batagen2026-10-10 09:17 UTC546
                self.shame += 1;
dfa38cf4cd4batagen2026-10-10 09:17 UTC547
                self.touch();
dfa38cf4cd4batagen2026-10-10 09:17 UTC548
                return Err(
dfa38cf4cd4batagen2026-10-10 09:17 UTC549
                    Problem::new(StatusCode::CONFLICT, "test-failed", "The test failed.")
dfa38cf4cd4batagen2026-10-10 09:17 UTC550
                        .with("path", op.path.clone()),
dfa38cf4cd4batagen2026-10-10 09:17 UTC551
                );
dfa38cf4cd4batagen2026-10-10 09:17 UTC552
            }
dfa38cf4cd4batagen2026-10-10 09:17 UTC553
        }
dfa38cf4cd4batagen2026-10-10 09:17 UTC554
        Ok(Outcome::json(
dfa38cf4cd4batagen2026-10-10 09:17 UTC555
            StatusCode::OK,
dfa38cf4cd4batagen2026-10-10 09:17 UTC556
            json!({ "detail": "Correct." }),
dfa38cf4cd4batagen2026-10-10 09:17 UTC557
        ))
dfa38cf4cd4batagen2026-10-10 09:17 UTC558
    }
dfa38cf4cd4batagen2026-10-10 09:17 UTC559
dfa38cf4cd4batagen2026-10-10 09:17 UTC560
    pub fn delete_foreskin(&mut self, if_match: Option<&str>) -> Result<Outcome, Problem> {
dfa38cf4cd4batagen2026-10-10 09:17 UTC561
        if self.foreskin.is_none() {
dfa38cf4cd4batagen2026-10-10 09:17 UTC562
            return Err(Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC563
                StatusCode::GONE,
dfa38cf4cd4batagen2026-10-10 09:17 UTC564
                "foreskin-gone",
dfa38cf4cd4batagen2026-10-10 09:17 UTC565
                "The foreskin is gone. It will not be returning.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC566
            ));
dfa38cf4cd4batagen2026-10-10 09:17 UTC567
        }
dfa38cf4cd4batagen2026-10-10 09:17 UTC568
        let Some(if_match) = if_match else {
dfa38cf4cd4batagen2026-10-10 09:17 UTC569
            return Err(Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC570
                StatusCode::PRECONDITION_REQUIRED,
dfa38cf4cd4batagen2026-10-10 09:17 UTC571
                "confirmation-required",
dfa38cf4cd4batagen2026-10-10 09:17 UTC572
                "The server requires you to be sure. Send If-Match.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC573
            ));
dfa38cf4cd4batagen2026-10-10 09:17 UTC574
        };
dfa38cf4cd4batagen2026-10-10 09:17 UTC575
        if !super::etag_matches(if_match, &self.etag()) {
dfa38cf4cd4batagen2026-10-10 09:17 UTC576
            return Err(Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC577
                StatusCode::PRECONDITION_FAILED,
dfa38cf4cd4batagen2026-10-10 09:17 UTC578
                "stale-foreskin",
dfa38cf4cd4batagen2026-10-10 09:17 UTC579
                "The foreskin has changed since you last looked at it.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC580
            ));
dfa38cf4cd4batagen2026-10-10 09:17 UTC581
        }
dfa38cf4cd4batagen2026-10-10 09:17 UTC582
        self.foreskin = None;
dfa38cf4cd4batagen2026-10-10 09:17 UTC583
        self.shame += 20;
dfa38cf4cd4batagen2026-10-10 09:17 UTC584
        self.touch();
dfa38cf4cd4batagen2026-10-10 09:17 UTC585
        Ok(Outcome::empty(StatusCode::NO_CONTENT))
dfa38cf4cd4batagen2026-10-10 09:17 UTC586
    }
dfa38cf4cd4batagen2026-10-10 09:17 UTC587
dfa38cf4cd4batagen2026-10-10 09:17 UTC588
    pub fn set_grip(&mut self, grip: Grip) {
dfa38cf4cd4batagen2026-10-10 09:17 UTC589
        self.grip = grip;
dfa38cf4cd4batagen2026-10-10 09:17 UTC590
        self.touch();
dfa38cf4cd4batagen2026-10-10 09:17 UTC591
    }
dfa38cf4cd4batagen2026-10-10 09:17 UTC592
dfa38cf4cd4batagen2026-10-10 09:17 UTC593
    pub fn accept_terms(&mut self) -> Result<Outcome, Problem> {
dfa38cf4cd4batagen2026-10-10 09:17 UTC594
        if self.floor != Floor::Hotel {
dfa38cf4cd4batagen2026-10-10 09:17 UTC595
            return Err(Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC596
                StatusCode::NOT_FOUND,
dfa38cf4cd4batagen2026-10-10 09:17 UTC597
                "no-portal",
dfa38cf4cd4batagen2026-10-10 09:17 UTC598
                "There is no captive portal here. There is only the regular kind of captivity.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC599
            ));
dfa38cf4cd4batagen2026-10-10 09:17 UTC600
        }
dfa38cf4cd4batagen2026-10-10 09:17 UTC601
        self.wifi_accepted = true;
dfa38cf4cd4batagen2026-10-10 09:17 UTC602
        self.touch();
dfa38cf4cd4batagen2026-10-10 09:17 UTC603
        Ok(Outcome::json(
dfa38cf4cd4batagen2026-10-10 09:17 UTC604
            StatusCode::SEE_OTHER,
dfa38cf4cd4batagen2026-10-10 09:17 UTC605
            json!({ "detail": "Connected. The hotel does not want to know." }),
dfa38cf4cd4batagen2026-10-10 09:17 UTC606
        )
dfa38cf4cd4batagen2026-10-10 09:17 UTC607
        .header(header::LOCATION, format!("/runs/{}", self.id)))
dfa38cf4cd4batagen2026-10-10 09:17 UTC608
    }
dfa38cf4cd4batagen2026-10-10 09:17 UTC609
dfa38cf4cd4batagen2026-10-10 09:17 UTC610
    pub fn elder(
dfa38cf4cd4batagen2026-10-10 09:17 UTC611
        &mut self,
dfa38cf4cd4batagen2026-10-10 09:17 UTC612
        proxy_authorization: Option<&str>,
dfa38cf4cd4batagen2026-10-10 09:17 UTC613
        if_modified_since: Option<&str>,
dfa38cf4cd4batagen2026-10-10 09:17 UTC614
    ) -> Result<Outcome, Problem> {
dfa38cf4cd4batagen2026-10-10 09:17 UTC615
        if self.floor != Floor::Forum {
dfa38cf4cd4batagen2026-10-10 09:17 UTC616
            return Err(Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC617
                StatusCode::NOT_FOUND,
dfa38cf4cd4batagen2026-10-10 09:17 UTC618
                "elder-absent",
dfa38cf4cd4batagen2026-10-10 09:17 UTC619
                "The Elder only posts on the forum.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC620
            ));
dfa38cf4cd4batagen2026-10-10 09:17 UTC621
        }
dfa38cf4cd4batagen2026-10-10 09:17 UTC622
        if !self.verified {
dfa38cf4cd4batagen2026-10-10 09:17 UTC623
            let verification = |detail: &str| {
dfa38cf4cd4batagen2026-10-10 09:17 UTC624
                Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC625
                    StatusCode::PROXY_AUTHENTICATION_REQUIRED,
dfa38cf4cd4batagen2026-10-10 09:17 UTC626
                    "verification-required",
dfa38cf4cd4batagen2026-10-10 09:17 UTC627
                    detail,
dfa38cf4cd4batagen2026-10-10 09:17 UTC628
                )
dfa38cf4cd4batagen2026-10-10 09:17 UTC629
                .header(header::PROXY_AUTHENTICATE, "Ruler realm=\"forum\"")
dfa38cf4cd4batagen2026-10-10 09:17 UTC630
            };
dfa38cf4cd4batagen2026-10-10 09:17 UTC631
            let Some(credentials) = proxy_authorization else {
dfa38cf4cd4batagen2026-10-10 09:17 UTC632
                return Err(verification("The forum requires verification."));
dfa38cf4cd4batagen2026-10-10 09:17 UTC633
            };
dfa38cf4cd4batagen2026-10-10 09:17 UTC634
            let Some(claimed) =
dfa38cf4cd4batagen2026-10-10 09:17 UTC635
                parse_credentials(credentials, "ruler").and_then(|v| v.parse::<f64>().ok())
dfa38cf4cd4batagen2026-10-10 09:17 UTC636
            else {
dfa38cf4cd4batagen2026-10-10 09:17 UTC637
                return Err(verification("The forum requires verification, in inches."));
dfa38cf4cd4batagen2026-10-10 09:17 UTC638
            };
dfa38cf4cd4batagen2026-10-10 09:17 UTC639
            let actual = self.current_length();
dfa38cf4cd4batagen2026-10-10 09:17 UTC640
            if claimed >= actual + 1.0 {
dfa38cf4cd4batagen2026-10-10 09:17 UTC641
                self.shame += 2;
dfa38cf4cd4batagen2026-10-10 09:17 UTC642
                self.touch();
dfa38cf4cd4batagen2026-10-10 09:17 UTC643
                return Err(exaggeration());
dfa38cf4cd4batagen2026-10-10 09:17 UTC644
            }
177535ac8abfatagen2026-10-10 10:39 UTC645
            if !honest(claimed, actual) {
dfa38cf4cd4batagen2026-10-10 09:17 UTC646
                self.shame += 1;
dfa38cf4cd4batagen2026-10-10 09:17 UTC647
                self.touch();
dfa38cf4cd4batagen2026-10-10 09:17 UTC648
                return Err(verification("Verification failed. The forum can tell."));
dfa38cf4cd4batagen2026-10-10 09:17 UTC649
            }
dfa38cf4cd4batagen2026-10-10 09:17 UTC650
            self.verified = true;
dfa38cf4cd4batagen2026-10-10 09:17 UTC651
            self.touch();
dfa38cf4cd4batagen2026-10-10 09:17 UTC652
        }
dfa38cf4cd4batagen2026-10-10 09:17 UTC653
dfa38cf4cd4batagen2026-10-10 09:17 UTC654
        let last_modified =
dfa38cf4cd4batagen2026-10-10 09:17 UTC655
            httpdate::parse_http_date(ELDER_LAST_MODIFIED).expect("valid constant date");
dfa38cf4cd4batagen2026-10-10 09:17 UTC656
        let current = if_modified_since
dfa38cf4cd4batagen2026-10-10 09:17 UTC657
            .and_then(|d| httpdate::parse_http_date(d).ok())
dfa38cf4cd4batagen2026-10-10 09:17 UTC658
            .is_some_and(|since| since >= last_modified);
dfa38cf4cd4batagen2026-10-10 09:17 UTC659
        if current {
dfa38cf4cd4batagen2026-10-10 09:17 UTC660
            self.elder_beaten = true;
dfa38cf4cd4batagen2026-10-10 09:17 UTC661
            self.touch();
dfa38cf4cd4batagen2026-10-10 09:17 UTC662
            return Ok(Outcome::empty(StatusCode::NOT_MODIFIED)
dfa38cf4cd4batagen2026-10-10 09:17 UTC663
                .header(header::LAST_MODIFIED, ELDER_LAST_MODIFIED));
dfa38cf4cd4batagen2026-10-10 09:17 UTC664
        }
dfa38cf4cd4batagen2026-10-10 09:17 UTC665
        Ok(
dfa38cf4cd4batagen2026-10-10 09:17 UTC666
            Outcome::raw(StatusCode::OK, "text/csv; header=present", SPREADSHEET)
dfa38cf4cd4batagen2026-10-10 09:17 UTC667
                .header(header::LAST_MODIFIED, ELDER_LAST_MODIFIED),
dfa38cf4cd4batagen2026-10-10 09:17 UTC668
        )
dfa38cf4cd4batagen2026-10-10 09:17 UTC669
    }
dfa38cf4cd4batagen2026-10-10 09:17 UTC670
dfa38cf4cd4batagen2026-10-10 09:17 UTC671
    pub fn diagnose(
dfa38cf4cd4batagen2026-10-10 09:17 UTC672
        &mut self,
dfa38cf4cd4batagen2026-10-10 09:17 UTC673
        authorization: Option<&str>,
dfa38cf4cd4batagen2026-10-10 09:17 UTC674
        if_none_match: Option<&str>,
dfa38cf4cd4batagen2026-10-10 09:17 UTC675
    ) -> Result<Outcome, Problem> {
dfa38cf4cd4batagen2026-10-10 09:17 UTC676
        if self.floor != Floor::Clinic {
dfa38cf4cd4batagen2026-10-10 09:17 UTC677
            let floors = Floor::Clinic.number() - self.floor.number();
dfa38cf4cd4batagen2026-10-10 09:17 UTC678
            return Err(Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC679
                StatusCode::TOO_EARLY,
dfa38cf4cd4batagen2026-10-10 09:17 UTC680
                "no-appointment",
dfa38cf4cd4batagen2026-10-10 09:17 UTC681
                format!("The clinic's next available appointment is {floors} floors away."),
dfa38cf4cd4batagen2026-10-10 09:17 UTC682
            ));
dfa38cf4cd4batagen2026-10-10 09:17 UTC683
        }
dfa38cf4cd4batagen2026-10-10 09:17 UTC684
        let intake = Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC685
            StatusCode::UNAUTHORIZED,
dfa38cf4cd4batagen2026-10-10 09:17 UTC686
            "intake",
dfa38cf4cd4batagen2026-10-10 09:17 UTC687
            "The clinic requires authorization.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC688
        );
dfa38cf4cd4batagen2026-10-10 09:17 UTC689
        let Some(authorization) = authorization else {
dfa38cf4cd4batagen2026-10-10 09:17 UTC690
            return Err(intake.header(header::WWW_AUTHENTICATE, "Bearer realm=\"clinic\""));
dfa38cf4cd4batagen2026-10-10 09:17 UTC691
        };
177535ac8abfatagen2026-10-10 10:39 UTC692
        let truthful = parse_credentials(authorization, "bearer")
dfa38cf4cd4batagen2026-10-10 09:17 UTC693
            .is_some_and(|token| token.eq_ignore_ascii_case("honesty"));
177535ac8abfatagen2026-10-10 10:39 UTC694
        if !truthful {
dfa38cf4cd4batagen2026-10-10 09:17 UTC695
            let hint = INTAKE[self.intake_attempts.min(INTAKE.len() - 1)];
dfa38cf4cd4batagen2026-10-10 09:17 UTC696
            self.intake_attempts += 1;
dfa38cf4cd4batagen2026-10-10 09:17 UTC697
            return Err(intake.header(
dfa38cf4cd4batagen2026-10-10 09:17 UTC698
                header::WWW_AUTHENTICATE,
dfa38cf4cd4batagen2026-10-10 09:17 UTC699
                format!(
dfa38cf4cd4batagen2026-10-10 09:17 UTC700
                    "Bearer realm=\"clinic\", error=\"invalid_token\", error_description=\"{hint}\""
dfa38cf4cd4batagen2026-10-10 09:17 UTC701
                ),
dfa38cf4cd4batagen2026-10-10 09:17 UTC702
            ));
dfa38cf4cd4batagen2026-10-10 09:17 UTC703
        }
dfa38cf4cd4batagen2026-10-10 09:17 UTC704
dfa38cf4cd4batagen2026-10-10 09:17 UTC705
        self.peyronie = false;
dfa38cf4cd4batagen2026-10-10 09:17 UTC706
        let etag = self.origin_etag.clone();
dfa38cf4cd4batagen2026-10-10 09:17 UTC707
        if if_none_match.is_some_and(|inm| super::etag_matches(inm, &etag)) {
dfa38cf4cd4batagen2026-10-10 09:17 UTC708
            self.cured = true;
dfa38cf4cd4batagen2026-10-10 09:17 UTC709
            return Ok(Outcome::empty(StatusCode::NOT_MODIFIED)
dfa38cf4cd4batagen2026-10-10 09:17 UTC710
                .header(header::ETAG, etag)
dfa38cf4cd4batagen2026-10-10 09:17 UTC711
                .header(X_DIAGNOSIS, "normal"));
dfa38cf4cd4batagen2026-10-10 09:17 UTC712
        }
dfa38cf4cd4batagen2026-10-10 09:17 UTC713
        if !self.measured {
dfa38cf4cd4batagen2026-10-10 09:17 UTC714
            self.measured = true;
dfa38cf4cd4batagen2026-10-10 09:17 UTC715
            if !self.items.ruler {
dfa38cf4cd4batagen2026-10-10 09:17 UTC716
                self.shame += 15;
dfa38cf4cd4batagen2026-10-10 09:17 UTC717
            }
dfa38cf4cd4batagen2026-10-10 09:17 UTC718
        }
dfa38cf4cd4batagen2026-10-10 09:17 UTC719
        Ok(Outcome::json(
dfa38cf4cd4batagen2026-10-10 09:17 UTC720
            StatusCode::OK,
dfa38cf4cd4batagen2026-10-10 09:17 UTC721
            json!({
dfa38cf4cd4batagen2026-10-10 09:17 UTC722
                "diagnosis": "within normal range",
dfa38cf4cd4batagen2026-10-10 09:17 UTC723
                "measured_length": round2(self.length),
dfa38cf4cd4batagen2026-10-10 09:17 UTC724
                "you_said": round2(self.self_reported()),
dfa38cf4cd4batagen2026-10-10 09:17 UTC725
                "prescription": "Please stop.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC726
            }),
dfa38cf4cd4batagen2026-10-10 09:17 UTC727
        )
dfa38cf4cd4batagen2026-10-10 09:17 UTC728
        .header(header::ETAG, etag))
dfa38cf4cd4batagen2026-10-10 09:17 UTC729
    }
dfa38cf4cd4batagen2026-10-10 09:17 UTC730
}
dfa38cf4cd4batagen2026-10-10 09:17 UTC731
dfa38cf4cd4batagen2026-10-10 09:17 UTC732
fn pump_bro_absent() -> Problem {
dfa38cf4cd4batagen2026-10-10 09:17 UTC733
    Problem::new(
dfa38cf4cd4batagen2026-10-10 09:17 UTC734
        StatusCode::NOT_FOUND,
dfa38cf4cd4batagen2026-10-10 09:17 UTC735
        "pump-bro-absent",
dfa38cf4cd4batagen2026-10-10 09:17 UTC736
        "Pump-Bro is not here.",
dfa38cf4cd4batagen2026-10-10 09:17 UTC737
    )
dfa38cf4cd4batagen2026-10-10 09:17 UTC738
}