atagen

jelq

public

an http based roguelike rpg about jelqing

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

Blame: src/http/shaft.rs

8b8e83329a1datagen2026-10-12 12:01 UTC1
use std::convert::Infallible;
8b8e83329a1datagen2026-10-12 12:01 UTC2
8b8e83329a1datagen2026-10-12 12:01 UTC3
use axum::{
8b8e83329a1datagen2026-10-12 12:01 UTC4
    body::{Body, Bytes},
8b8e83329a1datagen2026-10-12 12:01 UTC5
    http::{StatusCode, header},
8b8e83329a1datagen2026-10-12 12:01 UTC6
    response::{IntoResponse, Response},
8b8e83329a1datagen2026-10-12 12:01 UTC7
};
8b8e83329a1datagen2026-10-12 12:01 UTC8
8b8e83329a1datagen2026-10-12 12:01 UTC9
use super::ctx::Ctx;
8b8e83329a1datagen2026-10-12 12:01 UTC10
use crate::{
8b8e83329a1datagen2026-10-12 12:01 UTC11
    game::{Run, balance::MM_PER_INCH, shaft},
8b8e83329a1datagen2026-10-12 12:01 UTC12
    problem::Problem,
8b8e83329a1datagen2026-10-12 12:01 UTC13
};
8b8e83329a1datagen2026-10-12 12:01 UTC14
8b8e83329a1datagen2026-10-12 12:01 UTC15
const SHAFT_CHUNK: usize = 16;
8b8e83329a1datagen2026-10-12 12:01 UTC16
8b8e83329a1datagen2026-10-12 12:01 UTC17
pub(super) fn millimetres(inches: f64) -> usize {
8b8e83329a1datagen2026-10-12 12:01 UTC18
    (inches * MM_PER_INCH).round() as usize
8b8e83329a1datagen2026-10-12 12:01 UTC19
}
8b8e83329a1datagen2026-10-12 12:01 UTC20
8b8e83329a1datagen2026-10-12 12:01 UTC21
pub(super) enum InchRange {
8b8e83329a1datagen2026-10-12 12:01 UTC22
    Satisfiable(f64, f64),
8b8e83329a1datagen2026-10-12 12:01 UTC23
    Unsatisfiable,
8b8e83329a1datagen2026-10-12 12:01 UTC24
}
8b8e83329a1datagen2026-10-12 12:01 UTC25
8b8e83329a1datagen2026-10-12 12:01 UTC26
/// `None` means the header is not a valid range, which RFC 9110 §14.2 says to ignore rather than refuse.
8b8e83329a1datagen2026-10-12 12:01 UTC27
pub(super) fn parse_inch_range(range: &str, total: f64) -> Option<InchRange> {
8b8e83329a1datagen2026-10-12 12:01 UTC28
    let (start, end) = range.strip_prefix("inches=")?.split_once('-')?;
8b8e83329a1datagen2026-10-12 12:01 UTC29
    let number = |text: &str| {
8b8e83329a1datagen2026-10-12 12:01 UTC30
        text.parse::<f64>()
8b8e83329a1datagen2026-10-12 12:01 UTC31
            .ok()
8b8e83329a1datagen2026-10-12 12:01 UTC32
            .filter(|n| n.is_finite() && *n >= 0.0)
8b8e83329a1datagen2026-10-12 12:01 UTC33
    };
8b8e83329a1datagen2026-10-12 12:01 UTC34
    let (start, end) = match (start.trim(), end.trim()) {
8b8e83329a1datagen2026-10-12 12:01 UTC35
        ("", "") => return None,
8b8e83329a1datagen2026-10-12 12:01 UTC36
        ("", suffix) => ((total - number(suffix)?).max(0.0), total),
8b8e83329a1datagen2026-10-12 12:01 UTC37
        (start, "") => (number(start)?, total),
8b8e83329a1datagen2026-10-12 12:01 UTC38
        (start, end) => {
8b8e83329a1datagen2026-10-12 12:01 UTC39
            let (start, end) = (number(start)?, number(end)?);
8b8e83329a1datagen2026-10-12 12:01 UTC40
            if start > end {
8b8e83329a1datagen2026-10-12 12:01 UTC41
                return None;
8b8e83329a1datagen2026-10-12 12:01 UTC42
            }
8b8e83329a1datagen2026-10-12 12:01 UTC43
            (start, end)
8b8e83329a1datagen2026-10-12 12:01 UTC44
        }
8b8e83329a1datagen2026-10-12 12:01 UTC45
    };
8b8e83329a1datagen2026-10-12 12:01 UTC46
    Some(if start < total {
8b8e83329a1datagen2026-10-12 12:01 UTC47
        InchRange::Satisfiable(start, end.min(total))
8b8e83329a1datagen2026-10-12 12:01 UTC48
    } else {
8b8e83329a1datagen2026-10-12 12:01 UTC49
        InchRange::Unsatisfiable
8b8e83329a1datagen2026-10-12 12:01 UTC50
    })
8b8e83329a1datagen2026-10-12 12:01 UTC51
}
8b8e83329a1datagen2026-10-12 12:01 UTC52
8b8e83329a1datagen2026-10-12 12:01 UTC53
pub(super) fn unsatisfied_range(inches: f64) -> Response {
8b8e83329a1datagen2026-10-12 12:01 UTC54
    Problem::new(
8b8e83329a1datagen2026-10-12 12:01 UTC55
        StatusCode::RANGE_NOT_SATISFIABLE,
8b8e83329a1datagen2026-10-12 12:01 UTC56
        "range-not-satisfiable",
8b8e83329a1datagen2026-10-12 12:01 UTC57
        format!("The requested range is not available. {inches:.2} inches are."),
8b8e83329a1datagen2026-10-12 12:01 UTC58
    )
8b8e83329a1datagen2026-10-12 12:01 UTC59
    .header(header::CONTENT_RANGE, format!("inches */{inches:.2}"))
8b8e83329a1datagen2026-10-12 12:01 UTC60
    .into_response()
8b8e83329a1datagen2026-10-12 12:01 UTC61
}
8b8e83329a1datagen2026-10-12 12:01 UTC62
8b8e83329a1datagen2026-10-12 12:01 UTC63
pub(super) fn partial_inches(
8b8e83329a1datagen2026-10-12 12:01 UTC64
    body: &str,
8b8e83329a1datagen2026-10-12 12:01 UTC65
    offset: usize,
8b8e83329a1datagen2026-10-12 12:01 UTC66
    start: f64,
8b8e83329a1datagen2026-10-12 12:01 UTC67
    end: f64,
8b8e83329a1datagen2026-10-12 12:01 UTC68
    total: f64,
8b8e83329a1datagen2026-10-12 12:01 UTC69
) -> Response {
8b8e83329a1datagen2026-10-12 12:01 UTC70
    let from = (millimetres(start) + offset).min(body.len() - 1);
8b8e83329a1datagen2026-10-12 12:01 UTC71
    let to = (millimetres(end) + offset).clamp(from + 1, body.len());
8b8e83329a1datagen2026-10-12 12:01 UTC72
    (
8b8e83329a1datagen2026-10-12 12:01 UTC73
        StatusCode::PARTIAL_CONTENT,
8b8e83329a1datagen2026-10-12 12:01 UTC74
        [
8b8e83329a1datagen2026-10-12 12:01 UTC75
            (
8b8e83329a1datagen2026-10-12 12:01 UTC76
                header::CONTENT_TYPE,
8b8e83329a1datagen2026-10-12 12:01 UTC77
                "text/plain; charset=us-ascii".to_owned(),
8b8e83329a1datagen2026-10-12 12:01 UTC78
            ),
8b8e83329a1datagen2026-10-12 12:01 UTC79
            (header::ACCEPT_RANGES, "inches".to_owned()),
8b8e83329a1datagen2026-10-12 12:01 UTC80
            (
8b8e83329a1datagen2026-10-12 12:01 UTC81
                header::CONTENT_RANGE,
8b8e83329a1datagen2026-10-12 12:01 UTC82
                format!("inches {start:.2}-{end:.2}/{total:.2}"),
8b8e83329a1datagen2026-10-12 12:01 UTC83
            ),
8b8e83329a1datagen2026-10-12 12:01 UTC84
        ],
8b8e83329a1datagen2026-10-12 12:01 UTC85
        body[from..to].to_owned(),
8b8e83329a1datagen2026-10-12 12:01 UTC86
    )
8b8e83329a1datagen2026-10-12 12:01 UTC87
        .into_response()
8b8e83329a1datagen2026-10-12 12:01 UTC88
}
8b8e83329a1datagen2026-10-12 12:01 UTC89
8b8e83329a1datagen2026-10-12 12:01 UTC90
pub(super) fn get_length(run: &mut Run, ctx: &Ctx) -> Response {
8b8e83329a1datagen2026-10-12 12:01 UTC91
    let inches = run.shown_length();
8b8e83329a1datagen2026-10-12 12:01 UTC92
    let body = shaft(millimetres(inches));
8b8e83329a1datagen2026-10-12 12:01 UTC93
    let content_type = (
8b8e83329a1datagen2026-10-12 12:01 UTC94
        header::CONTENT_TYPE,
8b8e83329a1datagen2026-10-12 12:01 UTC95
        "text/plain; charset=us-ascii".to_owned(),
8b8e83329a1datagen2026-10-12 12:01 UTC96
    );
8b8e83329a1datagen2026-10-12 12:01 UTC97
    let accept_ranges = (header::ACCEPT_RANGES, "inches".to_owned());
8b8e83329a1datagen2026-10-12 12:01 UTC98
8b8e83329a1datagen2026-10-12 12:01 UTC99
    if let Some(range) = ctx
8b8e83329a1datagen2026-10-12 12:01 UTC100
        .header(header::RANGE)
8b8e83329a1datagen2026-10-12 12:01 UTC101
        .and_then(|range| parse_inch_range(range, inches))
8b8e83329a1datagen2026-10-12 12:01 UTC102
    {
8b8e83329a1datagen2026-10-12 12:01 UTC103
        match range {
8b8e83329a1datagen2026-10-12 12:01 UTC104
            InchRange::Satisfiable(start, end) => {
8b8e83329a1datagen2026-10-12 12:01 UTC105
                return partial_inches(&body, 0, start, end, inches);
8b8e83329a1datagen2026-10-12 12:01 UTC106
            }
8b8e83329a1datagen2026-10-12 12:01 UTC107
            InchRange::Unsatisfiable => return unsatisfied_range(inches),
8b8e83329a1datagen2026-10-12 12:01 UTC108
        }
8b8e83329a1datagen2026-10-12 12:01 UTC109
    }
8b8e83329a1datagen2026-10-12 12:01 UTC110
8b8e83329a1datagen2026-10-12 12:01 UTC111
    if run.measured() {
8b8e83329a1datagen2026-10-12 12:01 UTC112
        return (StatusCode::OK, [content_type, accept_ranges], body).into_response();
8b8e83329a1datagen2026-10-12 12:01 UTC113
    }
8b8e83329a1datagen2026-10-12 12:01 UTC114
    let chunks: Vec<Result<Bytes, Infallible>> = body
8b8e83329a1datagen2026-10-12 12:01 UTC115
        .into_bytes()
8b8e83329a1datagen2026-10-12 12:01 UTC116
        .chunks(SHAFT_CHUNK)
8b8e83329a1datagen2026-10-12 12:01 UTC117
        .map(|c| Ok(Bytes::copy_from_slice(c)))
8b8e83329a1datagen2026-10-12 12:01 UTC118
        .collect();
8b8e83329a1datagen2026-10-12 12:01 UTC119
    let body = Body::from_stream(futures_util::stream::iter(chunks));
8b8e83329a1datagen2026-10-12 12:01 UTC120
    (StatusCode::OK, [content_type, accept_ranges], body).into_response()
8b8e83329a1datagen2026-10-12 12:01 UTC121
}
8b8e83329a1datagen2026-10-12 12:01 UTC122
8b8e83329a1datagen2026-10-12 12:01 UTC123
pub(super) fn get_foreskin(run: &mut Run, ctx: &Ctx) -> Response {
8b8e83329a1datagen2026-10-12 12:01 UTC124
    let Some(inches) = run.foreskin() else {
8b8e83329a1datagen2026-10-12 12:01 UTC125
        return Problem::new(
8b8e83329a1datagen2026-10-12 12:01 UTC126
            StatusCode::GONE,
8b8e83329a1datagen2026-10-12 12:01 UTC127
            "foreskin-gone",
8b8e83329a1datagen2026-10-12 12:01 UTC128
            "The foreskin is gone. It will not be returning.",
8b8e83329a1datagen2026-10-12 12:01 UTC129
        )
8b8e83329a1datagen2026-10-12 12:01 UTC130
        .into_response();
8b8e83329a1datagen2026-10-12 12:01 UTC131
    };
8b8e83329a1datagen2026-10-12 12:01 UTC132
    let body = format!("8({})D", "~".repeat(millimetres(inches)));
8b8e83329a1datagen2026-10-12 12:01 UTC133
    if let Some(range) = ctx
8b8e83329a1datagen2026-10-12 12:01 UTC134
        .header(header::RANGE)
8b8e83329a1datagen2026-10-12 12:01 UTC135
        .and_then(|range| parse_inch_range(range, inches))
8b8e83329a1datagen2026-10-12 12:01 UTC136
    {
8b8e83329a1datagen2026-10-12 12:01 UTC137
        match range {
8b8e83329a1datagen2026-10-12 12:01 UTC138
            InchRange::Satisfiable(start, end) => {
8b8e83329a1datagen2026-10-12 12:01 UTC139
                return partial_inches(&body, 2, start, end, inches);
8b8e83329a1datagen2026-10-12 12:01 UTC140
            }
8b8e83329a1datagen2026-10-12 12:01 UTC141
            InchRange::Unsatisfiable => return unsatisfied_range(inches),
8b8e83329a1datagen2026-10-12 12:01 UTC142
        }
8b8e83329a1datagen2026-10-12 12:01 UTC143
    }
8b8e83329a1datagen2026-10-12 12:01 UTC144
    (
8b8e83329a1datagen2026-10-12 12:01 UTC145
        StatusCode::OK,
8b8e83329a1datagen2026-10-12 12:01 UTC146
        [
8b8e83329a1datagen2026-10-12 12:01 UTC147
            (header::ETAG, run.etag()),
8b8e83329a1datagen2026-10-12 12:01 UTC148
            (
8b8e83329a1datagen2026-10-12 12:01 UTC149
                header::CONTENT_TYPE,
8b8e83329a1datagen2026-10-12 12:01 UTC150
                "text/plain; charset=us-ascii".to_owned(),
8b8e83329a1datagen2026-10-12 12:01 UTC151
            ),
8b8e83329a1datagen2026-10-12 12:01 UTC152
            (header::ACCEPT_RANGES, "inches".to_owned()),
8b8e83329a1datagen2026-10-12 12:01 UTC153
        ],
8b8e83329a1datagen2026-10-12 12:01 UTC154
        body,
8b8e83329a1datagen2026-10-12 12:01 UTC155
    )
8b8e83329a1datagen2026-10-12 12:01 UTC156
        .into_response()
8b8e83329a1datagen2026-10-12 12:01 UTC157
}