aiecs 1.5.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aiecs/__init__.py +72 -0
- aiecs/__main__.py +41 -0
- aiecs/aiecs_client.py +469 -0
- aiecs/application/__init__.py +10 -0
- aiecs/application/executors/__init__.py +10 -0
- aiecs/application/executors/operation_executor.py +363 -0
- aiecs/application/knowledge_graph/__init__.py +7 -0
- aiecs/application/knowledge_graph/builder/__init__.py +37 -0
- aiecs/application/knowledge_graph/builder/document_builder.py +375 -0
- aiecs/application/knowledge_graph/builder/graph_builder.py +356 -0
- aiecs/application/knowledge_graph/builder/schema_mapping.py +531 -0
- aiecs/application/knowledge_graph/builder/structured_pipeline.py +443 -0
- aiecs/application/knowledge_graph/builder/text_chunker.py +319 -0
- aiecs/application/knowledge_graph/extractors/__init__.py +27 -0
- aiecs/application/knowledge_graph/extractors/base.py +100 -0
- aiecs/application/knowledge_graph/extractors/llm_entity_extractor.py +327 -0
- aiecs/application/knowledge_graph/extractors/llm_relation_extractor.py +349 -0
- aiecs/application/knowledge_graph/extractors/ner_entity_extractor.py +244 -0
- aiecs/application/knowledge_graph/fusion/__init__.py +23 -0
- aiecs/application/knowledge_graph/fusion/entity_deduplicator.py +387 -0
- aiecs/application/knowledge_graph/fusion/entity_linker.py +343 -0
- aiecs/application/knowledge_graph/fusion/knowledge_fusion.py +580 -0
- aiecs/application/knowledge_graph/fusion/relation_deduplicator.py +189 -0
- aiecs/application/knowledge_graph/pattern_matching/__init__.py +21 -0
- aiecs/application/knowledge_graph/pattern_matching/pattern_matcher.py +344 -0
- aiecs/application/knowledge_graph/pattern_matching/query_executor.py +378 -0
- aiecs/application/knowledge_graph/profiling/__init__.py +12 -0
- aiecs/application/knowledge_graph/profiling/query_plan_visualizer.py +199 -0
- aiecs/application/knowledge_graph/profiling/query_profiler.py +223 -0
- aiecs/application/knowledge_graph/reasoning/__init__.py +27 -0
- aiecs/application/knowledge_graph/reasoning/evidence_synthesis.py +347 -0
- aiecs/application/knowledge_graph/reasoning/inference_engine.py +504 -0
- aiecs/application/knowledge_graph/reasoning/logic_form_parser.py +167 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/__init__.py +79 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_builder.py +513 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_nodes.py +630 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_validator.py +654 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/error_handler.py +477 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/parser.py +390 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/query_context.py +217 -0
- aiecs/application/knowledge_graph/reasoning/logic_query_integration.py +169 -0
- aiecs/application/knowledge_graph/reasoning/query_planner.py +872 -0
- aiecs/application/knowledge_graph/reasoning/reasoning_engine.py +554 -0
- aiecs/application/knowledge_graph/retrieval/__init__.py +19 -0
- aiecs/application/knowledge_graph/retrieval/retrieval_strategies.py +596 -0
- aiecs/application/knowledge_graph/search/__init__.py +59 -0
- aiecs/application/knowledge_graph/search/hybrid_search.py +423 -0
- aiecs/application/knowledge_graph/search/reranker.py +295 -0
- aiecs/application/knowledge_graph/search/reranker_strategies.py +553 -0
- aiecs/application/knowledge_graph/search/text_similarity.py +398 -0
- aiecs/application/knowledge_graph/traversal/__init__.py +15 -0
- aiecs/application/knowledge_graph/traversal/enhanced_traversal.py +329 -0
- aiecs/application/knowledge_graph/traversal/path_scorer.py +269 -0
- aiecs/application/knowledge_graph/validators/__init__.py +13 -0
- aiecs/application/knowledge_graph/validators/relation_validator.py +189 -0
- aiecs/application/knowledge_graph/visualization/__init__.py +11 -0
- aiecs/application/knowledge_graph/visualization/graph_visualizer.py +321 -0
- aiecs/common/__init__.py +9 -0
- aiecs/common/knowledge_graph/__init__.py +17 -0
- aiecs/common/knowledge_graph/runnable.py +484 -0
- aiecs/config/__init__.py +16 -0
- aiecs/config/config.py +498 -0
- aiecs/config/graph_config.py +137 -0
- aiecs/config/registry.py +23 -0
- aiecs/core/__init__.py +46 -0
- aiecs/core/interface/__init__.py +34 -0
- aiecs/core/interface/execution_interface.py +152 -0
- aiecs/core/interface/storage_interface.py +171 -0
- aiecs/domain/__init__.py +289 -0
- aiecs/domain/agent/__init__.py +189 -0
- aiecs/domain/agent/base_agent.py +697 -0
- aiecs/domain/agent/exceptions.py +103 -0
- aiecs/domain/agent/graph_aware_mixin.py +559 -0
- aiecs/domain/agent/hybrid_agent.py +490 -0
- aiecs/domain/agent/integration/__init__.py +26 -0
- aiecs/domain/agent/integration/context_compressor.py +222 -0
- aiecs/domain/agent/integration/context_engine_adapter.py +252 -0
- aiecs/domain/agent/integration/retry_policy.py +219 -0
- aiecs/domain/agent/integration/role_config.py +213 -0
- aiecs/domain/agent/knowledge_aware_agent.py +646 -0
- aiecs/domain/agent/lifecycle.py +296 -0
- aiecs/domain/agent/llm_agent.py +300 -0
- aiecs/domain/agent/memory/__init__.py +12 -0
- aiecs/domain/agent/memory/conversation.py +197 -0
- aiecs/domain/agent/migration/__init__.py +14 -0
- aiecs/domain/agent/migration/conversion.py +160 -0
- aiecs/domain/agent/migration/legacy_wrapper.py +90 -0
- aiecs/domain/agent/models.py +317 -0
- aiecs/domain/agent/observability.py +407 -0
- aiecs/domain/agent/persistence.py +289 -0
- aiecs/domain/agent/prompts/__init__.py +29 -0
- aiecs/domain/agent/prompts/builder.py +161 -0
- aiecs/domain/agent/prompts/formatters.py +189 -0
- aiecs/domain/agent/prompts/template.py +255 -0
- aiecs/domain/agent/registry.py +260 -0
- aiecs/domain/agent/tool_agent.py +257 -0
- aiecs/domain/agent/tools/__init__.py +12 -0
- aiecs/domain/agent/tools/schema_generator.py +221 -0
- aiecs/domain/community/__init__.py +155 -0
- aiecs/domain/community/agent_adapter.py +477 -0
- aiecs/domain/community/analytics.py +481 -0
- aiecs/domain/community/collaborative_workflow.py +642 -0
- aiecs/domain/community/communication_hub.py +645 -0
- aiecs/domain/community/community_builder.py +320 -0
- aiecs/domain/community/community_integration.py +800 -0
- aiecs/domain/community/community_manager.py +813 -0
- aiecs/domain/community/decision_engine.py +879 -0
- aiecs/domain/community/exceptions.py +225 -0
- aiecs/domain/community/models/__init__.py +33 -0
- aiecs/domain/community/models/community_models.py +268 -0
- aiecs/domain/community/resource_manager.py +457 -0
- aiecs/domain/community/shared_context_manager.py +603 -0
- aiecs/domain/context/__init__.py +58 -0
- aiecs/domain/context/context_engine.py +989 -0
- aiecs/domain/context/conversation_models.py +354 -0
- aiecs/domain/context/graph_memory.py +467 -0
- aiecs/domain/execution/__init__.py +12 -0
- aiecs/domain/execution/model.py +57 -0
- aiecs/domain/knowledge_graph/__init__.py +19 -0
- aiecs/domain/knowledge_graph/models/__init__.py +52 -0
- aiecs/domain/knowledge_graph/models/entity.py +130 -0
- aiecs/domain/knowledge_graph/models/evidence.py +194 -0
- aiecs/domain/knowledge_graph/models/inference_rule.py +186 -0
- aiecs/domain/knowledge_graph/models/path.py +179 -0
- aiecs/domain/knowledge_graph/models/path_pattern.py +173 -0
- aiecs/domain/knowledge_graph/models/query.py +272 -0
- aiecs/domain/knowledge_graph/models/query_plan.py +187 -0
- aiecs/domain/knowledge_graph/models/relation.py +136 -0
- aiecs/domain/knowledge_graph/schema/__init__.py +23 -0
- aiecs/domain/knowledge_graph/schema/entity_type.py +135 -0
- aiecs/domain/knowledge_graph/schema/graph_schema.py +271 -0
- aiecs/domain/knowledge_graph/schema/property_schema.py +155 -0
- aiecs/domain/knowledge_graph/schema/relation_type.py +171 -0
- aiecs/domain/knowledge_graph/schema/schema_manager.py +496 -0
- aiecs/domain/knowledge_graph/schema/type_enums.py +205 -0
- aiecs/domain/task/__init__.py +13 -0
- aiecs/domain/task/dsl_processor.py +613 -0
- aiecs/domain/task/model.py +62 -0
- aiecs/domain/task/task_context.py +268 -0
- aiecs/infrastructure/__init__.py +24 -0
- aiecs/infrastructure/graph_storage/__init__.py +11 -0
- aiecs/infrastructure/graph_storage/base.py +601 -0
- aiecs/infrastructure/graph_storage/batch_operations.py +449 -0
- aiecs/infrastructure/graph_storage/cache.py +429 -0
- aiecs/infrastructure/graph_storage/distributed.py +226 -0
- aiecs/infrastructure/graph_storage/error_handling.py +390 -0
- aiecs/infrastructure/graph_storage/graceful_degradation.py +306 -0
- aiecs/infrastructure/graph_storage/health_checks.py +378 -0
- aiecs/infrastructure/graph_storage/in_memory.py +514 -0
- aiecs/infrastructure/graph_storage/index_optimization.py +483 -0
- aiecs/infrastructure/graph_storage/lazy_loading.py +410 -0
- aiecs/infrastructure/graph_storage/metrics.py +357 -0
- aiecs/infrastructure/graph_storage/migration.py +413 -0
- aiecs/infrastructure/graph_storage/pagination.py +471 -0
- aiecs/infrastructure/graph_storage/performance_monitoring.py +466 -0
- aiecs/infrastructure/graph_storage/postgres.py +871 -0
- aiecs/infrastructure/graph_storage/query_optimizer.py +635 -0
- aiecs/infrastructure/graph_storage/schema_cache.py +290 -0
- aiecs/infrastructure/graph_storage/sqlite.py +623 -0
- aiecs/infrastructure/graph_storage/streaming.py +495 -0
- aiecs/infrastructure/messaging/__init__.py +13 -0
- aiecs/infrastructure/messaging/celery_task_manager.py +383 -0
- aiecs/infrastructure/messaging/websocket_manager.py +298 -0
- aiecs/infrastructure/monitoring/__init__.py +34 -0
- aiecs/infrastructure/monitoring/executor_metrics.py +174 -0
- aiecs/infrastructure/monitoring/global_metrics_manager.py +213 -0
- aiecs/infrastructure/monitoring/structured_logger.py +48 -0
- aiecs/infrastructure/monitoring/tracing_manager.py +410 -0
- aiecs/infrastructure/persistence/__init__.py +24 -0
- aiecs/infrastructure/persistence/context_engine_client.py +187 -0
- aiecs/infrastructure/persistence/database_manager.py +333 -0
- aiecs/infrastructure/persistence/file_storage.py +754 -0
- aiecs/infrastructure/persistence/redis_client.py +220 -0
- aiecs/llm/__init__.py +86 -0
- aiecs/llm/callbacks/__init__.py +11 -0
- aiecs/llm/callbacks/custom_callbacks.py +264 -0
- aiecs/llm/client_factory.py +420 -0
- aiecs/llm/clients/__init__.py +33 -0
- aiecs/llm/clients/base_client.py +193 -0
- aiecs/llm/clients/googleai_client.py +181 -0
- aiecs/llm/clients/openai_client.py +131 -0
- aiecs/llm/clients/vertex_client.py +437 -0
- aiecs/llm/clients/xai_client.py +184 -0
- aiecs/llm/config/__init__.py +51 -0
- aiecs/llm/config/config_loader.py +275 -0
- aiecs/llm/config/config_validator.py +236 -0
- aiecs/llm/config/model_config.py +151 -0
- aiecs/llm/utils/__init__.py +10 -0
- aiecs/llm/utils/validate_config.py +91 -0
- aiecs/main.py +363 -0
- aiecs/scripts/__init__.py +3 -0
- aiecs/scripts/aid/VERSION_MANAGEMENT.md +97 -0
- aiecs/scripts/aid/__init__.py +19 -0
- aiecs/scripts/aid/version_manager.py +215 -0
- aiecs/scripts/dependance_check/DEPENDENCY_SYSTEM_SUMMARY.md +242 -0
- aiecs/scripts/dependance_check/README_DEPENDENCY_CHECKER.md +310 -0
- aiecs/scripts/dependance_check/__init__.py +17 -0
- aiecs/scripts/dependance_check/dependency_checker.py +938 -0
- aiecs/scripts/dependance_check/dependency_fixer.py +391 -0
- aiecs/scripts/dependance_check/download_nlp_data.py +396 -0
- aiecs/scripts/dependance_check/quick_dependency_check.py +270 -0
- aiecs/scripts/dependance_check/setup_nlp_data.sh +217 -0
- aiecs/scripts/dependance_patch/__init__.py +7 -0
- aiecs/scripts/dependance_patch/fix_weasel/README_WEASEL_PATCH.md +126 -0
- aiecs/scripts/dependance_patch/fix_weasel/__init__.py +11 -0
- aiecs/scripts/dependance_patch/fix_weasel/fix_weasel_validator.py +128 -0
- aiecs/scripts/dependance_patch/fix_weasel/fix_weasel_validator.sh +82 -0
- aiecs/scripts/dependance_patch/fix_weasel/patch_weasel_library.sh +188 -0
- aiecs/scripts/dependance_patch/fix_weasel/run_weasel_patch.sh +41 -0
- aiecs/scripts/tools_develop/README.md +449 -0
- aiecs/scripts/tools_develop/TOOL_AUTO_DISCOVERY.md +234 -0
- aiecs/scripts/tools_develop/__init__.py +21 -0
- aiecs/scripts/tools_develop/check_type_annotations.py +259 -0
- aiecs/scripts/tools_develop/validate_tool_schemas.py +422 -0
- aiecs/scripts/tools_develop/verify_tools.py +356 -0
- aiecs/tasks/__init__.py +1 -0
- aiecs/tasks/worker.py +172 -0
- aiecs/tools/__init__.py +299 -0
- aiecs/tools/apisource/__init__.py +99 -0
- aiecs/tools/apisource/intelligence/__init__.py +19 -0
- aiecs/tools/apisource/intelligence/data_fusion.py +381 -0
- aiecs/tools/apisource/intelligence/query_analyzer.py +413 -0
- aiecs/tools/apisource/intelligence/search_enhancer.py +388 -0
- aiecs/tools/apisource/monitoring/__init__.py +9 -0
- aiecs/tools/apisource/monitoring/metrics.py +303 -0
- aiecs/tools/apisource/providers/__init__.py +115 -0
- aiecs/tools/apisource/providers/base.py +664 -0
- aiecs/tools/apisource/providers/census.py +401 -0
- aiecs/tools/apisource/providers/fred.py +564 -0
- aiecs/tools/apisource/providers/newsapi.py +412 -0
- aiecs/tools/apisource/providers/worldbank.py +357 -0
- aiecs/tools/apisource/reliability/__init__.py +12 -0
- aiecs/tools/apisource/reliability/error_handler.py +375 -0
- aiecs/tools/apisource/reliability/fallback_strategy.py +391 -0
- aiecs/tools/apisource/tool.py +850 -0
- aiecs/tools/apisource/utils/__init__.py +9 -0
- aiecs/tools/apisource/utils/validators.py +338 -0
- aiecs/tools/base_tool.py +201 -0
- aiecs/tools/docs/__init__.py +121 -0
- aiecs/tools/docs/ai_document_orchestrator.py +599 -0
- aiecs/tools/docs/ai_document_writer_orchestrator.py +2403 -0
- aiecs/tools/docs/content_insertion_tool.py +1333 -0
- aiecs/tools/docs/document_creator_tool.py +1317 -0
- aiecs/tools/docs/document_layout_tool.py +1166 -0
- aiecs/tools/docs/document_parser_tool.py +994 -0
- aiecs/tools/docs/document_writer_tool.py +1818 -0
- aiecs/tools/knowledge_graph/__init__.py +17 -0
- aiecs/tools/knowledge_graph/graph_reasoning_tool.py +734 -0
- aiecs/tools/knowledge_graph/graph_search_tool.py +923 -0
- aiecs/tools/knowledge_graph/kg_builder_tool.py +476 -0
- aiecs/tools/langchain_adapter.py +542 -0
- aiecs/tools/schema_generator.py +275 -0
- aiecs/tools/search_tool/__init__.py +100 -0
- aiecs/tools/search_tool/analyzers.py +589 -0
- aiecs/tools/search_tool/cache.py +260 -0
- aiecs/tools/search_tool/constants.py +128 -0
- aiecs/tools/search_tool/context.py +216 -0
- aiecs/tools/search_tool/core.py +749 -0
- aiecs/tools/search_tool/deduplicator.py +123 -0
- aiecs/tools/search_tool/error_handler.py +271 -0
- aiecs/tools/search_tool/metrics.py +371 -0
- aiecs/tools/search_tool/rate_limiter.py +178 -0
- aiecs/tools/search_tool/schemas.py +277 -0
- aiecs/tools/statistics/__init__.py +80 -0
- aiecs/tools/statistics/ai_data_analysis_orchestrator.py +643 -0
- aiecs/tools/statistics/ai_insight_generator_tool.py +505 -0
- aiecs/tools/statistics/ai_report_orchestrator_tool.py +694 -0
- aiecs/tools/statistics/data_loader_tool.py +564 -0
- aiecs/tools/statistics/data_profiler_tool.py +658 -0
- aiecs/tools/statistics/data_transformer_tool.py +573 -0
- aiecs/tools/statistics/data_visualizer_tool.py +495 -0
- aiecs/tools/statistics/model_trainer_tool.py +487 -0
- aiecs/tools/statistics/statistical_analyzer_tool.py +459 -0
- aiecs/tools/task_tools/__init__.py +86 -0
- aiecs/tools/task_tools/chart_tool.py +732 -0
- aiecs/tools/task_tools/classfire_tool.py +922 -0
- aiecs/tools/task_tools/image_tool.py +447 -0
- aiecs/tools/task_tools/office_tool.py +684 -0
- aiecs/tools/task_tools/pandas_tool.py +635 -0
- aiecs/tools/task_tools/report_tool.py +635 -0
- aiecs/tools/task_tools/research_tool.py +392 -0
- aiecs/tools/task_tools/scraper_tool.py +715 -0
- aiecs/tools/task_tools/stats_tool.py +688 -0
- aiecs/tools/temp_file_manager.py +130 -0
- aiecs/tools/tool_executor/__init__.py +37 -0
- aiecs/tools/tool_executor/tool_executor.py +881 -0
- aiecs/utils/LLM_output_structor.py +445 -0
- aiecs/utils/__init__.py +34 -0
- aiecs/utils/base_callback.py +47 -0
- aiecs/utils/cache_provider.py +695 -0
- aiecs/utils/execution_utils.py +184 -0
- aiecs/utils/logging.py +1 -0
- aiecs/utils/prompt_loader.py +14 -0
- aiecs/utils/token_usage_repository.py +323 -0
- aiecs/ws/__init__.py +0 -0
- aiecs/ws/socket_server.py +52 -0
- aiecs-1.5.1.dist-info/METADATA +608 -0
- aiecs-1.5.1.dist-info/RECORD +302 -0
- aiecs-1.5.1.dist-info/WHEEL +5 -0
- aiecs-1.5.1.dist-info/entry_points.txt +10 -0
- aiecs-1.5.1.dist-info/licenses/LICENSE +225 -0
- aiecs-1.5.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,654 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AST Validator for Logic Query Parser
|
|
3
|
+
|
|
4
|
+
This module provides comprehensive validation of AST nodes against the knowledge graph schema.
|
|
5
|
+
It validates entity types, properties, relation types, property types, and variable references.
|
|
6
|
+
|
|
7
|
+
Design Principles:
|
|
8
|
+
1. Schema-aware validation (entity types, properties, relations)
|
|
9
|
+
2. Error accumulation (collect all errors, don't stop at first)
|
|
10
|
+
3. Helpful error messages with suggestions
|
|
11
|
+
4. Type checking (property values match expected types)
|
|
12
|
+
|
|
13
|
+
Phase: 2.4 - Logic Query Parser
|
|
14
|
+
Task: 3.1 - Implement AST Validator
|
|
15
|
+
Version: 1.0
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from typing import Any, List, Optional, Set
|
|
19
|
+
from .ast_nodes import (
|
|
20
|
+
ValidationError,
|
|
21
|
+
ASTNode,
|
|
22
|
+
QueryNode,
|
|
23
|
+
FindNode,
|
|
24
|
+
TraversalNode,
|
|
25
|
+
PropertyFilterNode,
|
|
26
|
+
BooleanFilterNode,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ASTValidator:
|
|
31
|
+
"""
|
|
32
|
+
AST Validator with schema integration
|
|
33
|
+
|
|
34
|
+
This class provides comprehensive validation of AST nodes against the
|
|
35
|
+
knowledge graph schema. It validates:
|
|
36
|
+
- Entity types exist in schema
|
|
37
|
+
- Properties exist in entity schema
|
|
38
|
+
- Relation types exist in schema
|
|
39
|
+
- Property values match expected types
|
|
40
|
+
- Relation endpoints match entity types
|
|
41
|
+
- Variable references are defined before use
|
|
42
|
+
|
|
43
|
+
The validator accumulates all errors instead of stopping at the first error.
|
|
44
|
+
|
|
45
|
+
Example:
|
|
46
|
+
```python
|
|
47
|
+
from aiecs.domain.knowledge_graph.schema import SchemaManager
|
|
48
|
+
|
|
49
|
+
schema = SchemaManager.load("schema.json")
|
|
50
|
+
validator = ASTValidator(schema)
|
|
51
|
+
|
|
52
|
+
errors = validator.validate(ast_node)
|
|
53
|
+
if errors:
|
|
54
|
+
for error in errors:
|
|
55
|
+
print(f"Line {error.line}: {error.message}")
|
|
56
|
+
```
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(self, schema: Any):
|
|
60
|
+
"""
|
|
61
|
+
Initialize AST validator
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
schema: SchemaManager instance for validation
|
|
65
|
+
"""
|
|
66
|
+
self.schema = schema
|
|
67
|
+
self.current_entity_type: Optional[str] = None
|
|
68
|
+
self.defined_variables: Set[str] = set()
|
|
69
|
+
|
|
70
|
+
# ========================================================================
|
|
71
|
+
# Main Validation Entry Point
|
|
72
|
+
# ========================================================================
|
|
73
|
+
|
|
74
|
+
def validate(self, node: ASTNode) -> List[ValidationError]:
|
|
75
|
+
"""
|
|
76
|
+
Validate an AST node and all its children
|
|
77
|
+
|
|
78
|
+
This is the main entry point for validation. It accumulates all
|
|
79
|
+
errors from the node and its children.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
node: AST node to validate
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
List of validation errors (empty if valid)
|
|
86
|
+
"""
|
|
87
|
+
errors = []
|
|
88
|
+
|
|
89
|
+
# Dispatch to specific validation method based on node type
|
|
90
|
+
if isinstance(node, QueryNode):
|
|
91
|
+
errors.extend(self.validate_query_node(node))
|
|
92
|
+
elif isinstance(node, FindNode):
|
|
93
|
+
errors.extend(self.validate_find_node(node))
|
|
94
|
+
elif isinstance(node, TraversalNode):
|
|
95
|
+
errors.extend(self.validate_traversal_node(node))
|
|
96
|
+
elif isinstance(node, PropertyFilterNode):
|
|
97
|
+
errors.extend(self.validate_property_filter_node(node))
|
|
98
|
+
elif isinstance(node, BooleanFilterNode):
|
|
99
|
+
errors.extend(self.validate_boolean_filter_node(node))
|
|
100
|
+
else:
|
|
101
|
+
# Fallback to node's own validate method
|
|
102
|
+
errors.extend(node.validate(self.schema))
|
|
103
|
+
|
|
104
|
+
return errors
|
|
105
|
+
|
|
106
|
+
# ========================================================================
|
|
107
|
+
# Node-Specific Validation Methods
|
|
108
|
+
# ========================================================================
|
|
109
|
+
|
|
110
|
+
def validate_query_node(self, node: QueryNode) -> List[ValidationError]:
|
|
111
|
+
"""
|
|
112
|
+
Validate QueryNode
|
|
113
|
+
|
|
114
|
+
Validates:
|
|
115
|
+
- FindNode
|
|
116
|
+
- All TraversalNodes
|
|
117
|
+
- Entity type consistency across traversals
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
node: QueryNode to validate
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
List of validation errors
|
|
124
|
+
"""
|
|
125
|
+
errors = []
|
|
126
|
+
|
|
127
|
+
# Validate FindNode
|
|
128
|
+
errors.extend(self.validate_find_node(node.find))
|
|
129
|
+
|
|
130
|
+
# Track current entity type for traversal validation
|
|
131
|
+
self.current_entity_type = node.find.entity_type
|
|
132
|
+
|
|
133
|
+
# Validate all traversals
|
|
134
|
+
for traversal in node.traversals:
|
|
135
|
+
errors.extend(self.validate_traversal_node(traversal))
|
|
136
|
+
|
|
137
|
+
return errors
|
|
138
|
+
|
|
139
|
+
def validate_find_node(self, node: FindNode) -> List[ValidationError]:
|
|
140
|
+
"""
|
|
141
|
+
Validate FindNode
|
|
142
|
+
|
|
143
|
+
Validates:
|
|
144
|
+
- Entity type exists in schema
|
|
145
|
+
- All filters reference valid properties
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
node: FindNode to validate
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
List of validation errors
|
|
152
|
+
"""
|
|
153
|
+
errors = []
|
|
154
|
+
|
|
155
|
+
# Validate entity type exists
|
|
156
|
+
errors.extend(self.validate_entity_type(node.entity_type, node.line, node.column))
|
|
157
|
+
|
|
158
|
+
# Set current entity type for property validation
|
|
159
|
+
self.current_entity_type = node.entity_type
|
|
160
|
+
|
|
161
|
+
# Validate all filters
|
|
162
|
+
for filter_node in node.filters:
|
|
163
|
+
errors.extend(self.validate(filter_node))
|
|
164
|
+
|
|
165
|
+
return errors
|
|
166
|
+
|
|
167
|
+
def validate_traversal_node(self, node: TraversalNode) -> List[ValidationError]:
|
|
168
|
+
"""
|
|
169
|
+
Validate TraversalNode
|
|
170
|
+
|
|
171
|
+
Validates:
|
|
172
|
+
- Relation type exists in schema
|
|
173
|
+
- Relation endpoints match current entity type
|
|
174
|
+
- Direction is valid
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
node: TraversalNode to validate
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
List of validation errors
|
|
181
|
+
"""
|
|
182
|
+
errors = []
|
|
183
|
+
|
|
184
|
+
# Validate relation type exists
|
|
185
|
+
errors.extend(self.validate_relation_type(node.relation_type, node.line, node.column))
|
|
186
|
+
|
|
187
|
+
# Validate relation endpoints if we have a current entity type
|
|
188
|
+
if self.current_entity_type:
|
|
189
|
+
errors.extend(
|
|
190
|
+
self.validate_relation_endpoints(
|
|
191
|
+
node.relation_type,
|
|
192
|
+
self.current_entity_type,
|
|
193
|
+
node.direction or "outgoing",
|
|
194
|
+
node.line,
|
|
195
|
+
node.column,
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Validate direction
|
|
200
|
+
if node.direction and node.direction not in ["incoming", "outgoing"]:
|
|
201
|
+
errors.append(
|
|
202
|
+
ValidationError(
|
|
203
|
+
line=node.line,
|
|
204
|
+
column=node.column,
|
|
205
|
+
message=f"Invalid direction '{node.direction}'. Must be 'incoming' or 'outgoing'",
|
|
206
|
+
suggestion="Use 'INCOMING' or 'OUTGOING'",
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
return errors
|
|
211
|
+
|
|
212
|
+
def validate_property_filter_node(self, node: PropertyFilterNode) -> List[ValidationError]:
|
|
213
|
+
"""
|
|
214
|
+
Validate PropertyFilterNode
|
|
215
|
+
|
|
216
|
+
Validates:
|
|
217
|
+
- Property exists in current entity type
|
|
218
|
+
- Property value type matches schema
|
|
219
|
+
- Operator is valid for property type
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
node: PropertyFilterNode to validate
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
List of validation errors
|
|
226
|
+
"""
|
|
227
|
+
errors = []
|
|
228
|
+
|
|
229
|
+
# Validate operator
|
|
230
|
+
valid_operators = ["==", "!=", ">", "<", ">=", "<=", "IN", "CONTAINS"]
|
|
231
|
+
if node.operator not in valid_operators:
|
|
232
|
+
errors.append(
|
|
233
|
+
ValidationError(
|
|
234
|
+
line=node.line,
|
|
235
|
+
column=node.column,
|
|
236
|
+
message=f"Invalid operator '{node.operator}'",
|
|
237
|
+
suggestion=f"Valid operators: {', '.join(valid_operators)}",
|
|
238
|
+
)
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# Validate property exists in current entity type
|
|
242
|
+
if self.current_entity_type:
|
|
243
|
+
errors.extend(
|
|
244
|
+
self.validate_property(
|
|
245
|
+
self.current_entity_type,
|
|
246
|
+
node.property_path,
|
|
247
|
+
node.line,
|
|
248
|
+
node.column,
|
|
249
|
+
)
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
# Validate property value type
|
|
253
|
+
errors.extend(
|
|
254
|
+
self.validate_property_value_type(
|
|
255
|
+
self.current_entity_type,
|
|
256
|
+
node.property_path,
|
|
257
|
+
node.value,
|
|
258
|
+
node.operator,
|
|
259
|
+
node.line,
|
|
260
|
+
node.column,
|
|
261
|
+
)
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
# Validate operator-specific constraints
|
|
265
|
+
if node.operator == "IN" and not isinstance(node.value, list):
|
|
266
|
+
errors.append(
|
|
267
|
+
ValidationError(
|
|
268
|
+
line=node.line,
|
|
269
|
+
column=node.column,
|
|
270
|
+
message=f"IN operator requires a list value, got {type(node.value).__name__}",
|
|
271
|
+
suggestion="Use a list like ['value1', 'value2']",
|
|
272
|
+
)
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
if node.operator == "CONTAINS" and not isinstance(node.value, str):
|
|
276
|
+
errors.append(
|
|
277
|
+
ValidationError(
|
|
278
|
+
line=node.line,
|
|
279
|
+
column=node.column,
|
|
280
|
+
message=f"CONTAINS operator requires a string value, got {type(node.value).__name__}",
|
|
281
|
+
suggestion="Use a string value",
|
|
282
|
+
)
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
return errors
|
|
286
|
+
|
|
287
|
+
def validate_boolean_filter_node(self, node: BooleanFilterNode) -> List[ValidationError]:
|
|
288
|
+
"""
|
|
289
|
+
Validate BooleanFilterNode
|
|
290
|
+
|
|
291
|
+
Validates:
|
|
292
|
+
- Operator is valid (AND, OR, NOT)
|
|
293
|
+
- Has at least one operand
|
|
294
|
+
- All operands are valid
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
node: BooleanFilterNode to validate
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
List of validation errors
|
|
301
|
+
"""
|
|
302
|
+
errors = []
|
|
303
|
+
|
|
304
|
+
# Validate operator
|
|
305
|
+
valid_operators = ["AND", "OR", "NOT"]
|
|
306
|
+
if node.operator not in valid_operators:
|
|
307
|
+
errors.append(
|
|
308
|
+
ValidationError(
|
|
309
|
+
line=node.line,
|
|
310
|
+
column=node.column,
|
|
311
|
+
message=f"Invalid boolean operator '{node.operator}'",
|
|
312
|
+
suggestion=f"Valid operators: {', '.join(valid_operators)}",
|
|
313
|
+
)
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
# Validate operand count
|
|
317
|
+
if not node.operands:
|
|
318
|
+
errors.append(
|
|
319
|
+
ValidationError(
|
|
320
|
+
line=node.line,
|
|
321
|
+
column=node.column,
|
|
322
|
+
message=f"Boolean operator '{node.operator}' requires at least one operand",
|
|
323
|
+
)
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
# Validate all operands
|
|
327
|
+
for operand in node.operands:
|
|
328
|
+
errors.extend(self.validate(operand))
|
|
329
|
+
|
|
330
|
+
return errors
|
|
331
|
+
|
|
332
|
+
# ========================================================================
|
|
333
|
+
# Helper Validation Methods
|
|
334
|
+
# ========================================================================
|
|
335
|
+
|
|
336
|
+
def validate_entity_type(
|
|
337
|
+
self, entity_type: str, line: int, column: int
|
|
338
|
+
) -> List[ValidationError]:
|
|
339
|
+
"""
|
|
340
|
+
Validate that an entity type exists in the schema
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
entity_type: Entity type name to validate
|
|
344
|
+
line: Line number for error reporting
|
|
345
|
+
column: Column number for error reporting
|
|
346
|
+
|
|
347
|
+
Returns:
|
|
348
|
+
List of validation errors
|
|
349
|
+
"""
|
|
350
|
+
errors = []
|
|
351
|
+
|
|
352
|
+
# Check if schema has the method
|
|
353
|
+
if not hasattr(self.schema, "get_entity_type"):
|
|
354
|
+
return errors
|
|
355
|
+
|
|
356
|
+
# Check if entity type exists
|
|
357
|
+
entity_schema = self.schema.get_entity_type(entity_type)
|
|
358
|
+
if entity_schema is None:
|
|
359
|
+
# Get available types for suggestion
|
|
360
|
+
available_types = []
|
|
361
|
+
if hasattr(self.schema, "list_entity_types"):
|
|
362
|
+
available_types = self.schema.list_entity_types()
|
|
363
|
+
|
|
364
|
+
suggestion = None
|
|
365
|
+
if available_types:
|
|
366
|
+
suggestion = f"Available entity types: {', '.join(available_types[:5])}"
|
|
367
|
+
if len(available_types) > 5:
|
|
368
|
+
suggestion += f" (and {len(available_types) - 5} more)"
|
|
369
|
+
|
|
370
|
+
errors.append(
|
|
371
|
+
ValidationError(
|
|
372
|
+
line=line,
|
|
373
|
+
column=column,
|
|
374
|
+
message=f"Entity type '{entity_type}' not found in schema",
|
|
375
|
+
suggestion=suggestion,
|
|
376
|
+
)
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
return errors
|
|
380
|
+
|
|
381
|
+
def validate_relation_type(
|
|
382
|
+
self, relation_type: str, line: int, column: int
|
|
383
|
+
) -> List[ValidationError]:
|
|
384
|
+
"""
|
|
385
|
+
Validate that a relation type exists in the schema
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
relation_type: Relation type name to validate
|
|
389
|
+
line: Line number for error reporting
|
|
390
|
+
column: Column number for error reporting
|
|
391
|
+
|
|
392
|
+
Returns:
|
|
393
|
+
List of validation errors
|
|
394
|
+
"""
|
|
395
|
+
errors = []
|
|
396
|
+
|
|
397
|
+
# Check if schema has the method
|
|
398
|
+
if not hasattr(self.schema, "get_relation_type"):
|
|
399
|
+
return errors
|
|
400
|
+
|
|
401
|
+
# Check if relation type exists
|
|
402
|
+
relation_schema = self.schema.get_relation_type(relation_type)
|
|
403
|
+
if relation_schema is None:
|
|
404
|
+
# Get available types for suggestion
|
|
405
|
+
available_types = []
|
|
406
|
+
if hasattr(self.schema, "list_relation_types"):
|
|
407
|
+
available_types = self.schema.list_relation_types()
|
|
408
|
+
|
|
409
|
+
suggestion = None
|
|
410
|
+
if available_types:
|
|
411
|
+
suggestion = f"Available relation types: {', '.join(available_types[:5])}"
|
|
412
|
+
if len(available_types) > 5:
|
|
413
|
+
suggestion += f" (and {len(available_types) - 5} more)"
|
|
414
|
+
|
|
415
|
+
errors.append(
|
|
416
|
+
ValidationError(
|
|
417
|
+
line=line,
|
|
418
|
+
column=column,
|
|
419
|
+
message=f"Relation type '{relation_type}' not found in schema",
|
|
420
|
+
suggestion=suggestion,
|
|
421
|
+
)
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
return errors
|
|
425
|
+
|
|
426
|
+
def validate_property(
|
|
427
|
+
self, entity_type: str, property_path: str, line: int, column: int
|
|
428
|
+
) -> List[ValidationError]:
|
|
429
|
+
"""
|
|
430
|
+
Validate that a property exists in an entity type
|
|
431
|
+
|
|
432
|
+
Args:
|
|
433
|
+
entity_type: Entity type name
|
|
434
|
+
property_path: Property path (may be nested like "address.city")
|
|
435
|
+
line: Line number for error reporting
|
|
436
|
+
column: Column number for error reporting
|
|
437
|
+
|
|
438
|
+
Returns:
|
|
439
|
+
List of validation errors
|
|
440
|
+
"""
|
|
441
|
+
errors = []
|
|
442
|
+
|
|
443
|
+
# Get entity type schema
|
|
444
|
+
if not hasattr(self.schema, "get_entity_type"):
|
|
445
|
+
return errors
|
|
446
|
+
|
|
447
|
+
entity_schema = self.schema.get_entity_type(entity_type)
|
|
448
|
+
if entity_schema is None:
|
|
449
|
+
return errors # Entity type error already reported
|
|
450
|
+
|
|
451
|
+
# Handle nested properties (e.g., "address.city")
|
|
452
|
+
property_parts = property_path.split(".")
|
|
453
|
+
current_property = property_parts[0]
|
|
454
|
+
|
|
455
|
+
# Check if property exists
|
|
456
|
+
if not hasattr(entity_schema, "get_property"):
|
|
457
|
+
return errors
|
|
458
|
+
|
|
459
|
+
property_schema = entity_schema.get_property(current_property)
|
|
460
|
+
if property_schema is None:
|
|
461
|
+
# Get available properties for suggestion
|
|
462
|
+
available_props = []
|
|
463
|
+
if hasattr(entity_schema, "properties"):
|
|
464
|
+
available_props = list(entity_schema.properties.keys())
|
|
465
|
+
|
|
466
|
+
suggestion = None
|
|
467
|
+
if available_props:
|
|
468
|
+
suggestion = (
|
|
469
|
+
f"Available properties for {entity_type}: {', '.join(available_props[:5])}"
|
|
470
|
+
)
|
|
471
|
+
if len(available_props) > 5:
|
|
472
|
+
suggestion += f" (and {len(available_props) - 5} more)"
|
|
473
|
+
|
|
474
|
+
errors.append(
|
|
475
|
+
ValidationError(
|
|
476
|
+
line=line,
|
|
477
|
+
column=column,
|
|
478
|
+
message=f"Property '{current_property}' not found in entity type '{entity_type}'",
|
|
479
|
+
suggestion=suggestion,
|
|
480
|
+
)
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
# TODO: Validate nested properties (requires nested schema support)
|
|
484
|
+
|
|
485
|
+
return errors
|
|
486
|
+
|
|
487
|
+
def validate_property_value_type(
|
|
488
|
+
self,
|
|
489
|
+
entity_type: str,
|
|
490
|
+
property_path: str,
|
|
491
|
+
value: Any,
|
|
492
|
+
operator: str,
|
|
493
|
+
line: int,
|
|
494
|
+
column: int,
|
|
495
|
+
) -> List[ValidationError]:
|
|
496
|
+
"""
|
|
497
|
+
Validate that a property value matches the expected type
|
|
498
|
+
|
|
499
|
+
Args:
|
|
500
|
+
entity_type: Entity type name
|
|
501
|
+
property_path: Property path
|
|
502
|
+
value: Value to validate
|
|
503
|
+
operator: Operator being used
|
|
504
|
+
line: Line number for error reporting
|
|
505
|
+
column: Column number for error reporting
|
|
506
|
+
|
|
507
|
+
Returns:
|
|
508
|
+
List of validation errors
|
|
509
|
+
"""
|
|
510
|
+
errors = []
|
|
511
|
+
|
|
512
|
+
# Get entity type schema
|
|
513
|
+
if not hasattr(self.schema, "get_entity_type"):
|
|
514
|
+
return errors
|
|
515
|
+
|
|
516
|
+
entity_schema = self.schema.get_entity_type(entity_type)
|
|
517
|
+
if entity_schema is None:
|
|
518
|
+
return errors
|
|
519
|
+
|
|
520
|
+
# Get property schema
|
|
521
|
+
property_parts = property_path.split(".")
|
|
522
|
+
current_property = property_parts[0]
|
|
523
|
+
|
|
524
|
+
if not hasattr(entity_schema, "get_property"):
|
|
525
|
+
return errors
|
|
526
|
+
|
|
527
|
+
property_schema = entity_schema.get_property(current_property)
|
|
528
|
+
if property_schema is None:
|
|
529
|
+
return errors # Property error already reported
|
|
530
|
+
|
|
531
|
+
# Get property type
|
|
532
|
+
if not hasattr(property_schema, "property_type"):
|
|
533
|
+
return errors
|
|
534
|
+
|
|
535
|
+
property_type = property_schema.property_type
|
|
536
|
+
|
|
537
|
+
# Map property types to Python types
|
|
538
|
+
type_map = {
|
|
539
|
+
"STRING": str,
|
|
540
|
+
"INTEGER": int,
|
|
541
|
+
"FLOAT": float,
|
|
542
|
+
"BOOLEAN": bool,
|
|
543
|
+
"DATE": str, # Dates are typically strings in queries
|
|
544
|
+
"DATETIME": str,
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
# Get expected Python type
|
|
548
|
+
expected_type = None
|
|
549
|
+
if hasattr(property_type, "value"):
|
|
550
|
+
# Enum type
|
|
551
|
+
expected_type = type_map.get(property_type.value)
|
|
552
|
+
elif hasattr(property_type, "name"):
|
|
553
|
+
# String type name
|
|
554
|
+
expected_type = type_map.get(property_type.name)
|
|
555
|
+
|
|
556
|
+
if expected_type is None:
|
|
557
|
+
return errors # Unknown type, skip validation
|
|
558
|
+
|
|
559
|
+
# For IN operator, check list elements
|
|
560
|
+
if operator == "IN":
|
|
561
|
+
if isinstance(value, list):
|
|
562
|
+
for item in value:
|
|
563
|
+
if not isinstance(item, expected_type):
|
|
564
|
+
errors.append(
|
|
565
|
+
ValidationError(
|
|
566
|
+
line=line,
|
|
567
|
+
column=column,
|
|
568
|
+
message=f"Property '{property_path}' expects {expected_type.__name__} values, "
|
|
569
|
+
f"but list contains {type(item).__name__}",
|
|
570
|
+
suggestion=f"Ensure all list values are {expected_type.__name__}",
|
|
571
|
+
)
|
|
572
|
+
)
|
|
573
|
+
break # Only report once
|
|
574
|
+
return errors
|
|
575
|
+
|
|
576
|
+
# Check value type
|
|
577
|
+
if not isinstance(value, expected_type):
|
|
578
|
+
errors.append(
|
|
579
|
+
ValidationError(
|
|
580
|
+
line=line,
|
|
581
|
+
column=column,
|
|
582
|
+
message=f"Property '{property_path}' expects {expected_type.__name__} value, "
|
|
583
|
+
f"got {type(value).__name__}",
|
|
584
|
+
suggestion=f"Use a {expected_type.__name__} value",
|
|
585
|
+
)
|
|
586
|
+
)
|
|
587
|
+
|
|
588
|
+
return errors
|
|
589
|
+
|
|
590
|
+
def validate_relation_endpoints(
|
|
591
|
+
self,
|
|
592
|
+
relation_type: str,
|
|
593
|
+
current_entity_type: str,
|
|
594
|
+
direction: str,
|
|
595
|
+
line: int,
|
|
596
|
+
column: int,
|
|
597
|
+
) -> List[ValidationError]:
|
|
598
|
+
"""
|
|
599
|
+
Validate that relation endpoints match entity types
|
|
600
|
+
|
|
601
|
+
Args:
|
|
602
|
+
relation_type: Relation type name
|
|
603
|
+
current_entity_type: Current entity type in the query
|
|
604
|
+
direction: Direction of traversal ("incoming" or "outgoing")
|
|
605
|
+
line: Line number for error reporting
|
|
606
|
+
column: Column number for error reporting
|
|
607
|
+
|
|
608
|
+
Returns:
|
|
609
|
+
List of validation errors
|
|
610
|
+
"""
|
|
611
|
+
errors = []
|
|
612
|
+
|
|
613
|
+
# Get relation type schema
|
|
614
|
+
if not hasattr(self.schema, "get_relation_type"):
|
|
615
|
+
return errors
|
|
616
|
+
|
|
617
|
+
relation_schema = self.schema.get_relation_type(relation_type)
|
|
618
|
+
if relation_schema is None:
|
|
619
|
+
return errors # Relation type error already reported
|
|
620
|
+
|
|
621
|
+
# Check if relation has endpoint constraints
|
|
622
|
+
if not hasattr(relation_schema, "source_entity_types") or not hasattr(
|
|
623
|
+
relation_schema, "target_entity_types"
|
|
624
|
+
):
|
|
625
|
+
return errors
|
|
626
|
+
|
|
627
|
+
source_types = relation_schema.source_entity_types
|
|
628
|
+
target_types = relation_schema.target_entity_types
|
|
629
|
+
|
|
630
|
+
# Validate based on direction
|
|
631
|
+
if direction == "outgoing":
|
|
632
|
+
# Current entity is source, check if it's allowed
|
|
633
|
+
if source_types and current_entity_type not in source_types:
|
|
634
|
+
errors.append(
|
|
635
|
+
ValidationError(
|
|
636
|
+
line=line,
|
|
637
|
+
column=column,
|
|
638
|
+
message=f"Entity type '{current_entity_type}' cannot be source of relation '{relation_type}'",
|
|
639
|
+
suggestion=f"Allowed source types: {', '.join(source_types)}",
|
|
640
|
+
)
|
|
641
|
+
)
|
|
642
|
+
elif direction == "incoming":
|
|
643
|
+
# Current entity is target, check if it's allowed
|
|
644
|
+
if target_types and current_entity_type not in target_types:
|
|
645
|
+
errors.append(
|
|
646
|
+
ValidationError(
|
|
647
|
+
line=line,
|
|
648
|
+
column=column,
|
|
649
|
+
message=f"Entity type '{current_entity_type}' cannot be target of relation '{relation_type}'",
|
|
650
|
+
suggestion=f"Allowed target types: {', '.join(target_types)}",
|
|
651
|
+
)
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
return errors
|