atagen

jelq

public

an http based roguelike rpg about jelqing

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