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}