More macros

This commit is contained in:
Riley Apeldoorn 2024-05-02 23:03:31 +02:00
parent edc21b4403
commit 21b47409f1
3 changed files with 63 additions and 32 deletions

View file

@ -29,6 +29,46 @@ macro_rules! fuck {
}; };
} }
// Makes a response.
macro_rules! respond {
($($arg:tt)*) => {
crate::api::Resp {
$($arg)*,
.. crate::api::Resp::default()
}.into()
};
}
/// Parameters for a response
struct Resp<'s> {
body: Option<&'s Value>,
kind: &'s str,
code: u16,
}
impl<'s> Default for Resp<'s> {
fn default() -> Self {
Resp {
body: None,
kind: "application/json",
code: 200,
}
}
}
impl<'a> From<Resp<'a>> for Response {
fn from(Resp { body, kind, code }: Resp<'_>) -> Response {
let resp = Response::<()>::builder()
.status(code)
.header("content-type", kind);
resp.body(match body {
Some(data) => Full::new(serde_json::to_vec(&data).unwrap().into()),
None => Full::new(Bytes::default()),
})
.unwrap()
}
}
pub mod ap; pub mod ap;
pub mod wf; pub mod wf;
@ -205,23 +245,6 @@ fn with_json(
} }
} }
/// A quick, simple way to construct a response.
fn respond<const N: usize>(
status: u16,
body: Option<impl Into<Bytes>>,
headers: [(&str, &str); N],
) -> Response {
let mut resp = Response::<()>::builder().status(status);
for (name, data) in headers {
resp = resp.header(name, data);
}
resp.body(match body {
Some(bytes) => Full::new(bytes.into()),
None => Full::new(Bytes::default()),
})
.unwrap()
}
mod error { mod error {
//! Pre-baked error responses. //! Pre-baked error responses.
@ -261,10 +284,13 @@ mod error {
} }
/// Check whether the requester wants json from us. /// Check whether the requester wants json from us.
pub fn accepts_json(&self) -> bool { pub fn accepts_json(&self) -> bool {
fn is_json((k, v): (&str, &str)) -> bool {
k == "application" && v.split('+').any(|p| p == "json")
}
self.accept self.accept
.iter() .iter()
.filter_map(|s| s.split_once('/')) .filter_map(|s| s.split_once('/'))
.any(|(k, v)| k == "application" && v.split('+').any(|p| p == "json")) .any(is_json)
} }
} }
} }

View file

@ -6,19 +6,19 @@ use puppy::{
actor::{get_signing_key, Actor}, actor::{get_signing_key, Actor},
get_local_ap_object, Context, Error, Key, get_local_ap_object, Context, Error, Key,
}; };
use serde_json::{to_string, Value}; use serde_json::Value;
use crate::sig::{Signer, Verifier}; use crate::sig::{Signer, Verifier};
use super::{error::Message, respond, Response}; use super::{error::Message, Response};
/// Proxy a request through the instance. /// Proxy a request through the instance.
pub async fn proxy(cx: &Context, params: &[(&str, &str)]) -> Result<Response, Message> { pub async fn proxy(cx: &Context, params: &[(&str, &str)]) -> Result<Response, Message> {
// Extract our query parameters. // Extract our query parameters.
let Some(user) = params.iter().find_map(|(k, v)| (*k == "user").then_some(v)) else { let Some(user) = params.iter().find_map(|(k, v)| (*k == "user").then_some(v)) else {
fuck!(400: "Expected `user` query param"); fuck!(400: "expected `user` query param");
}; };
let Some(url) = params.iter().find_map(|(k, v)| (*k == "url").then_some(v)) else { let Some(url) = params.iter().find_map(|(k, v)| (*k == "url").then_some(v)) else {
fuck!(400: "Expected `url` query param"); fuck!(400: "expected `url` query param");
}; };
// Look up the actor's key in the store (which is accessible through the puppy context). // Look up the actor's key in the store (which is accessible through the puppy context).
@ -48,19 +48,22 @@ pub fn serve_object(cx: &Context, object_ulid: &str) -> Result<Response, Message
let Ok(parsed) = object_ulid.parse::<Key>() else { let Ok(parsed) = object_ulid.parse::<Key>() else {
fuck!(400: "improperly formatted ulid"); fuck!(400: "improperly formatted ulid");
}; };
let result = cx.run(|tx| get_local_ap_object(&tx, parsed)); let result = cx.run(|tx| get_local_ap_object(&tx, parsed));
let Ok(object) = result else { let Ok(object) = result else {
fuck!(404: "object does not exist"); fuck!(404: "object does not exist");
}; };
let json = to_string(&object.to_json_ld()).unwrap();
Ok(respond(200, Some(json), [AP_CONTENT_TYPE]))
}
const AP_CONTENT_TYPE: (&str, &str) = ("content-type", "application/activity+json"); Ok(respond! {
kind: "application/activity+json",
body: Some(&object.to_json_ld())
})
}
/// Serve the special actor used for signing requests. /// Serve the special actor used for signing requests.
pub fn serve_verifier_actor(verifier: &Verifier) -> Response { pub fn serve_verifier_actor(verifier: &Verifier) -> Response {
let body = verifier.to_json_ld(); respond! {
let encoded = serde_json::to_vec(&body).unwrap(); kind: "application/activity+json",
respond(200, Some(encoded), [AP_CONTENT_TYPE]) body: Some(&verifier.to_json_ld())
}
} }

View file

@ -7,7 +7,7 @@ use puppy::{
use serde_json::{json, Value}; use serde_json::{json, Value};
use derive_more::Display; use derive_more::Display;
use super::{error::Message, respond, Response}; use super::{error::Message, Response};
const WF_CONTENT_TYPE: (&str, &str) = ("content-type", "application/jrd+json"); const WF_CONTENT_TYPE: (&str, &str) = ("content-type", "application/jrd+json");
@ -23,8 +23,10 @@ pub fn resolve(cx: &Context, params: &[(&str, &str)]) -> Result<Response, Messag
fuck!(500: "internal error"); fuck!(500: "internal error");
}; };
let jrd = make_jrd(handle, &id); let jrd = make_jrd(handle, &id);
let encoded = serde_json::to_vec(&jrd).unwrap(); Ok(respond! {
Ok(respond(200, Some(encoded), [WF_CONTENT_TYPE])) body: Some(&jrd),
kind: "application/jrd+json"
})
} }
Some(_) | None => fuck!(400: "missing/invalid resource param"), Some(_) | None => fuck!(400: "missing/invalid resource param"),
} }