Skip to main content

datalog_structural_model/
profile.rs

1//! Checked generic language profiles; RDF/OWL semantics belong in rule packages.
2use crate::{DatalogCapabilities, DatalogFeature, EqualityMode, Program, ProgramValidationError};
3use std::{error::Error, fmt};
4
5/// Explicit language features and equality regime selected for a program.
6/// This is neither an RDF entailment profile nor an execution strategy.
7#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
8#[serde(deny_unknown_fields)]
9pub struct DatalogProfile {
10    capabilities: DatalogCapabilities,
11    equality_mode: EqualityMode,
12}
13impl DatalogProfile {
14    /// Construct an explicit language contract. Program-specific combinations
15    /// are checked by [`Program::validate_for`].
16    #[must_use]
17    pub fn new(capabilities: DatalogCapabilities, equality_mode: EqualityMode) -> Self {
18        Self {
19            capabilities,
20            equality_mode,
21        }
22    }
23    /// Positive flat Datalog without built-in expression extensions.
24    #[must_use]
25    pub fn positive() -> Self {
26        Self::new(DatalogCapabilities::positive(), EqualityMode::Default)
27    }
28    /// Flat Datalog with stratified negation, without built-in extensions.
29    #[must_use]
30    pub fn stratified() -> Self {
31        Self::new(
32            DatalogCapabilities::positive().with(DatalogFeature::StratifiedNegation),
33            EqualityMode::Default,
34        )
35    }
36    /// The explicitly selected language features.
37    #[must_use]
38    pub fn capabilities(&self) -> &DatalogCapabilities {
39        &self.capabilities
40    }
41    /// The selected equality validation regime.
42    #[must_use]
43    pub fn equality_mode(&self) -> EqualityMode {
44        self.equality_mode
45    }
46}
47
48/// Features required by the program and absent from the selected profile.
49#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
50#[serde(deny_unknown_fields)]
51pub struct UnsupportedCapabilities {
52    missing: DatalogCapabilities,
53}
54impl UnsupportedCapabilities {
55    /// Inspect every missing feature, including nested operators.
56    #[must_use]
57    pub fn missing(&self) -> &DatalogCapabilities {
58        &self.missing
59    }
60}
61impl fmt::Display for UnsupportedCapabilities {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        write!(f, "unsupported Datalog features: {:?}", self.missing)
64    }
65}
66impl Error for UnsupportedCapabilities {}
67
68/// An invalid program or an explicitly unsupported language feature.
69#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
70#[serde(deny_unknown_fields)]
71pub enum ProfileCheckError {
72    /// Structural safety, stratification or equality compatibility failed.
73    InvalidProgram(ProgramValidationError),
74    /// One or more required features are absent from the profile.
75    Unsupported(UnsupportedCapabilities),
76}
77impl fmt::Display for ProfileCheckError {
78    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79        match self {
80            Self::InvalidProgram(error) => error.fmt(f),
81            Self::Unsupported(error) => error.fmt(f),
82        }
83    }
84}
85impl Error for ProfileCheckError {
86    fn source(&self) -> Option<&(dyn Error + 'static)> {
87        match self {
88            Self::InvalidProgram(error) => Some(error),
89            Self::Unsupported(error) => Some(error),
90        }
91    }
92}
93impl Program {
94    /// Validate safety and reject every feature missing from the selected contract.
95    /// # Errors
96    /// Returns typed structural failures or the exact unsupported feature set.
97    pub fn validate_for(&self, profile: &DatalogProfile) -> Result<(), ProfileCheckError> {
98        self.validate_with_equality_mode(profile.equality_mode)
99            .map_err(ProfileCheckError::InvalidProgram)?;
100        let missing = self
101            .required_capabilities()
102            .missing_from(&profile.capabilities);
103        if !missing.is_empty() {
104            return Err(ProfileCheckError::Unsupported(UnsupportedCapabilities {
105                missing,
106            }));
107        }
108        Ok(())
109    }
110}