systemprompt_loader/bundle/source/
mod.rs1pub mod https;
14pub mod oci;
15mod stream;
16
17use std::path::{Path, PathBuf};
18
19use systemprompt_models::profile::ServicesSource;
20
21use super::error::{BundleError, BundleResult};
22
23pub use https::HttpsFetcher;
24pub use oci::{OciFetcher, push_bundle};
25
26pub const MAX_BUNDLE_BYTES: u64 = 512 * 1024 * 1024;
27
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct RemoteRef {
30 pub digest: String,
31}
32
33impl RemoteRef {
34 #[must_use]
35 pub const fn is_unknown(&self) -> bool {
36 self.digest.is_empty()
37 }
38}
39
40#[derive(Debug, Clone)]
41pub struct FetchedBundle {
42 pub archive: PathBuf,
43 pub digest: String,
44}
45
46pub trait BundleFetcher {
47 fn head(&self) -> impl Future<Output = BundleResult<RemoteRef>> + Send;
48
49 fn fetch(&self, into: &Path) -> impl Future<Output = BundleResult<FetchedBundle>> + Send;
50}
51
52#[derive(Debug)]
53pub enum AnyFetcher {
54 Https(HttpsFetcher),
55 Oci(OciFetcher),
56}
57
58impl AnyFetcher {
59 pub fn from_source(
60 source: &ServicesSource,
61 auth: Option<String>,
62 client: &reqwest::Client,
63 ) -> BundleResult<Self> {
64 if !source.is_exactly_one() {
65 return Err(BundleError::policy(format!(
66 "source {} must declare exactly one of https: or oci:",
67 source.name
68 )));
69 }
70 if let Some(https) = source.https.as_ref() {
71 return Ok(Self::Https(HttpsFetcher::new(
72 &source.name,
73 &https.url,
74 auth,
75 client.clone(),
76 )));
77 }
78 let oci = source
79 .oci
80 .as_ref()
81 .ok_or_else(|| BundleError::policy("source declares no transport"))?;
82 Ok(Self::Oci(OciFetcher::new(
83 &source.name,
84 &oci.reference,
85 auth,
86 client.clone(),
87 )?))
88 }
89}
90
91impl BundleFetcher for AnyFetcher {
92 async fn head(&self) -> BundleResult<RemoteRef> {
93 match self {
94 Self::Https(f) => f.head().await,
95 Self::Oci(f) => f.head().await,
96 }
97 }
98
99 async fn fetch(&self, into: &Path) -> BundleResult<FetchedBundle> {
100 match self {
101 Self::Https(f) => f.fetch(into).await,
102 Self::Oci(f) => f.fetch(into).await,
103 }
104 }
105}