sea_core/graph/
mod.rs

1use crate::patterns::Pattern;
2use crate::policy::{Policy, Severity, Violation};
3use crate::primitives::{
4    ConceptChange, Entity, Flow, Instance, MappingContract, Metric, ProjectionContract,
5    RelationType, Resource, ResourceInstance, Role,
6};
7use crate::validation_result::ValidationResult;
8use crate::ConceptId;
9use indexmap::IndexMap;
10use serde::{Deserialize, Serialize};
11
12pub mod to_ast;
13
14/// Configuration for graph evaluation behavior
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct GraphConfig {
17    /// Enable three-valued logic (True, False, NULL) for policy evaluation.
18    /// When false, uses strict boolean logic (True, False).
19    pub use_three_valued_logic: bool,
20}
21
22impl Default for GraphConfig {
23    fn default() -> Self {
24        Self {
25            // Default to three-valued logic for backward compatibility with the feature flag
26            use_three_valued_logic: true,
27        }
28    }
29}
30
31#[derive(Debug, Clone, Default, Serialize, Deserialize)]
32pub struct Graph {
33    entities: IndexMap<ConceptId, Entity>,
34    roles: IndexMap<ConceptId, Role>,
35    resources: IndexMap<ConceptId, Resource>,
36    flows: IndexMap<ConceptId, Flow>,
37    relations: IndexMap<ConceptId, RelationType>,
38    instances: IndexMap<ConceptId, ResourceInstance>,
39    /// Entity instances keyed by ConceptId for consistency with other graph collections.
40    entity_instances: IndexMap<ConceptId, Instance>,
41    policies: IndexMap<ConceptId, Policy>,
42    #[serde(default)]
43    patterns: IndexMap<ConceptId, Pattern>,
44    #[serde(default)]
45    concept_changes: IndexMap<ConceptId, ConceptChange>,
46    #[serde(default)]
47    metrics: IndexMap<ConceptId, Metric>,
48    #[serde(default)]
49    mappings: IndexMap<ConceptId, MappingContract>,
50    #[serde(default)]
51    projections: IndexMap<ConceptId, ProjectionContract>,
52    #[serde(default)]
53    entity_roles: IndexMap<ConceptId, Vec<ConceptId>>,
54    #[serde(default)]
55    config: GraphConfig,
56}
57
58impl Graph {
59    pub fn new() -> Self {
60        Self::default()
61    }
62
63    pub fn is_empty(&self) -> bool {
64        self.entities.is_empty()
65            && self.roles.is_empty()
66            && self.resources.is_empty()
67            && self.flows.is_empty()
68            && self.relations.is_empty()
69            && self.instances.is_empty()
70            && self.entity_instances.is_empty()
71            && self.policies.is_empty()
72            && self.patterns.is_empty()
73            && self.concept_changes.is_empty()
74            && self.concept_changes.is_empty()
75            && self.metrics.is_empty()
76            && self.mappings.is_empty()
77            && self.projections.is_empty()
78    }
79
80    /// Set the evaluation mode for policy evaluation.
81    /// When `use_three_valued_logic` is true, policies will use three-valued logic (True, False, NULL).
82    /// When false, policies will use strict boolean logic (True, False).
83    pub fn set_evaluation_mode(&mut self, use_three_valued_logic: bool) {
84        self.config.use_three_valued_logic = use_three_valued_logic;
85    }
86
87    /// Get the current evaluation mode.
88    pub fn use_three_valued_logic(&self) -> bool {
89        self.config.use_three_valued_logic
90    }
91
92    pub fn config(&self) -> &GraphConfig {
93        &self.config
94    }
95
96    pub fn config_mut(&mut self) -> &mut GraphConfig {
97        &mut self.config
98    }
99
100    pub fn entity_count(&self) -> usize {
101        self.entities.len()
102    }
103
104    pub fn add_entity(&mut self, entity: Entity) -> Result<(), String> {
105        let id = entity.id().clone();
106        if self.entities.contains_key(&id) {
107            return Err(format!("Entity with ID {} already exists", id));
108        }
109        self.entities.insert(id, entity);
110        Ok(())
111    }
112
113    pub fn has_entity(&self, id: &ConceptId) -> bool {
114        self.entities.contains_key(id)
115    }
116
117    pub fn get_entity(&self, id: &ConceptId) -> Option<&Entity> {
118        self.entities.get(id)
119    }
120
121    /// Returns a mutable reference to an Entity identified by `id`.
122    /// This method is necessary for operations that need to update attributes
123    /// on existing entities (such as association relationships).
124    pub fn get_entity_mut(&mut self, id: &ConceptId) -> Option<&mut Entity> {
125        self.entities.get_mut(id)
126    }
127
128    pub fn remove_entity(&mut self, id: &ConceptId) -> Result<Entity, String> {
129        // Check for references in flows
130        let referencing_flows: Vec<String> = self
131            .flows
132            .values()
133            .filter(|flow| flow.from_id() == id || flow.to_id() == id)
134            .map(|flow| flow.id().to_string())
135            .collect();
136
137        // Check for references in instances
138        let referencing_instances: Vec<String> = self
139            .instances
140            .values()
141            .filter(|instance| instance.entity_id() == id)
142            .map(|instance| instance.id().to_string())
143            .collect();
144
145        if !referencing_flows.is_empty() || !referencing_instances.is_empty() {
146            let mut error_msg = format!("Cannot remove entity {} because it is referenced", id);
147            if !referencing_flows.is_empty() {
148                error_msg.push_str(&format!(" by flows: {}", referencing_flows.join(", ")));
149            }
150            if !referencing_instances.is_empty() {
151                error_msg.push_str(&format!(
152                    " by instances: {}",
153                    referencing_instances.join(", ")
154                ));
155            }
156            return Err(error_msg);
157        }
158
159        self.entities
160            .shift_remove(id)
161            .ok_or_else(|| format!("Entity with ID {} not found", id))
162    }
163
164    pub fn role_count(&self) -> usize {
165        self.roles.len()
166    }
167
168    pub fn add_role(&mut self, role: Role) -> Result<(), String> {
169        let id = role.id().clone();
170        if self.roles.contains_key(&id) {
171            return Err(format!("Role with ID {} already exists", id));
172        }
173        self.roles.insert(id, role);
174        Ok(())
175    }
176
177    pub fn get_role(&self, id: &ConceptId) -> Option<&Role> {
178        self.roles.get(id)
179    }
180
181    pub fn has_role(&self, id: &ConceptId) -> bool {
182        self.roles.contains_key(id)
183    }
184
185    pub fn assign_role_to_entity(
186        &mut self,
187        entity_id: ConceptId,
188        role_id: ConceptId,
189    ) -> Result<(), String> {
190        if !self.entities.contains_key(&entity_id) {
191            return Err(format!("Entity with ID {} not found", entity_id));
192        }
193        if !self.roles.contains_key(&role_id) {
194            return Err(format!("Role with ID {} not found", role_id));
195        }
196
197        let roles = self.entity_roles.entry(entity_id).or_default();
198        if !roles.contains(&role_id) {
199            roles.push(role_id);
200        }
201        Ok(())
202    }
203
204    pub fn roles_for_entity(&self, entity_id: &ConceptId) -> Option<&Vec<ConceptId>> {
205        self.entity_roles.get(entity_id)
206    }
207
208    pub fn role_names_for_entity(&self, entity_id: &ConceptId) -> Vec<String> {
209        self.entity_roles
210            .get(entity_id)
211            .into_iter()
212            .flat_map(|roles| roles.iter())
213            .filter_map(|role_id| self.roles.get(role_id))
214            .map(|role| role.name().to_string())
215            .collect()
216    }
217
218    pub fn resource_count(&self) -> usize {
219        self.resources.len()
220    }
221
222    pub fn add_resource(&mut self, resource: Resource) -> Result<(), String> {
223        let id = resource.id().clone();
224        if self.resources.contains_key(&id) {
225            return Err(format!("Resource with ID {} already exists", id));
226        }
227        self.resources.insert(id, resource);
228        Ok(())
229    }
230
231    pub fn has_resource(&self, id: &ConceptId) -> bool {
232        self.resources.contains_key(id)
233    }
234
235    pub fn get_resource(&self, id: &ConceptId) -> Option<&Resource> {
236        self.resources.get(id)
237    }
238
239    pub fn remove_resource(&mut self, id: &ConceptId) -> Result<Resource, String> {
240        // Check for references in flows
241        let referencing_flows: Vec<String> = self
242            .flows
243            .values()
244            .filter(|flow| flow.resource_id() == id)
245            .map(|flow| flow.id().to_string())
246            .collect();
247
248        // Check for references in instances
249        let referencing_instances: Vec<String> = self
250            .instances
251            .values()
252            .filter(|instance| instance.resource_id() == id)
253            .map(|instance| instance.id().to_string())
254            .collect();
255
256        if !referencing_flows.is_empty() || !referencing_instances.is_empty() {
257            let mut error_msg = format!("Cannot remove resource {} because it is referenced", id);
258            if !referencing_flows.is_empty() {
259                error_msg.push_str(&format!(" by flows: {}", referencing_flows.join(", ")));
260            }
261            if !referencing_instances.is_empty() {
262                error_msg.push_str(&format!(
263                    " by instances: {}",
264                    referencing_instances.join(", ")
265                ));
266            }
267            return Err(error_msg);
268        }
269
270        self.resources
271            .shift_remove(id)
272            .ok_or_else(|| format!("Resource with ID {} not found", id))
273    }
274
275    pub fn flow_count(&self) -> usize {
276        self.flows.len()
277    }
278
279    pub fn pattern_count(&self) -> usize {
280        self.patterns.len()
281    }
282
283    pub fn relation_count(&self) -> usize {
284        self.relations.len()
285    }
286
287    pub fn add_relation_type(&mut self, relation: RelationType) -> Result<(), String> {
288        let id = relation.id().clone();
289        if self.relations.contains_key(&id) {
290            return Err(format!("Relation with ID {} already exists", id));
291        }
292        self.relations.insert(id, relation);
293        Ok(())
294    }
295
296    pub fn all_relations(&self) -> Vec<&RelationType> {
297        self.relations.values().collect()
298    }
299
300    pub fn add_flow(&mut self, flow: Flow) -> Result<(), String> {
301        let id = flow.id().clone();
302        if self.flows.contains_key(&id) {
303            return Err(format!("Flow with ID {} already exists", id));
304        }
305        if !self.entities.contains_key(flow.from_id()) {
306            return Err("Source entity not found".to_string());
307        }
308        if !self.entities.contains_key(flow.to_id()) {
309            return Err("Target entity not found".to_string());
310        }
311        if !self.resources.contains_key(flow.resource_id()) {
312            return Err("Resource not found".to_string());
313        }
314        self.flows.insert(id, flow);
315        Ok(())
316    }
317
318    pub fn add_pattern(&mut self, pattern: Pattern) -> Result<(), String> {
319        let id = pattern.id().clone();
320        if self.patterns.contains_key(&id) {
321            return Err(format!("Pattern with ID {} already exists", id));
322        }
323
324        if self.patterns.values().any(|existing| {
325            existing.name() == pattern.name() && existing.namespace() == pattern.namespace()
326        }) {
327            return Err(format!(
328                "Pattern '{}' already declared in namespace '{}'",
329                pattern.name(),
330                pattern.namespace()
331            ));
332        }
333
334        self.patterns.insert(id, pattern);
335        Ok(())
336    }
337
338    pub fn add_concept_change(&mut self, change: ConceptChange) -> Result<(), String> {
339        let id = change.id().clone();
340        if self.concept_changes.contains_key(&id) {
341            return Err(format!("ConceptChange with ID {} already exists", id));
342        }
343        self.concept_changes.insert(id, change);
344        Ok(())
345    }
346
347    pub fn get_concept_change(&self, id: &ConceptId) -> Option<&ConceptChange> {
348        self.concept_changes.get(id)
349    }
350
351    pub fn all_concept_changes(&self) -> Vec<&ConceptChange> {
352        self.concept_changes.values().collect()
353    }
354
355    pub fn has_flow(&self, id: &ConceptId) -> bool {
356        self.flows.contains_key(id)
357    }
358
359    pub fn get_flow(&self, id: &ConceptId) -> Option<&Flow> {
360        self.flows.get(id)
361    }
362
363    pub fn remove_flow(&mut self, id: &ConceptId) -> Result<Flow, String> {
364        self.flows
365            .shift_remove(id)
366            .ok_or_else(|| format!("Flow with ID {} not found", id))
367    }
368
369    pub fn instance_count(&self) -> usize {
370        self.instances.len()
371    }
372
373    pub fn add_instance(&mut self, instance: ResourceInstance) -> Result<(), String> {
374        let id = instance.id().clone();
375        if self.instances.contains_key(&id) {
376            return Err(format!("Instance with ID {} already exists", id));
377        }
378        if !self.entities.contains_key(instance.entity_id()) {
379            return Err("Entity not found".to_string());
380        }
381        if !self.resources.contains_key(instance.resource_id()) {
382            return Err("Resource not found".to_string());
383        }
384        self.instances.insert(id, instance);
385        Ok(())
386    }
387
388    /// Adds an association relationship from `owner` -> `owned` using a named `rel_type`.
389    /// Associations are represented as a JSON attribute `associations` on the source entity to
390    /// maintain a simple, serializable representation without introducing a new primitive.
391    pub fn add_association(
392        &mut self,
393        owner: &ConceptId,
394        owned: &ConceptId,
395        rel_type: &str,
396    ) -> Result<(), String> {
397        if !self.entities.contains_key(owner) {
398            return Err("Source entity not found".to_string());
399        }
400        if !self.entities.contains_key(owned) {
401            return Err("Destination entity not found".to_string());
402        }
403
404        let owned_str = owned.to_string();
405        let rel_type_str = rel_type.to_string();
406
407        // Mutably borrow the entity and insert/update the `associations` attribute.
408        if let Some(entity) = self.entities.get_mut(owner) {
409            use serde_json::{json, Value};
410            let mut associations: Value = entity
411                .get_attribute("associations")
412                .cloned()
413                .unwrap_or_else(|| json!([]));
414
415            if let Value::Array(arr) = &mut associations {
416                arr.push(json!({"type": rel_type_str, "target": owned_str}));
417            } else {
418                // Replace non-array associations with an array structure.
419                associations = json!([ {"type": rel_type_str, "target": owned_str} ]);
420            }
421
422            entity.set_attribute("associations", associations);
423            Ok(())
424        } else {
425            Err("Failed to retrieve entity for association".to_string())
426        }
427    }
428
429    pub fn has_instance(&self, id: &ConceptId) -> bool {
430        self.instances.contains_key(id)
431    }
432
433    pub fn get_instance(&self, id: &ConceptId) -> Option<&ResourceInstance> {
434        self.instances.get(id)
435    }
436
437    pub fn remove_instance(&mut self, id: &ConceptId) -> Result<ResourceInstance, String> {
438        self.instances
439            .shift_remove(id)
440            .ok_or_else(|| format!("Instance with ID {} not found", id))
441    }
442
443    // Entity Instance methods
444    pub fn entity_instance_count(&self) -> usize {
445        self.entity_instances.len()
446    }
447
448    pub fn add_entity_instance(&mut self, instance: Instance) -> Result<(), String> {
449        let id = instance.id().clone();
450        if self.entity_instances.contains_key(&id) {
451            return Err(format!(
452                "Entity instance '{}' already exists",
453                instance.name()
454            ));
455        }
456        if self.find_entity_instance_by_name(instance.name()).is_some() {
457            return Err(format!(
458                "Entity instance '{}' already exists",
459                instance.name()
460            ));
461        }
462
463        let namespace = instance.namespace();
464        let entity_type = instance.entity_type();
465
466        self.find_entity_by_name_and_namespace(entity_type, namespace)
467            .ok_or_else(|| {
468                format!(
469                    "Entity '{}' not found in namespace '{}'",
470                    entity_type, namespace
471                )
472            })?;
473
474        self.entity_instances.insert(id, instance);
475        Ok(())
476    }
477
478    pub fn get_entity_instance(&self, name: &str) -> Option<&Instance> {
479        self.find_entity_instance_by_name(name)
480            .and_then(|id| self.entity_instances.get(&id))
481    }
482
483    pub fn get_entity_instance_mut(&mut self, name: &str) -> Option<&mut Instance> {
484        let id = self.find_entity_instance_by_name(name)?;
485        self.entity_instances.get_mut(&id)
486    }
487
488    pub fn all_entity_instances(&self) -> Vec<&Instance> {
489        self.entity_instances.values().collect()
490    }
491
492    pub fn remove_entity_instance(&mut self, name: &str) -> Result<Instance, String> {
493        let id = self
494            .find_entity_instance_by_name(name)
495            .ok_or_else(|| format!("Entity instance '{}' not found", name))?;
496
497        self.entity_instances
498            .shift_remove(&id)
499            .ok_or_else(|| format!("Entity instance '{}' not found", name))
500    }
501
502    pub fn has_entity_instance_by_id(&self, id: &ConceptId) -> bool {
503        self.entity_instances.contains_key(id)
504    }
505
506    pub fn get_entity_instance_by_id(&self, id: &ConceptId) -> Option<&Instance> {
507        self.entity_instances.get(id)
508    }
509
510    pub fn remove_entity_instance_by_id(&mut self, id: &ConceptId) -> Result<Instance, String> {
511        self.entity_instances
512            .shift_remove(id)
513            .ok_or_else(|| format!("Entity instance with id '{}' not found", id))
514    }
515
516    pub fn flows_from(&self, entity_id: &ConceptId) -> Vec<&Flow> {
517        self.flows
518            .values()
519            .filter(|flow| flow.from_id() == entity_id)
520            .collect()
521    }
522
523    pub fn flows_to(&self, entity_id: &ConceptId) -> Vec<&Flow> {
524        self.flows
525            .values()
526            .filter(|flow| flow.to_id() == entity_id)
527            .collect()
528    }
529
530    pub fn upstream_entities(&self, entity_id: &ConceptId) -> Vec<&Entity> {
531        self.flows_to(entity_id)
532            .iter()
533            .filter_map(|flow| self.get_entity(flow.from_id()))
534            .collect()
535    }
536
537    pub fn downstream_entities(&self, entity_id: &ConceptId) -> Vec<&Entity> {
538        self.flows_from(entity_id)
539            .iter()
540            .filter_map(|flow| self.get_entity(flow.to_id()))
541            .collect()
542    }
543
544    pub fn find_entity_by_name_and_namespace(
545        &self,
546        name: &str,
547        namespace: &str,
548    ) -> Option<ConceptId> {
549        self.entities
550            .iter()
551            .find(|(_, entity)| entity.name() == name && entity.namespace() == namespace)
552            .map(|(id, _)| id.clone())
553    }
554
555    pub fn find_entity_by_name(&self, name: &str) -> Option<ConceptId> {
556        self.entities
557            .iter()
558            .find(|(_, entity)| entity.name() == name)
559            .map(|(id, _)| id.clone())
560    }
561
562    pub fn find_resource_by_name(&self, name: &str) -> Option<ConceptId> {
563        self.resources
564            .iter()
565            .find(|(_, resource)| resource.name() == name)
566            .map(|(id, _)| id.clone())
567    }
568
569    pub fn find_role_by_name(&self, name: &str) -> Option<ConceptId> {
570        self.roles
571            .iter()
572            .find(|(_, role)| role.name() == name)
573            .map(|(id, _)| id.clone())
574    }
575
576    pub fn find_entity_instance_by_name(&self, name: &str) -> Option<ConceptId> {
577        self.entity_instances
578            .iter()
579            .find(|(_, instance)| instance.name() == name)
580            .map(|(id, _)| id.clone())
581    }
582
583    pub fn find_entity_instance_by_name_and_namespace(
584        &self,
585        name: &str,
586        namespace: &str,
587    ) -> Option<ConceptId> {
588        self.entity_instances
589            .iter()
590            .find(|(_, instance)| instance.name() == name && instance.namespace() == namespace)
591            .map(|(id, _)| id.clone())
592    }
593
594    pub fn find_pattern(&self, name: &str, namespace: Option<&str>) -> Option<&Pattern> {
595        if let Some(ns) = namespace {
596            if let Some((_, pattern)) = self
597                .patterns
598                .iter()
599                .find(|(_, pattern)| pattern.name() == name && pattern.namespace() == ns)
600            {
601                return Some(pattern);
602            }
603        }
604
605        self.patterns
606            .iter()
607            .find(|(_, pattern)| pattern.name() == name)
608            .map(|(_, pattern)| pattern)
609    }
610
611    pub fn all_entities(&self) -> Vec<&Entity> {
612        self.entities.values().collect()
613    }
614
615    pub fn all_roles(&self) -> Vec<&Role> {
616        self.roles.values().collect()
617    }
618
619    pub fn all_resources(&self) -> Vec<&Resource> {
620        self.resources.values().collect()
621    }
622
623    pub fn all_flows(&self) -> Vec<&Flow> {
624        self.flows.values().collect()
625    }
626
627    pub fn all_instances(&self) -> Vec<&ResourceInstance> {
628        self.instances.values().collect()
629    }
630
631    pub fn all_patterns(&self) -> Vec<&Pattern> {
632        self.patterns.values().collect()
633    }
634
635    pub fn policy_count(&self) -> usize {
636        self.policies.len()
637    }
638
639    pub fn add_policy(&mut self, policy: Policy) -> Result<(), String> {
640        let id = policy.id.clone();
641        if self.policies.contains_key(&id) {
642            return Err(format!("Policy with ID {} already exists", id));
643        }
644        self.policies.insert(id, policy);
645        Ok(())
646    }
647
648    pub fn has_policy(&self, id: &ConceptId) -> bool {
649        self.policies.contains_key(id)
650    }
651
652    pub fn get_policy(&self, id: &ConceptId) -> Option<&Policy> {
653        self.policies.get(id)
654    }
655
656    pub fn remove_policy(&mut self, id: &ConceptId) -> Result<Policy, String> {
657        self.policies
658            .shift_remove(id)
659            .ok_or_else(|| format!("Policy with ID {} not found", id))
660    }
661
662    pub fn all_policies(&self) -> Vec<&Policy> {
663        self.policies.values().collect()
664    }
665
666    pub fn metric_count(&self) -> usize {
667        self.metrics.len()
668    }
669
670    pub fn add_metric(&mut self, metric: Metric) -> Result<(), String> {
671        let id = metric.id().clone();
672        if self.metrics.contains_key(&id) {
673            return Err(format!("Metric with ID {} already exists", id));
674        }
675        self.metrics.insert(id, metric);
676        Ok(())
677    }
678
679    pub fn has_metric(&self, id: &ConceptId) -> bool {
680        self.metrics.contains_key(id)
681    }
682
683    pub fn get_metric(&self, id: &ConceptId) -> Option<&Metric> {
684        self.metrics.get(id)
685    }
686
687    pub fn all_metrics(&self) -> Vec<&Metric> {
688        self.metrics.values().collect()
689    }
690
691    pub fn mapping_count(&self) -> usize {
692        self.mappings.len()
693    }
694
695    pub fn add_mapping(&mut self, mapping: MappingContract) -> Result<(), String> {
696        let id = mapping.id().clone();
697        if self.mappings.contains_key(&id) {
698            return Err(format!("Mapping with ID {} already exists", id));
699        }
700        self.mappings.insert(id, mapping);
701        Ok(())
702    }
703
704    pub fn has_mapping(&self, id: &ConceptId) -> bool {
705        self.mappings.contains_key(id)
706    }
707
708    pub fn get_mapping(&self, id: &ConceptId) -> Option<&MappingContract> {
709        self.mappings.get(id)
710    }
711
712    pub fn all_mappings(&self) -> Vec<&MappingContract> {
713        self.mappings.values().collect()
714    }
715
716    pub fn projection_count(&self) -> usize {
717        self.projections.len()
718    }
719
720    pub fn add_projection(&mut self, projection: ProjectionContract) -> Result<(), String> {
721        let id = projection.id().clone();
722        if self.projections.contains_key(&id) {
723            return Err(format!("Projection with ID {} already exists", id));
724        }
725        self.projections.insert(id, projection);
726        Ok(())
727    }
728
729    pub fn has_projection(&self, id: &ConceptId) -> bool {
730        self.projections.contains_key(id)
731    }
732
733    pub fn get_projection(&self, id: &ConceptId) -> Option<&ProjectionContract> {
734        self.projections.get(id)
735    }
736
737    pub fn all_projections(&self) -> Vec<&ProjectionContract> {
738        self.projections.values().collect()
739    }
740
741    /// Extend this graph with all nodes and policies from another graph.
742    /// The operation is atomic: the existing graph is only modified if the entire
743    /// merge succeeds, which prevents partial state when errors occur.
744    pub fn extend(&mut self, other: Graph) -> Result<(), String> {
745        let mut merged = self.clone();
746        merged.extend_from_graph(other)?;
747        *self = merged;
748        Ok(())
749    }
750
751    fn extend_from_graph(&mut self, other: Graph) -> Result<(), String> {
752        let Graph {
753            entities,
754            roles,
755            resources,
756            flows,
757            relations,
758            instances,
759            entity_instances,
760            policies,
761            patterns,
762            concept_changes,
763            metrics,
764            mappings,
765            projections,
766            entity_roles,
767            config: _,
768        } = other;
769
770        for entity in entities.into_values() {
771            self.add_entity(entity)?;
772        }
773
774        for role in roles.into_values() {
775            self.add_role(role)?;
776        }
777
778        for resource in resources.into_values() {
779            self.add_resource(resource)?;
780        }
781
782        for instance in instances.into_values() {
783            self.add_instance(instance)?;
784        }
785
786        for entity_instance in entity_instances.into_values() {
787            self.add_entity_instance(entity_instance)?;
788        }
789
790        for flow in flows.into_values() {
791            self.add_flow(flow)?;
792        }
793
794        for relation in relations.into_values() {
795            self.add_relation_type(relation)?;
796        }
797
798        for pattern in patterns.into_values() {
799            self.add_pattern(pattern)?;
800        }
801
802        for (entity_id, roles_for_entity) in entity_roles.into_iter() {
803            for role_id in roles_for_entity {
804                self.assign_role_to_entity(entity_id.clone(), role_id)?;
805            }
806        }
807
808        for policy in policies.into_values() {
809            self.add_policy(policy)?;
810        }
811
812        for change in concept_changes.into_values() {
813            self.add_concept_change(change)?;
814        }
815
816        for metric in metrics.into_values() {
817            self.add_metric(metric)?;
818        }
819
820        for mapping in mappings.into_values() {
821            self.add_mapping(mapping)?;
822        }
823
824        for projection in projections.into_values() {
825            self.add_projection(projection)?;
826        }
827
828        Ok(())
829    }
830
831    /// Validate the graph by evaluating all policies against it and
832    /// collecting any violations produced. Returns a `ValidationResult`.
833    pub fn validate(&self) -> ValidationResult {
834        let mut all_violations: Vec<Violation> = Vec::new();
835        let use_three_valued_logic = self.config.use_three_valued_logic;
836
837        for policy in self.policies.values() {
838            match policy.evaluate_with_mode(self, use_three_valued_logic) {
839                Ok(eval) => {
840                    all_violations.extend(eval.violations);
841                }
842                Err(err) => {
843                    // If a policy evaluation fails, produce an ERROR severity
844                    // violation indicating evaluation failure so the user can
845                    // see the issue.
846                    let v = Violation::new(
847                        &policy.name,
848                        format!("Policy evaluation failed: {}", err),
849                        Severity::Error,
850                    );
851                    all_violations.push(v);
852                }
853            }
854        }
855
856        ValidationResult::new(self.policies.len(), all_violations)
857    }
858}