atagen

jelq

public

an http based roguelike rpg about jelqing

Star0
Watch0
5cc6ab966f30f5bc5611ddefd166242a46ff3954
1
ssh://brewery@pls.lobotomise.me:33/atagen/jelq.git
src/game/body.rsRust121 lines3.7 KB
1
use axum::http::StatusCode;
2
use serde::Deserialize;
3
use serde_json::{Value, json};
4
5
use super::{
6
    Grip, Outcome, Run,
7
    balance::{EXAGGERATION, PUT_GAIN},
8
    etag_matches, honest,
9
};
10
use crate::problem::Problem;
11
12
#[derive(Deserialize)]
13
pub struct PatchOp {
14
    pub op: String,
15
    pub path: String,
16
    #[serde(default)]
17
    pub value: Value,
18
}
19
20
pub(super) fn exaggeration() -> Problem {
21
    Problem::new(
22
        StatusCode::BAD_REQUEST,
23
        "exaggeration",
24
        "The server has measured. Lying is not a technique.",
25
    )
26
}
27
28
impl Run {
29
    pub fn put(&mut self) -> Outcome {
30
        if self.put_applied {
31
            return Outcome::empty(StatusCode::NO_CONTENT);
32
        }
33
        self.put_applied = true;
34
        self.length += PUT_GAIN;
35
        self.touch();
36
        Outcome::json(StatusCode::OK, json!({ "detail": "Applied." }))
37
    }
38
39
    pub fn patch(&mut self, ops: &[PatchOp]) -> Result<Outcome, Problem> {
40
        for op in ops {
41
            if op.op != "test" {
42
                return Err(Problem::new(
43
                    StatusCode::UNPROCESSABLE_ENTITY,
44
                    "wishful-thinking",
45
                    "The server accepts only the truth.",
46
                )
47
                .with("op", op.op.clone()));
48
            }
49
            let actual = match op.path.as_str() {
50
                "/length" => self.current_length(),
51
                "/girth" => self.girth,
52
                "/foreskin" => self.foreskin.unwrap_or(0.0),
53
                _ => {
54
                    return Err(Problem::new(
55
                        StatusCode::UNPROCESSABLE_ENTITY,
56
                        "no-such-measurement",
57
                        "There is no such measurement.",
58
                    )
59
                    .with("path", op.path.clone()));
60
                }
61
            };
62
            let Some(claimed) = op.value.as_f64() else {
63
                return Err(Problem::new(
64
                    StatusCode::BAD_REQUEST,
65
                    "malformed-patch",
66
                    "A test requires a numeric value.",
67
                ));
68
            };
69
            if op.path == "/length" && claimed >= actual + EXAGGERATION {
70
                self.shame += 2;
71
                self.touch();
72
                return Err(exaggeration());
73
            }
74
            if !honest(claimed, actual) {
75
                self.shame += 1;
76
                self.touch();
77
                return Err(
78
                    Problem::new(StatusCode::CONFLICT, "test-failed", "The test failed.")
79
                        .with("path", op.path.clone()),
80
                );
81
            }
82
        }
83
        Ok(Outcome::json(
84
            StatusCode::OK,
85
            json!({ "detail": "Correct." }),
86
        ))
87
    }
88
89
    pub fn delete_foreskin(&mut self, if_match: Option<&str>) -> Result<Outcome, Problem> {
90
        if self.foreskin.is_none() {
91
            return Err(Problem::new(
92
                StatusCode::GONE,
93
                "foreskin-gone",
94
                "The foreskin is gone. It will not be returning.",
95
            ));
96
        }
97
        let Some(if_match) = if_match else {
98
            return Err(Problem::new(
99
                StatusCode::PRECONDITION_REQUIRED,
100
                "confirmation-required",
101
                "The server requires you to be sure. Send If-Match.",
102
            ));
103
        };
104
        if !etag_matches(if_match, &self.etag()) {
105
            return Err(Problem::new(
106
                StatusCode::PRECONDITION_FAILED,
107
                "stale-foreskin",
108
                "The foreskin has changed since you last looked at it.",
109
            ));
110
        }
111
        self.foreskin = None;
112
        self.shame += 20;
113
        self.touch();
114
        Ok(Outcome::empty(StatusCode::NO_CONTENT))
115
    }
116
117
    pub fn set_grip(&mut self, grip: Grip) {
118
        self.grip = grip;
119
        self.touch();
120
    }
121
}