1use std::{
2 collections::HashMap,
3 pin::Pin,
4 sync::Arc,
5 task::{self, Poll},
6};
7use tokio::io::{self, AsyncWrite};
8
9use snix_castore::utils as castore_utils;
10use snix_castore::{blobservice::BlobService, directoryservice::DirectoryService};
11use url::Url;
12
13use crate::composition::REG;
14use crate::nar::{NarCalculationService, SimpleRenderer};
15use crate::pathinfoservice::PathInfoService;
16use snix_castore::composition::{
17 Composition, DeserializeWithRegistry, ServiceBuilder, with_registry,
18};
19
20#[derive(serde::Deserialize, Default)]
21pub struct CompositionConfigs {
22 pub blobservices:
23 HashMap<String, DeserializeWithRegistry<Box<dyn ServiceBuilder<Output = dyn BlobService>>>>,
24 pub directoryservices: HashMap<
25 String,
26 DeserializeWithRegistry<Box<dyn ServiceBuilder<Output = dyn DirectoryService>>>,
27 >,
28 pub pathinfoservices: HashMap<
29 String,
30 DeserializeWithRegistry<Box<dyn ServiceBuilder<Output = dyn PathInfoService>>>,
31 >,
32}
33
34#[derive(clap::Parser, Clone)]
39#[group(id = "StoreServiceUrls")]
40pub struct ServiceUrls {
41 #[clap(flatten)]
42 pub castore_service_addrs: castore_utils::ServiceUrls,
43
44 #[arg(long, env, default_value = "redb:/var/lib/snix-store/pathinfo.redb")]
45 pub path_info_service_addr: String,
46}
47
48#[derive(clap::Parser, Clone)]
53#[group(id = "StoreServiceUrlsGrpc")]
54pub struct ServiceUrlsGrpc {
55 #[clap(flatten)]
56 castore_service_addrs: castore_utils::ServiceUrlsGrpc,
57
58 #[arg(long, env, default_value = "grpc+http://[::1]:8000")]
59 path_info_service_addr: String,
60}
61
62#[derive(clap::Parser, Clone)]
70#[group(id = "StoreServiceUrlsMemory")]
71pub struct ServiceUrlsMemory {
72 #[clap(flatten)]
73 castore_service_addrs: castore_utils::ServiceUrlsMemory,
74
75 #[arg(long, env, default_value = "redb+memory:")]
76 path_info_service_addr: String,
77}
78
79impl From<ServiceUrlsGrpc> for ServiceUrls {
80 fn from(urls: ServiceUrlsGrpc) -> ServiceUrls {
81 ServiceUrls {
82 castore_service_addrs: urls.castore_service_addrs.into(),
83 path_info_service_addr: urls.path_info_service_addr,
84 }
85 }
86}
87
88impl From<ServiceUrlsMemory> for ServiceUrls {
89 fn from(urls: ServiceUrlsMemory) -> ServiceUrls {
90 ServiceUrls {
91 castore_service_addrs: urls.castore_service_addrs.into(),
92 path_info_service_addr: urls.path_info_service_addr,
93 }
94 }
95}
96
97pub async fn addrs_to_configs(
102 urls: impl Into<ServiceUrls>,
103) -> Result<CompositionConfigs, Box<dyn std::error::Error + Send + Sync>> {
104 let urls: ServiceUrls = urls.into();
105
106 #[cfg(feature = "xp-composition-cli")]
107 if let Some(conf_path) = urls.castore_service_addrs.experimental_store_composition {
108 let conf_text = tokio::fs::read_to_string(conf_path).await?;
109 return Ok(with_registry(®, || toml::from_str(&conf_text))?);
110 }
111
112 let mut configs: CompositionConfigs = Default::default();
113
114 let blob_service_url = Url::parse(&urls.castore_service_addrs.blob_service_addr)?;
115 let directory_service_url = Url::parse(&urls.castore_service_addrs.directory_service_addr)?;
116 let path_info_service_url = Url::parse(&urls.path_info_service_addr)?;
117
118 configs.blobservices.insert(
119 "root".into(),
120 with_registry(®, || blob_service_url.try_into())?,
121 );
122 configs.directoryservices.insert(
123 "root".into(),
124 with_registry(®, || directory_service_url.try_into())?,
125 );
126 configs.pathinfoservices.insert(
127 "root".into(),
128 with_registry(®, || path_info_service_url.try_into())?,
129 );
130
131 Ok(configs)
132}
133
134pub async fn construct_services(
136 urls: impl Into<ServiceUrls>,
137) -> Result<
138 (
139 Arc<dyn BlobService>,
140 Arc<dyn DirectoryService>,
141 Arc<dyn PathInfoService>,
142 Box<dyn NarCalculationService>,
143 ),
144 Box<dyn std::error::Error + Send + Sync>,
145> {
146 let configs = addrs_to_configs(urls).await?;
147 construct_services_from_configs(configs).await
148}
149
150pub async fn construct_services_from_configs(
152 configs: CompositionConfigs,
153) -> Result<
154 (
155 Arc<dyn BlobService>,
156 Arc<dyn DirectoryService>,
157 Arc<dyn PathInfoService>,
158 Box<dyn NarCalculationService>,
159 ),
160 Box<dyn std::error::Error + Send + Sync>,
161> {
162 let mut comp = Composition::new(®);
163
164 comp.extend(configs.blobservices);
165 comp.extend(configs.directoryservices);
166 comp.extend(configs.pathinfoservices);
167
168 let blob_service: Arc<dyn BlobService> = comp.build("root").await?;
169 let directory_service: Arc<dyn DirectoryService> = comp.build("root").await?;
170 let path_info_service: Arc<dyn PathInfoService> = comp.build("root").await?;
171
172 let nar_calculation_service: Box<dyn NarCalculationService> = path_info_service
176 .nar_calculation_service()
177 .unwrap_or_else(|| {
178 Box::new(SimpleRenderer::new(
179 blob_service.clone(),
180 directory_service.clone(),
181 ))
182 });
183
184 Ok((
185 blob_service,
186 directory_service,
187 path_info_service,
188 nar_calculation_service,
189 ))
190}
191
192pub struct AsyncIoBridge<T>(pub T);
195
196impl<W: std::io::Write + Unpin> AsyncWrite for AsyncIoBridge<W> {
197 fn poll_write(
198 self: Pin<&mut Self>,
199 _cx: &mut task::Context<'_>,
200 buf: &[u8],
201 ) -> Poll<io::Result<usize>> {
202 Poll::Ready(self.get_mut().0.write(buf))
203 }
204
205 fn poll_flush(self: Pin<&mut Self>, _cx: &mut task::Context<'_>) -> Poll<io::Result<()>> {
206 Poll::Ready(self.get_mut().0.flush())
207 }
208
209 fn poll_shutdown(
210 self: Pin<&mut Self>,
211 _cx: &mut task::Context<'_>,
212 ) -> Poll<Result<(), io::Error>> {
213 Poll::Ready(Ok(()))
214 }
215}
216
217pub fn gen_test_pathinfo_service() -> impl PathInfoService + Clone {
219 crate::pathinfoservice::RedbPathInfoService::new_temporary(
220 "test".to_string(),
221 crate::pathinfoservice::RedbPathInfoServiceConfig::default(),
222 )
223 .expect("creating pathinfoservice to succeed")
224}