perma-worker 0.1.0

Serverless permalink service
Documentation
use perma_core::{Redirect, RedirectTo, RelativeRef};

use worker::event;
use worker::Method;
use worker::{Context, Env, Request, Response, Result};

#[event(fetch)]
async fn fetch(mut req: Request, env: Env, _ctx: Context) -> Result<Response> {
    let kv = env.kv("KV")?;
    let psk = env.secret("PSK")?.to_string();
    let url = req.url()?;

    if url.path() == "/api/v1/_redirects" {
        if req.headers().get("Authorization")? != Some(format!("Bearer sk-{}", psk)) {
            return Response::error("unauthorized", 401);
        }

        if req.method() == Method::Get {
            let accept = req.headers().get("Accept")?.unwrap_or_default();
            match accept.as_str() {
                "text/redirects" => {
                    if let Some(redirects) = kv.get("_redirects:raw").bytes().await? {
                        Response::builder()
                            .with_header("Content-Type", "application/octet-stream")?
                            .from_bytes(redirects)
                    } else {
                        Response::error("_redirects not found", 404)
                    }
                }
                "application/json" | _ => {
                    if let Some(redirects) = kv.get("_redirects").bytes().await? {
                        Response::builder()
                            .with_header("Content-Type", "application/application/json")?
                            .from_bytes(redirects)
                    } else {
                        Response::error("_redirects not found", 404)
                    }
                }
            }
        } else if req.method() == Method::Put {
            let ct = req.headers().get("Content-Type")?.unwrap_or_default();

            match ct.as_str() {
                "text/redirects" => {
                    let raw = req.text().await?;
                    match raw
                        .lines()
                        .map(|l| l.trim())
                        .filter(|l| !l.is_empty() && !l.starts_with('#'))
                        .map(|l| l.parse::<Redirect>().map_err(|e| (l, e)))
                        .collect::<Result<Vec<_>, _>>()
                    {
                        Ok(redirects) => {
                            kv.put("_redirects:raw", raw.as_str())?.execute().await?;
                            kv.put("_redirects", &redirects)?.execute().await?;
                            Response::empty()
                        }
                        Err((l, e)) => {
                            Response::error(format!("parsing \"{}\" failed: {}", l, e), 400)
                        }
                    }
                }
                "application/json" => {
                    let redirects = req.json::<Vec<Redirect>>().await?;
                    let raw = redirects
                        .iter()
                        .map(|r| format!("{} {} {}", r.from(), r.to(), r.code().as_u16()))
                        .collect::<Vec<_>>()
                        .join("\n");
                    kv.put("_redirects:raw", raw.as_str())?.execute().await?;
                    kv.put("_redirects", &redirects)?.execute().await?;
                    Response::empty()
                }
                _ => Response::error("bad request", 400),
            }
        } else {
            Response::error("method not allowed", 405)
        }
    } else {
        if let Some(redirects) = kv.get("_redirects").json::<Vec<Redirect>>().await? {
            let lookup = url.path().trim_end_matches('/');
            if let Some((redirect, rel)) = redirects.into_iter().find_map(|r| {
                let path = r.from().path().trim_end_matches('/');
                if path == lookup {
                    Some((r, None))
                } else if let Some(path) = r.from().path().strip_suffix("*") {
                    if let Some(rel) = lookup.strip_prefix(path.trim_end_matches('/')) {
                        Some((r, Some(RelativeRef::parse(rel).unwrap())))
                    } else {
                        None
                    }
                } else {
                    None
                }
            }) {
                let mut to = match redirect.to() {
                    RedirectTo::Uri(uri) => uri.clone(),
                    RedirectTo::Path(path) => url.join(path.as_str())?,
                };

                if to.path_segments().and_then(|segs| segs.last()) == Some(":splat") {
                    if let Ok(mut segments) = to.path_segments_mut() {
                        segments.pop();

                        if let Some(rel) = rel {
                            for seg in rel.path_segments().into_iter().flatten() {
                                segments.push(seg);
                            }
                        }
                    }
                }

                Response::builder()
                    .with_status(redirect.code().as_u16())
                    .with_header("location", &to.to_string())?
                    .from_html(&format!(
                        "<a href=\"{}\">Redirecting...</a>",
                        to.to_string()
                    ))
            } else {
                Response::error("not found", 404)
            }
        } else {
            Response::error("_redirects not found", 404)
        }
    }
}