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