agent-pay 0.1.0

L402 + DID-signed invoices: agent-to-agent Lightning payments (Rust port of @p-vbordei/agent-pay)
Documentation
//! Shared test harness: glues a Paywall to a FetchFn so the client can call it.
#![allow(dead_code)]

use std::collections::HashMap;
use std::sync::Arc;

use agent_pay::{
    fetch_with_l402, FetchFn, FetchOptions, FetchResponse, InnerHandler, LightningNode, Paywall,
    PaywallResponse,
};

pub const SECRET: &[u8] = b"thirty-two-byte-test-secret-pad!";

pub fn make_fetch(paywall: Arc<Paywall>, inner: InnerHandler) -> FetchFn {
    Arc::new(move |url: String, headers: HashMap<String, String>| {
        let paywall = paywall.clone();
        let inner = inner.clone();
        Box::pin(async move {
            let path = path_from_url(&url);
            let resp = paywall.process_request(&path, headers, Some(inner)).await?;
            Ok(to_fetch(resp))
        })
    })
}

pub fn make_raw_fetch(paywall: Arc<Paywall>, inner: InnerHandler) -> FetchFn {
    Arc::new(move |url: String, headers: HashMap<String, String>| {
        let paywall = paywall.clone();
        let inner = inner.clone();
        Box::pin(async move {
            let path = path_from_url(&url);
            let resp = paywall.process_request(&path, headers, Some(inner)).await?;
            Ok(to_fetch(resp))
        })
    })
}

pub fn path_from_url(url: &str) -> String {
    if let Some(after_scheme) = url.split_once("://") {
        let (_, after) = after_scheme;
        if let Some((_host, path)) = after.split_once('/') {
            return format!("/{path}");
        }
    }
    url.to_string()
}

pub fn to_fetch(resp: PaywallResponse) -> FetchResponse {
    FetchResponse {
        status: resp.status,
        headers: resp.headers,
        body: resp.body,
        json: resp.json,
    }
}

pub fn ok_handler() -> InnerHandler {
    Arc::new(|_path: String, _h: HashMap<String, String>| {
        Box::pin(async move {
            Ok(PaywallResponse {
                status: 200,
                json: Some(serde_json::json!({ "ok": true })),
                ..Default::default()
            })
        })
    })
}

pub fn echo_handler() -> InnerHandler {
    Arc::new(|_path: String, _h: HashMap<String, String>| {
        Box::pin(async move {
            Ok(PaywallResponse {
                status: 200,
                json: Some(serde_json::json!({ "data": "hello" })),
                ..Default::default()
            })
        })
    })
}

pub async fn run_client(
    wallet: Arc<dyn LightningNode>,
    max_price_msat: u64,
    fetch: FetchFn,
    expected_did: Option<String>,
) -> Result<FetchResponse, agent_pay::Error> {
    let mut opts = FetchOptions::new(wallet, max_price_msat, fetch);
    opts.expected_did = expected_did;
    fetch_with_l402("http://x/r", opts).await
}