an http based roguelike rpg about jelqing
| 1 | mod body; |
| 2 | mod floors; |
| 3 | mod household; |
| 4 | mod meta; |
| 5 | mod methods; |
| 6 | mod rolls; |
| 7 | mod turns; |
| 8 | |
| 9 | pub use std::{sync::Arc, time::Duration}; |
| 10 | |
| 11 | pub use axum::{ |
| 12 | Router, |
| 13 | body::{Body, to_bytes}, |
| 14 | http::{Method, Request, StatusCode, Version, header}, |
| 15 | response::Response, |
| 16 | }; |
| 17 | pub use serde_json::Value; |
| 18 | use tower::ServiceExt; |
| 19 | |
| 20 | pub use crate::{ |
| 21 | AppState, Config, |
| 22 | game::{ |
| 23 | Excuse, Floor, MumVisit, Run, Story, |
| 24 | balance::{BRANCH_TURN, SUSPICION_LIMIT}, |
| 25 | }, |
| 26 | router, |
| 27 | }; |
| 28 | |
| 29 | pub const SEED: u64 = 420; |
| 30 | |
| 31 | pub async fn send( |
| 32 | app: &Router, |
| 33 | method: &str, |
| 34 | uri: &str, |
| 35 | headers: &[(&str, &str)], |
| 36 | body: &str, |
| 37 | ) -> Response { |
| 38 | let mut request = Request::builder() |
| 39 | .method(Method::from_bytes(method.as_bytes()).unwrap()) |
| 40 | .uri(uri); |
| 41 | for (name, value) in headers { |
| 42 | request = request.header(*name, *value); |
| 43 | } |
| 44 | app.clone() |
| 45 | .oneshot(request.body(Body::from(body.to_owned())).unwrap()) |
| 46 | .await |
| 47 | .unwrap() |
| 48 | } |
| 49 | |
| 50 | pub async fn json(response: Response) -> Value { |
| 51 | serde_json::from_slice(&to_bytes(response.into_body(), usize::MAX).await.unwrap()).unwrap() |
| 52 | } |
| 53 | |
| 54 | pub async fn text(response: Response) -> String { |
| 55 | String::from_utf8( |
| 56 | to_bytes(response.into_body(), usize::MAX) |
| 57 | .await |
| 58 | .unwrap() |
| 59 | .to_vec(), |
| 60 | ) |
| 61 | .unwrap() |
| 62 | } |
| 63 | |
| 64 | pub fn header<'a>(response: &'a Response, name: &str) -> &'a str { |
| 65 | response.headers()[name].to_str().unwrap() |
| 66 | } |
| 67 | |
| 68 | /// Every `Warning` on a response, since a turn can carry several. |
| 69 | pub fn warnings(response: &Response) -> Vec<String> { |
| 70 | response |
| 71 | .headers() |
| 72 | .get_all("warning") |
| 73 | .iter() |
| 74 | .map(|value| value.to_str().unwrap().to_owned()) |
| 75 | .collect() |
| 76 | } |
| 77 | |
| 78 | /// A server holding one run, `cafebabe`, set up by `setup` before anyone can reach it. |
| 79 | pub struct Harness { |
| 80 | pub app: Router, |
| 81 | pub origin: String, |
| 82 | } |
| 83 | |
| 84 | pub fn harness(floor: Floor, seed: u64, setup: impl FnOnce(&mut Run)) -> Harness { |
| 85 | let state = Arc::new(AppState::default()); |
| 86 | let mut run = Run::new("cafebabe".into(), "cafe".into(), seed); |
| 87 | run.floor = floor; |
| 88 | setup(&mut run); |
| 89 | let origin = run.origin_etag(); |
| 90 | state.insert(run); |
| 91 | Harness { |
| 92 | app: router(state), |
| 93 | origin, |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | pub fn seeded_with(floor: Floor, setup: impl FnOnce(&mut Run)) -> Router { |
| 98 | harness(floor, SEED, setup).app |
| 99 | } |
| 100 | |
| 101 | pub fn app_with(floor: Floor, seed: u64, setup: impl FnOnce(&mut Run)) -> Router { |
| 102 | harness(floor, seed, setup).app |
| 103 | } |
| 104 | |
| 105 | pub fn app() -> Router { |
| 106 | crate::app(Config::default()).unwrap() |
| 107 | } |
| 108 | |
| 109 | pub fn mum_at_the_door(run: &mut Run) { |
| 110 | run.household.mum = Some(MumVisit { |
| 111 | errand: "She heard a noise.", |
| 112 | nonce: "abc".into(), |
| 113 | strikes: 0, |
| 114 | knocked: false, |
| 115 | }); |
| 116 | } |
| 117 | |
| 118 | /// The `Link` entries on a response, as `(rel, title)` with the rel base stripped. |
| 119 | pub fn links(response: &Response) -> Vec<(String, Option<String>)> { |
| 120 | header(response, "link") |
| 121 | .split(", <") |
| 122 | .filter_map(|entry| { |
| 123 | let rel = entry.split("rels/").nth(1)?.split('"').next()?.to_owned(); |
| 124 | let title = entry |
| 125 | .split("title=\"") |
| 126 | .nth(1) |
| 127 | .and_then(|t| t.split('"').next()) |
| 128 | .map(str::to_owned); |
| 129 | Some((rel, title)) |
| 130 | }) |
| 131 | .collect() |
| 132 | } |
| 133 | |
| 134 | pub fn advertised(response: &Response) -> Vec<String> { |
| 135 | links(response) |
| 136 | .into_iter() |
| 137 | .filter(|(rel, _)| rel == "technique") |
| 138 | .filter_map(|(_, title)| title) |
| 139 | .collect() |
| 140 | } |
| 141 | |
| 142 | pub fn linked_rels(response: &Response) -> Vec<String> { |
| 143 | links(response) |
| 144 | .into_iter() |
| 145 | .map(|(rel, _)| rel) |
| 146 | .filter(|rel| rel != "technique") |
| 147 | .collect() |
| 148 | } |