1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
//! The HTTP port: the engine's only window to the network.
//!
//! The engine builds [`HttpRequest`]s and reads [`HttpResponse`]s but never
//! performs IO itself. A CLI adapter implements [`Http`] with a real client,
//! which keeps the engine testable with a simple in-memory double.
use std::future::Future;
/// The HTTP method for a request. Clerk and Suno only need these two.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Method {
Get,
Post,
}
/// A request the engine wants an adapter to perform.
///
/// `body` is empty for GET and for bodyless POSTs (the Clerk token mint). An
/// adapter sends it only when non-empty, so a bodyless request stays on the
/// wire exactly as before.
#[derive(Debug, Clone)]
pub struct HttpRequest {
pub method: Method,
pub url: String,
pub headers: Vec<(String, String)>,
pub body: Vec<u8>,
}
impl HttpRequest {
/// A bare GET for a public (unauthenticated) URL: no headers, no token.
pub fn get(url: impl Into<String>) -> Self {
Self {
method: Method::Get,
url: url.into(),
headers: Vec::new(),
body: Vec::new(),
}
}
/// A POST carrying `body` (an empty `body` is a bodyless POST).
pub fn post(url: impl Into<String>, body: Vec<u8>) -> Self {
Self {
method: Method::Post,
url: url.into(),
headers: Vec::new(),
body,
}
}
}
/// The response an adapter returns to the engine.
#[derive(Debug, Clone)]
pub struct HttpResponse {
pub status: u16,
pub headers: Vec<(String, String)>,
pub body: Vec<u8>,
}
impl HttpResponse {
/// Read a header value by case-insensitive name, if present.
///
/// The download executor uses this for `Content-Length` (provider-reported
/// size) and `Retry-After` (rate-limit backoff), so the lookup must ignore
/// header-name casing the way HTTP does.
pub fn header(&self, name: &str) -> Option<&str> {
self.headers
.iter()
.find(|(key, _)| key.eq_ignore_ascii_case(name))
.map(|(_, value)| value.as_str())
}
}
/// A failure to complete a request at the transport level.
#[derive(Debug, thiserror::Error)]
#[error("{0}")]
pub struct TransportError(pub String);
/// The HTTP port an adapter implements for the engine.
pub trait Http {
/// Perform `request` and return the response, or a [`TransportError`].
fn send(
&self,
request: HttpRequest,
) -> impl Future<Output = Result<HttpResponse, TransportError>> + Send;
}