pub mod baseline;
pub mod engine;
pub mod google;
use std::fmt;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::graph::StructuredDataGraph;
use crate::types::SchemaNode;
use crate::validation::ValidationDiagnostic;
pub trait Profile: Send + Sync {
fn name(&self) -> &'static str;
fn version(&self) -> &'static str;
fn source_url(&self) -> &'static str;
fn supported_types(&self) -> &[&str];
fn evaluate_node(
&self,
node: &SchemaNode,
vocab_diagnostics: &[ValidationDiagnostic],
) -> NodeProfileResult;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[must_use]
pub struct ProfileResult {
pub eligibility: Eligibility,
pub type_results: Vec<TypeEligibility>,
pub diagnostics: Vec<ValidationDiagnostic>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[must_use]
pub enum Eligibility {
Eligible,
WarningsOnly,
NotEligible,
Restricted,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[must_use]
pub struct TypeEligibility {
pub schema_type: String,
pub eligible: bool,
pub required_missing: Vec<String>,
pub recommended_missing: Vec<String>,
pub field_diagnostics: Vec<ValidationDiagnostic>,
}
#[must_use]
pub struct NodeProfileResult {
pub type_eligibility: TypeEligibility,
}
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum ProfileError {
#[error("unknown profile: '{0}'")]
UnknownProfile(String),
#[error("no nodes matched the profile's supported types")]
NoMatchingTypes,
}
impl fmt::Display for Eligibility {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Eligible => write!(f, "Eligible"),
Self::WarningsOnly => write!(f, "WarningsOnly"),
Self::NotEligible => write!(f, "NotEligible"),
Self::Restricted => write!(f, "Restricted"),
}
}
}
pub struct ProfileRegistry {
profiles: Vec<Box<dyn Profile>>,
}
impl ProfileRegistry {
#[must_use]
pub fn new() -> Self {
Self {
profiles: Vec::new(),
}
}
#[must_use]
pub fn with_google() -> Self {
let mut registry = Self::new();
google::register_all(&mut registry);
registry
}
#[must_use]
pub fn with_baseline() -> Self {
let mut registry = Self::new();
registry.register(Box::new(baseline::BaselineProfile));
registry
}
pub fn register(&mut self, profile: Box<dyn Profile>) {
self.profiles.push(profile);
}
pub fn evaluate(
&self,
profile_name: &str,
graph: &StructuredDataGraph,
vocab_diagnostics: &[ValidationDiagnostic],
) -> Result<ProfileResult, ProfileError> {
let matching: Vec<_> = self
.profiles
.iter()
.filter(|p| p.name() == profile_name)
.collect();
if matching.is_empty() {
return Err(ProfileError::UnknownProfile(profile_name.to_string()));
}
let mut all_type_results = Vec::new();
let mut all_diagnostics = Vec::new();
for profile in &matching {
let result = engine::evaluate_graph(profile.as_ref(), graph, vocab_diagnostics);
all_type_results.extend(result.type_results);
all_diagnostics.extend(result.diagnostics);
}
let eligibility = engine::aggregate_eligibility(&all_type_results, &all_diagnostics);
Ok(ProfileResult {
eligibility,
type_results: all_type_results,
diagnostics: all_diagnostics,
})
}
#[must_use]
pub fn profile_names(&self) -> Vec<&str> {
self.profiles.iter().map(|p| p.name()).collect()
}
}
impl Default for ProfileRegistry {
fn default() -> Self {
Self::new()
}
}