1use std::any::{TypeId, type_name};
8use std::collections::{HashMap, HashSet};
9use std::marker::PhantomData;
10use crate::{ServiceCollection, Lifetime};
11
12pub struct ValidationBuilder<State = Initial> {
46 registrations: HashMap<TypeId, LifetimeInfo>,
47 dependencies: HashMap<TypeId, Vec<TypeId>>,
48 trait_registrations: HashMap<&'static str, Vec<TypeId>>,
49 _state: PhantomData<State>,
50}
51
52pub struct Initial;
54
55pub struct Validated;
57
58#[derive(Debug, Clone)]
60pub struct LifetimeInfo {
61 pub lifetime: Lifetime,
63 pub type_name: &'static str,
65 pub explicit: bool,
67 pub dependencies: Vec<TypeId>,
69}
70
71#[derive(Debug)]
73pub struct ValidationResult {
74 pub errors: Vec<ValidationError>,
76 pub warnings: Vec<ValidationWarning>,
78 pub services: HashMap<TypeId, LifetimeInfo>,
80}
81
82#[derive(Debug, Clone)]
84pub enum ValidationError {
85 SingletonDependsOnScoped {
87 singleton: &'static str,
88 singleton_id: TypeId,
89 scoped: &'static str,
90 scoped_id: TypeId,
91 },
92 MissingDependency {
94 service: &'static str,
95 service_id: TypeId,
96 dependency: &'static str,
97 dependency_id: TypeId,
98 },
99 CircularDependency {
101 cycle: Vec<(&'static str, TypeId)>,
102 },
103 UnimplementedTrait {
105 trait_name: &'static str,
106 implementations: Vec<&'static str>,
107 },
108}
109
110#[derive(Debug, Clone)]
112pub enum ValidationWarning {
113 SingletonDependsOnTransient {
115 singleton: &'static str,
116 transient: &'static str,
117 },
118 UnusedService {
120 service: &'static str,
121 },
122 MultipleTraitImplementations {
124 trait_name: &'static str,
125 implementations: Vec<&'static str>,
126 },
127}
128
129impl ValidationBuilder<Initial> {
130 pub fn new() -> Self {
132 Self {
133 registrations: HashMap::new(),
134 dependencies: HashMap::new(),
135 trait_registrations: HashMap::new(),
136 _state: PhantomData,
137 }
138 }
139
140 pub fn register<T: 'static>(mut self, lifetime: Lifetime) -> Self {
142 let type_id = TypeId::of::<T>();
143 let info = LifetimeInfo {
144 lifetime,
145 type_name: type_name::<T>(),
146 explicit: true,
147 dependencies: Vec::new(),
148 };
149 self.registrations.insert(type_id, info);
150 self
151 }
152
153 pub fn register_factory<T: 'static, F>(self, lifetime: Lifetime) -> Self
155 where
156 F: Fn(&crate::provider::ResolverContext) -> T,
157 {
158 self.register::<T>(lifetime)
159 }
160
161 pub fn register_trait<T: ?Sized + 'static, I: 'static>(mut self, lifetime: Lifetime) -> Self {
163 let trait_name = type_name::<T>();
164 let impl_id = TypeId::of::<I>();
165
166 self.trait_registrations
167 .entry(trait_name)
168 .or_insert_with(Vec::new)
169 .push(impl_id);
170
171 self.register::<I>(lifetime)
172 }
173
174 pub fn depends_on<T: 'static, D: 'static>(mut self) -> Self {
176 let service_id = TypeId::of::<T>();
177 let dep_id = TypeId::of::<D>();
178
179 self.dependencies
180 .entry(service_id)
181 .or_insert_with(Vec::new)
182 .push(dep_id);
183
184 if let Some(info) = self.registrations.get_mut(&service_id) {
186 if !info.dependencies.contains(&dep_id) {
187 info.dependencies.push(dep_id);
188 }
189 }
190
191 if !self.registrations.contains_key(&dep_id) {
193 let info = LifetimeInfo {
194 lifetime: Lifetime::Transient, type_name: type_name::<D>(),
196 explicit: false,
197 dependencies: Vec::new(),
198 };
199 self.registrations.insert(dep_id, info);
200 }
201
202 self
203 }
204
205 pub fn validate(self) -> ValidationBuilder<Validated> {
207 ValidationBuilder {
210 registrations: self.registrations,
211 dependencies: self.dependencies,
212 trait_registrations: self.trait_registrations,
213 _state: PhantomData,
214 }
215 }
216
217 pub fn validate_runtime(self) -> ValidationResult {
219 let mut errors = Vec::new();
220 let mut warnings = Vec::new();
221
222 for (service_id, service_info) in &self.registrations {
224 if let Some(deps) = self.dependencies.get(service_id) {
225 for &dep_id in deps {
226 if let Some(dep_info) = self.registrations.get(&dep_id) {
227 if service_info.lifetime == Lifetime::Singleton
229 && dep_info.lifetime == Lifetime::Scoped {
230 errors.push(ValidationError::SingletonDependsOnScoped {
231 singleton: service_info.type_name,
232 singleton_id: *service_id,
233 scoped: dep_info.type_name,
234 scoped_id: dep_id,
235 });
236 }
237
238 if service_info.lifetime == Lifetime::Singleton
240 && dep_info.lifetime == Lifetime::Transient {
241 warnings.push(ValidationWarning::SingletonDependsOnTransient {
242 singleton: service_info.type_name,
243 transient: dep_info.type_name,
244 });
245 }
246 } else {
247 errors.push(ValidationError::MissingDependency {
249 service: service_info.type_name,
250 service_id: *service_id,
251 dependency: "Unknown", dependency_id: dep_id,
253 });
254 }
255 }
256 }
257 }
258
259 let cycles = self.detect_cycles();
261 for cycle in cycles {
262 errors.push(ValidationError::CircularDependency { cycle });
263 }
264
265 for (trait_name, implementations) in &self.trait_registrations {
267 if implementations.is_empty() {
268 errors.push(ValidationError::UnimplementedTrait {
269 trait_name,
270 implementations: Vec::new(),
271 });
272 } else if implementations.len() > 1 {
273 let impl_names: Vec<_> = implementations.iter()
274 .filter_map(|&id| self.registrations.get(&id))
275 .map(|info| info.type_name)
276 .collect();
277 warnings.push(ValidationWarning::MultipleTraitImplementations {
278 trait_name,
279 implementations: impl_names,
280 });
281 }
282 }
283
284 ValidationResult {
285 errors,
286 warnings,
287 services: self.registrations.clone(),
288 }
289 }
290
291 fn detect_cycles(&self) -> Vec<Vec<(&'static str, TypeId)>> {
293 let mut visited = HashSet::new();
294 let mut path = Vec::new();
295 let mut cycles = Vec::new();
296
297 for &service_id in self.registrations.keys() {
298 if !visited.contains(&service_id) {
299 self.dfs_cycles(service_id, &mut visited, &mut path, &mut cycles);
300 }
301 }
302
303 cycles
304 }
305
306 fn dfs_cycles(
307 &self,
308 current: TypeId,
309 visited: &mut HashSet<TypeId>,
310 path: &mut Vec<TypeId>,
311 cycles: &mut Vec<Vec<(&'static str, TypeId)>>,
312 ) {
313 if let Some(cycle_start) = path.iter().position(|&id| id == current) {
314 let cycle_info: Vec<_> = path[cycle_start..]
316 .iter()
317 .chain(std::iter::once(¤t))
318 .filter_map(|&id| {
319 self.registrations.get(&id).map(|info| (info.type_name, id))
320 })
321 .collect();
322 cycles.push(cycle_info);
323 return;
324 }
325
326 if visited.contains(¤t) {
327 return;
328 }
329
330 visited.insert(current);
331 path.push(current);
332
333 if let Some(deps) = self.dependencies.get(¤t) {
334 for &dep in deps {
335 self.dfs_cycles(dep, visited, path, cycles);
336 }
337 }
338
339 path.pop();
340 }
341}
342
343impl ValidationBuilder<Validated> {
344 pub fn build(self) -> ServiceCollection {
346 ServiceCollection::new()
349 }
350
351 pub fn get_result(&self) -> ValidationResult {
353 ValidationResult {
354 errors: Vec::new(), warnings: Vec::new(),
356 services: self.registrations.clone(),
357 }
358 }
359}
360
361impl Default for ValidationBuilder<Initial> {
362 fn default() -> Self {
363 Self::new()
364 }
365}
366
367pub mod compile_time {
372 use super::*;
373
374 #[macro_export]
392 macro_rules! validate_services {
393 (
394 $(
395 $lifetime:ident $service:ty $(; depends_on $($dep:ty),+)?
396 ),*
397 ) => {
398 const _: fn() = || {
401 $( validate_service_registration::<$service>($crate::Lifetime::$lifetime); )*
402 };
403 };
404 }
405
406 pub const fn validate_service_registration<T>(_lifetime: Lifetime) {
408 }
411
412 pub const fn validate_lifetime_dependency(
414 service_lifetime: Lifetime,
415 dependency_lifetime: Lifetime,
416 ) -> bool {
417 match (service_lifetime, dependency_lifetime) {
418 (Lifetime::Singleton, Lifetime::Scoped) => false, _ => true, }
421 }
422}
423
424impl ValidationResult {
426 pub fn is_valid(&self) -> bool {
428 self.errors.is_empty()
429 }
430
431 pub fn has_warnings(&self) -> bool {
433 !self.warnings.is_empty()
434 }
435
436 pub fn format_issues(&self) -> String {
438 let mut output = String::new();
439
440 if !self.errors.is_empty() {
441 output.push_str("Validation Errors:\n");
442 for error in &self.errors {
443 output.push_str(&format!(" - {}\n", self.format_error(error)));
444 }
445 }
446
447 if !self.warnings.is_empty() {
448 if !output.is_empty() {
449 output.push('\n');
450 }
451 output.push_str("Validation Warnings:\n");
452 for warning in &self.warnings {
453 output.push_str(&format!(" - {}\n", self.format_warning(warning)));
454 }
455 }
456
457 output
458 }
459
460 fn format_error(&self, error: &ValidationError) -> String {
461 match error {
462 ValidationError::SingletonDependsOnScoped { singleton, scoped, .. } => {
463 format!("Singleton service '{}' cannot depend on scoped service '{}'", singleton, scoped)
464 }
465 ValidationError::MissingDependency { service, dependency, .. } => {
466 format!("Service '{}' depends on unregistered service '{}'", service, dependency)
467 }
468 ValidationError::CircularDependency { cycle } => {
469 let names: Vec<_> = cycle.iter().map(|(name, _)| *name).collect();
470 format!("Circular dependency detected: {}", names.join(" → "))
471 }
472 ValidationError::UnimplementedTrait { trait_name, .. } => {
473 format!("Trait '{}' registered but no implementation provided", trait_name)
474 }
475 }
476 }
477
478 fn format_warning(&self, warning: &ValidationWarning) -> String {
479 match warning {
480 ValidationWarning::SingletonDependsOnTransient { singleton, transient } => {
481 format!("Singleton '{}' depends on transient '{}' - will always get same instance", singleton, transient)
482 }
483 ValidationWarning::UnusedService { service } => {
484 format!("Service '{}' is registered but never used", service)
485 }
486 ValidationWarning::MultipleTraitImplementations { trait_name, implementations } => {
487 format!("Trait '{}' has multiple implementations: {}", trait_name, implementations.join(", "))
488 }
489 }
490 }
491}
492
493impl ServiceCollection {
495 pub fn create_validator(&self) -> ValidationBuilder<Initial> {
520 ValidationBuilder::new()
521 }
524
525 pub fn validate(&self) -> ValidationResult {
527 self.create_validator().validate_runtime()
528 }
529}