Skip to main content

schemaorg_rs/profiles/
engine.rs

1//! Profile evaluation engine -- orchestrates profile checks across graph nodes.
2
3use crate::graph::StructuredDataGraph;
4use crate::validation::ValidationDiagnostic;
5use crate::vocabulary;
6
7use super::{Eligibility, Profile, ProfileResult, TypeEligibility};
8
9/// Evaluates a graph against a single profile.
10///
11/// For each node in the graph, checks if its types match the profile's
12/// `supported_types()` (including subtypes via inheritance). If so,
13/// evaluates the node and collects results.
14pub fn evaluate_graph(
15    profile: &dyn Profile,
16    graph: &StructuredDataGraph,
17    vocab_diagnostics: &[ValidationDiagnostic],
18) -> ProfileResult {
19    let mut type_results = Vec::new();
20    let mut all_diagnostics = Vec::new();
21
22    for node in &graph.nodes {
23        // Check if this node's types match any supported type (including subtypes)
24        let dominated = node.types.iter().any(|t| {
25            profile
26                .supported_types()
27                .iter()
28                .any(|supported| t == supported || vocabulary::is_subtype(t, supported))
29        });
30
31        if !dominated {
32            continue;
33        }
34
35        let result = profile.evaluate_node(node, vocab_diagnostics);
36        all_diagnostics.extend(result.type_eligibility.field_diagnostics.iter().cloned());
37        type_results.push(result.type_eligibility);
38    }
39
40    let eligibility = aggregate_eligibility(&type_results, &all_diagnostics);
41
42    ProfileResult {
43        eligibility,
44        type_results,
45        diagnostics: all_diagnostics,
46    }
47}
48
49/// Aggregates individual type eligibilities into an overall eligibility verdict.
50pub(crate) fn aggregate_eligibility(
51    type_results: &[TypeEligibility],
52    diagnostics: &[ValidationDiagnostic],
53) -> Eligibility {
54    if type_results.is_empty() {
55        return Eligibility::NotEligible;
56    }
57
58    // Check if any type result has restricted eligibility (from diagnostics)
59    let has_restricted = diagnostics
60        .iter()
61        .any(|d| d.code == crate::validation::DiagnosticCode::EligibilityRestricted);
62
63    let all_eligible = type_results.iter().all(|r| r.eligible);
64    let has_warnings = !diagnostics.is_empty();
65
66    if has_restricted {
67        Eligibility::Restricted
68    } else if all_eligible && !has_warnings {
69        Eligibility::Eligible
70    } else if all_eligible {
71        Eligibility::WarningsOnly
72    } else {
73        Eligibility::NotEligible
74    }
75}