Skip to main content

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}