1use crate::engine::ForgeEngine;
4use crate::types::blueprint::*;
5use crate::types::ids::*;
6use crate::types::{ForgeError, ForgeResult};
7
8pub struct QueryEngine<'a> {
9 engine: &'a ForgeEngine,
10}
11
12impl<'a> QueryEngine<'a> {
13 pub fn new(engine: &'a ForgeEngine) -> Self {
14 Self { engine }
15 }
16
17 pub fn get_blueprint(&self, id: &BlueprintId) -> ForgeResult<&Blueprint> {
20 self.engine.store.load(id)
21 }
22
23 pub fn list_blueprints(&self) -> Vec<&Blueprint> {
24 self.engine.store.list()
25 }
26
27 pub fn list_by_status(&self, status: BlueprintStatus) -> Vec<&Blueprint> {
28 self.engine.store.list_by_status(status)
29 }
30
31 pub fn search_blueprints(&self, query: &str) -> Vec<&Blueprint> {
32 self.engine
33 .store
34 .list()
35 .into_iter()
36 .filter(|bp| {
37 bp.name.to_lowercase().contains(&query.to_lowercase())
38 || bp
39 .description
40 .to_lowercase()
41 .contains(&query.to_lowercase())
42 })
43 .collect()
44 }
45
46 pub fn blueprint_count(&self) -> usize {
47 self.engine.store.count()
48 }
49
50 pub fn blueprint_exists(&self, id: &BlueprintId) -> bool {
51 self.engine.store.contains(id)
52 }
53
54 pub fn get_entity(&self, bp_id: &BlueprintId, entity_id: &EntityId) -> ForgeResult<&Entity> {
57 let bp = self.engine.store.load(bp_id)?;
58 bp.entities
59 .iter()
60 .find(|e| e.id == *entity_id)
61 .ok_or_else(|| ForgeError::EntityNotFound(entity_id.to_string()))
62 }
63
64 pub fn get_entity_by_name(&self, bp_id: &BlueprintId, name: &str) -> ForgeResult<&Entity> {
65 let bp = self.engine.store.load(bp_id)?;
66 bp.find_entity(name)
67 .ok_or_else(|| ForgeError::EntityNotFound(name.to_string()))
68 }
69
70 pub fn list_entities(&self, bp_id: &BlueprintId) -> ForgeResult<&[Entity]> {
71 let bp = self.engine.store.load(bp_id)?;
72 Ok(&bp.entities)
73 }
74
75 pub fn entity_count(&self, bp_id: &BlueprintId) -> ForgeResult<usize> {
76 let bp = self.engine.store.load(bp_id)?;
77 Ok(bp.entity_count())
78 }
79
80 pub fn search_entities(&self, bp_id: &BlueprintId, query: &str) -> ForgeResult<Vec<&Entity>> {
81 let bp = self.engine.store.load(bp_id)?;
82 Ok(bp
83 .entities
84 .iter()
85 .filter(|e| {
86 e.name.to_lowercase().contains(&query.to_lowercase())
87 || e.description.to_lowercase().contains(&query.to_lowercase())
88 })
89 .collect())
90 }
91
92 pub fn list_aggregate_roots(&self, bp_id: &BlueprintId) -> ForgeResult<Vec<&Entity>> {
93 let bp = self.engine.store.load(bp_id)?;
94 Ok(bp.entities.iter().filter(|e| e.is_aggregate_root).collect())
95 }
96
97 pub fn get_file(&self, bp_id: &BlueprintId, file_id: &FileId) -> ForgeResult<&FileBlueprint> {
100 let bp = self.engine.store.load(bp_id)?;
101 bp.files
102 .iter()
103 .find(|f| f.id == *file_id)
104 .ok_or_else(|| ForgeError::FileNotFound(file_id.to_string()))
105 }
106
107 pub fn get_file_by_path(&self, bp_id: &BlueprintId, path: &str) -> ForgeResult<&FileBlueprint> {
108 let bp = self.engine.store.load(bp_id)?;
109 bp.find_file(path)
110 .ok_or_else(|| ForgeError::FileNotFound(path.to_string()))
111 }
112
113 pub fn list_files(&self, bp_id: &BlueprintId) -> ForgeResult<&[FileBlueprint]> {
114 let bp = self.engine.store.load(bp_id)?;
115 Ok(&bp.files)
116 }
117
118 pub fn list_files_by_type(
119 &self,
120 bp_id: &BlueprintId,
121 ft: FileType,
122 ) -> ForgeResult<Vec<&FileBlueprint>> {
123 let bp = self.engine.store.load(bp_id)?;
124 Ok(bp.files.iter().filter(|f| f.file_type == ft).collect())
125 }
126
127 pub fn file_count(&self, bp_id: &BlueprintId) -> ForgeResult<usize> {
128 let bp = self.engine.store.load(bp_id)?;
129 Ok(bp.file_count())
130 }
131
132 pub fn get_dependency(
135 &self,
136 bp_id: &BlueprintId,
137 dep_id: &DependencyId,
138 ) -> ForgeResult<&Dependency> {
139 let bp = self.engine.store.load(bp_id)?;
140 bp.dependencies
141 .iter()
142 .find(|d| d.id == *dep_id)
143 .ok_or_else(|| ForgeError::DependencyNotFound(dep_id.to_string()))
144 }
145
146 pub fn get_dependency_by_name(
147 &self,
148 bp_id: &BlueprintId,
149 name: &str,
150 ) -> ForgeResult<&Dependency> {
151 let bp = self.engine.store.load(bp_id)?;
152 bp.find_dependency(name)
153 .ok_or_else(|| ForgeError::DependencyNotFound(name.to_string()))
154 }
155
156 pub fn list_dependencies(&self, bp_id: &BlueprintId) -> ForgeResult<&[Dependency]> {
157 let bp = self.engine.store.load(bp_id)?;
158 Ok(&bp.dependencies)
159 }
160
161 pub fn list_dependencies_by_type(
162 &self,
163 bp_id: &BlueprintId,
164 dt: DependencyType,
165 ) -> ForgeResult<Vec<&Dependency>> {
166 let bp = self.engine.store.load(bp_id)?;
167 Ok(bp
168 .dependencies
169 .iter()
170 .filter(|d| d.dep_type == dt)
171 .collect())
172 }
173
174 pub fn dependency_count(&self, bp_id: &BlueprintId) -> ForgeResult<usize> {
175 let bp = self.engine.store.load(bp_id)?;
176 Ok(bp.dependency_count())
177 }
178
179 pub fn get_test_case(&self, bp_id: &BlueprintId, tc_id: &TestCaseId) -> ForgeResult<&TestCase> {
182 let bp = self.engine.store.load(bp_id)?;
183 bp.test_cases
184 .iter()
185 .find(|t| t.id == *tc_id)
186 .ok_or_else(|| ForgeError::TestCaseNotFound(tc_id.to_string()))
187 }
188
189 pub fn list_test_cases(&self, bp_id: &BlueprintId) -> ForgeResult<&[TestCase]> {
190 let bp = self.engine.store.load(bp_id)?;
191 Ok(&bp.test_cases)
192 }
193
194 pub fn list_tests_by_type(
195 &self,
196 bp_id: &BlueprintId,
197 tt: TestType,
198 ) -> ForgeResult<Vec<&TestCase>> {
199 let bp = self.engine.store.load(bp_id)?;
200 Ok(bp.test_cases.iter().filter(|t| t.test_type == tt).collect())
201 }
202
203 pub fn test_count(&self, bp_id: &BlueprintId) -> ForgeResult<usize> {
204 let bp = self.engine.store.load(bp_id)?;
205 Ok(bp.test_count())
206 }
207
208 pub fn list_type_definitions(&self, bp_id: &BlueprintId) -> ForgeResult<&[TypeDefinition]> {
211 let bp = self.engine.store.load(bp_id)?;
212 Ok(&bp.type_definitions)
213 }
214
215 pub fn get_type_definition(
216 &self,
217 bp_id: &BlueprintId,
218 name: &str,
219 ) -> ForgeResult<&TypeDefinition> {
220 let bp = self.engine.store.load(bp_id)?;
221 bp.type_definitions
222 .iter()
223 .find(|t| t.name == name)
224 .ok_or_else(|| ForgeError::MissingField(name.to_string()))
225 }
226
227 pub fn list_function_blueprints(
230 &self,
231 bp_id: &BlueprintId,
232 ) -> ForgeResult<&[FunctionBlueprint]> {
233 let bp = self.engine.store.load(bp_id)?;
234 Ok(&bp.function_blueprints)
235 }
236
237 pub fn list_layers(&self, bp_id: &BlueprintId) -> ForgeResult<&[ArchitectureLayer]> {
240 let bp = self.engine.store.load(bp_id)?;
241 Ok(&bp.layers)
242 }
243
244 pub fn list_concerns(&self, bp_id: &BlueprintId) -> ForgeResult<&[CrossCuttingConcern]> {
245 let bp = self.engine.store.load(bp_id)?;
246 Ok(&bp.concerns)
247 }
248
249 pub fn list_wiring(&self, bp_id: &BlueprintId) -> ForgeResult<&[ComponentWiring]> {
252 let bp = self.engine.store.load(bp_id)?;
253 Ok(&bp.wiring)
254 }
255
256 pub fn list_data_flows(&self, bp_id: &BlueprintId) -> ForgeResult<&[DataFlow]> {
257 let bp = self.engine.store.load(bp_id)?;
258 Ok(&bp.data_flows)
259 }
260
261 pub fn list_import_graph(&self, bp_id: &BlueprintId) -> ForgeResult<&[ImportEdge]> {
262 let bp = self.engine.store.load(bp_id)?;
263 Ok(&bp.import_graph)
264 }
265
266 pub fn get_generation_order(&self, bp_id: &BlueprintId) -> ForgeResult<&[String]> {
267 let bp = self.engine.store.load(bp_id)?;
268 Ok(&bp.generation_order)
269 }
270
271 pub fn validate_blueprint(&self, bp_id: &BlueprintId) -> ForgeResult<Vec<String>> {
274 let bp = self.engine.store.load(bp_id)?;
275 let mut issues = Vec::new();
276
277 if bp.name.is_empty() {
278 issues.push("Blueprint name is empty".into());
279 }
280 if bp.entities.is_empty() {
281 issues.push("Blueprint has no entities".into());
282 }
283 if bp.files.is_empty() {
284 issues.push("Blueprint has no files".into());
285 }
286
287 for entity in &bp.entities {
288 if entity.fields.is_empty() {
289 issues.push(format!("Entity '{}' has no fields", entity.name));
290 }
291 }
292
293 for file in &bp.files {
294 if file.path.is_empty() {
295 issues.push("File has empty path".into());
296 }
297 }
298
299 Ok(issues)
300 }
301
302 pub fn blueprint_summary(&self, bp_id: &BlueprintId) -> ForgeResult<BlueprintSummary> {
303 let bp = self.engine.store.load(bp_id)?;
304 Ok(BlueprintSummary {
305 id: bp.id,
306 name: bp.name.clone(),
307 domain: bp.domain,
308 status: bp.status,
309 entity_count: bp.entity_count(),
310 file_count: bp.file_count(),
311 dependency_count: bp.dependency_count(),
312 test_count: bp.test_count(),
313 type_count: bp.type_definitions.len(),
314 function_count: bp.function_blueprints.len(),
315 })
316 }
317}
318
319#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
320pub struct BlueprintSummary {
321 pub id: BlueprintId,
322 pub name: String,
323 pub domain: Domain,
324 pub status: BlueprintStatus,
325 pub entity_count: usize,
326 pub file_count: usize,
327 pub dependency_count: usize,
328 pub test_count: usize,
329 pub type_count: usize,
330 pub function_count: usize,
331}
332
333use crate::types::intent::Domain;
334
335#[cfg(test)]
336mod tests {
337 use super::*;
338 use crate::engine::ForgeEngine;
339 use crate::types::blueprint::*;
340 use crate::types::intent::*;
341
342 fn setup() -> (ForgeEngine, BlueprintId) {
343 let mut engine = ForgeEngine::new();
344 let id = engine
345 .create_blueprint("Test", "Test blueprint", Domain::Api)
346 .unwrap();
347 {
349 let mut w = engine.writer();
350 w.add_entity(&id, Entity::new("User", "A user entity"))
351 .unwrap();
352 w.add_entity(&id, Entity::new("Post", "A post entity"))
353 .unwrap();
354 w.add_file(&id, FileBlueprint::new("src/main.rs", FileType::Source))
355 .unwrap();
356 w.add_file(&id, FileBlueprint::new("src/models.rs", FileType::Source))
357 .unwrap();
358 w.add_file(&id, FileBlueprint::new("tests/test.rs", FileType::Test))
359 .unwrap();
360 w.add_dependency(&id, Dependency::new("serde", "1.0"))
361 .unwrap();
362 w.add_dependency(&id, Dependency::new("tokio", "1.35"))
363 .unwrap();
364 w.add_test_case(
365 &id,
366 TestCase::new("test_create", TestType::Unit, "User::create"),
367 )
368 .unwrap();
369 w.add_test_case(&id, TestCase::new("test_e2e", TestType::Integration, "api"))
370 .unwrap();
371 }
372 (engine, id)
373 }
374
375 #[test]
376 fn test_get_blueprint() {
377 let (engine, id) = setup();
378 let r = engine.reader();
379 let bp = r.get_blueprint(&id).unwrap();
380 assert_eq!(bp.name, "Test");
381 }
382
383 #[test]
384 fn test_list_blueprints() {
385 let (engine, _) = setup();
386 let r = engine.reader();
387 assert_eq!(r.list_blueprints().len(), 1);
388 }
389
390 #[test]
391 fn test_search_blueprints() {
392 let (engine, _) = setup();
393 let r = engine.reader();
394 assert_eq!(r.search_blueprints("test").len(), 1);
395 assert_eq!(r.search_blueprints("nonexistent").len(), 0);
396 }
397
398 #[test]
399 fn test_blueprint_count() {
400 let (engine, _) = setup();
401 let r = engine.reader();
402 assert_eq!(r.blueprint_count(), 1);
403 }
404
405 #[test]
406 fn test_get_entity() {
407 let (engine, id) = setup();
408 let r = engine.reader();
409 let entities = r.list_entities(&id).unwrap();
410 let eid = entities[0].id;
411 let entity = r.get_entity(&id, &eid).unwrap();
412 assert!(!entity.name.is_empty());
413 }
414
415 #[test]
416 fn test_get_entity_by_name() {
417 let (engine, id) = setup();
418 let r = engine.reader();
419 let entity = r.get_entity_by_name(&id, "User").unwrap();
420 assert_eq!(entity.name, "User");
421 }
422
423 #[test]
424 fn test_search_entities() {
425 let (engine, id) = setup();
426 let r = engine.reader();
427 let results = r.search_entities(&id, "user").unwrap();
428 assert_eq!(results.len(), 1);
429 }
430
431 #[test]
432 fn test_entity_count() {
433 let (engine, id) = setup();
434 let r = engine.reader();
435 assert_eq!(r.entity_count(&id).unwrap(), 2);
436 }
437
438 #[test]
439 fn test_list_files() {
440 let (engine, id) = setup();
441 let r = engine.reader();
442 assert_eq!(r.list_files(&id).unwrap().len(), 3);
443 }
444
445 #[test]
446 fn test_list_files_by_type() {
447 let (engine, id) = setup();
448 let r = engine.reader();
449 assert_eq!(
450 r.list_files_by_type(&id, FileType::Source).unwrap().len(),
451 2
452 );
453 assert_eq!(r.list_files_by_type(&id, FileType::Test).unwrap().len(), 1);
454 }
455
456 #[test]
457 fn test_file_count() {
458 let (engine, id) = setup();
459 let r = engine.reader();
460 assert_eq!(r.file_count(&id).unwrap(), 3);
461 }
462
463 #[test]
464 fn test_get_file_by_path() {
465 let (engine, id) = setup();
466 let r = engine.reader();
467 let file = r.get_file_by_path(&id, "src/main.rs").unwrap();
468 assert_eq!(file.path, "src/main.rs");
469 }
470
471 #[test]
472 fn test_list_dependencies() {
473 let (engine, id) = setup();
474 let r = engine.reader();
475 assert_eq!(r.list_dependencies(&id).unwrap().len(), 2);
476 }
477
478 #[test]
479 fn test_get_dependency_by_name() {
480 let (engine, id) = setup();
481 let r = engine.reader();
482 let dep = r.get_dependency_by_name(&id, "serde").unwrap();
483 assert_eq!(dep.version, "1.0");
484 }
485
486 #[test]
487 fn test_dependency_count() {
488 let (engine, id) = setup();
489 let r = engine.reader();
490 assert_eq!(r.dependency_count(&id).unwrap(), 2);
491 }
492
493 #[test]
494 fn test_list_test_cases() {
495 let (engine, id) = setup();
496 let r = engine.reader();
497 assert_eq!(r.list_test_cases(&id).unwrap().len(), 2);
498 }
499
500 #[test]
501 fn test_list_tests_by_type() {
502 let (engine, id) = setup();
503 let r = engine.reader();
504 assert_eq!(r.list_tests_by_type(&id, TestType::Unit).unwrap().len(), 1);
505 assert_eq!(
506 r.list_tests_by_type(&id, TestType::Integration)
507 .unwrap()
508 .len(),
509 1
510 );
511 }
512
513 #[test]
514 fn test_validate_blueprint() {
515 let (engine, id) = setup();
516 let r = engine.reader();
517 let issues = r.validate_blueprint(&id).unwrap();
518 assert!(!issues.is_empty());
519 }
520
521 #[test]
522 fn test_blueprint_summary() {
523 let (engine, id) = setup();
524 let r = engine.reader();
525 let summary = r.blueprint_summary(&id).unwrap();
526 assert_eq!(summary.name, "Test");
527 assert_eq!(summary.entity_count, 2);
528 assert_eq!(summary.file_count, 3);
529 assert_eq!(summary.dependency_count, 2);
530 assert_eq!(summary.test_count, 2);
531 }
532
533 #[test]
534 fn test_blueprint_not_found() {
535 let (engine, _) = setup();
536 let fake_id = BlueprintId::new();
537 let r = engine.reader();
538 assert!(r.get_blueprint(&fake_id).is_err());
539 }
540
541 #[test]
542 fn test_entity_not_found() {
543 let (engine, id) = setup();
544 let r = engine.reader();
545 assert!(r.get_entity_by_name(&id, "Nonexistent").is_err());
546 }
547
548 #[test]
549 fn test_list_aggregate_roots() {
550 let (engine, id) = setup();
551 let r = engine.reader();
552 assert_eq!(r.list_aggregate_roots(&id).unwrap().len(), 0);
553 }
554
555 #[test]
556 fn test_get_generation_order() {
557 let (engine, id) = setup();
558 let r = engine.reader();
559 assert!(r.get_generation_order(&id).unwrap().is_empty());
560 }
561
562 #[test]
563 fn test_list_wiring() {
564 let (engine, id) = setup();
565 let r = engine.reader();
566 assert!(r.list_wiring(&id).unwrap().is_empty());
567 }
568
569 #[test]
570 fn test_list_data_flows() {
571 let (engine, id) = setup();
572 let r = engine.reader();
573 assert!(r.list_data_flows(&id).unwrap().is_empty());
574 }
575
576 #[test]
577 fn test_list_import_graph() {
578 let (engine, id) = setup();
579 let r = engine.reader();
580 assert!(r.list_import_graph(&id).unwrap().is_empty());
581 }
582}