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