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.
20#[derive(Debug, Clone)]
21pub struct HttpRequest {
22    pub method: Method,
23    pub url: String,
24    pub headers: Vec<(String, String)>,
25    pub body: Vec<u8>,
26}
27
28impl HttpRequest {
29    /// A bare GET for a public (unauthenticated) URL: no headers, no token.
30    pub fn get(url: impl Into<String>) -> Self {
31        Self {
32            method: Method::Get,
33            url: url.into(),
34            headers: Vec::new(),
35            body: Vec::new(),
36        }
37    }
38
39    /// A POST carrying `body` (an empty `body` is a bodyless POST).
40    pub fn post(url: impl Into<String>, body: Vec<u8>) -> Self {
41        Self {
42            method: Method::Post,
43            url: url.into(),
44            headers: Vec::new(),
45            body,
46        }
47    }
48}
49
50/// The response an adapter returns to the engine.
51#[derive(Debug, Clone)]
52pub struct HttpResponse {
53    pub status: u16,
54    pub headers: Vec<(String, String)>,
55    pub body: Vec<u8>,
56}
57
58impl HttpResponse {
59    /// Read a header value by case-insensitive name, if present.
60    ///
61    /// The download executor uses this for `Content-Length` (provider-reported
62    /// size) and `Retry-After` (rate-limit backoff), so the lookup must ignore
63    /// header-name casing the way HTTP does.
64    pub fn header(&self, name: &str) -> Option<&str> {
65        self.headers
66            .iter()
67            .find(|(key, _)| key.eq_ignore_ascii_case(name))
68            .map(|(_, value)| value.as_str())
69    }
70}
71
72/// A failure to complete a request at the transport level.
73#[derive(Debug, thiserror::Error)]
74#[error("{0}")]
75pub struct TransportError(pub String);
76
77/// The HTTP port an adapter implements for the engine.
78pub trait Http {
79    /// Perform `request` and return the response, or a [`TransportError`].
80    fn send(
81        &self,
82        request: HttpRequest,
83    ) -> impl Future<Output = Result<HttpResponse, TransportError>> + Send;
84}