Add private complete mixed Project resolution with retained provenance
This commit is contained in:
@@ -180,14 +180,96 @@ fn check_ids(out: &mut Vec<Issue>, owner: Id, field: &str, ids: &[Id]) {
|
||||
}
|
||||
|
||||
pub fn validate(p: &Project) -> Vec<Issue> {
|
||||
validate_with_usage(p).0
|
||||
}
|
||||
|
||||
/// Runs the shared static/architectural route once and retains successful static
|
||||
/// metadata without retaining a Project-borrowing environment. Architectural
|
||||
/// issue production is outside the static meter and any caller auxiliary ledger;
|
||||
/// callers must reserve separately before mapping or retaining these issues.
|
||||
pub(crate) fn validate_with_usage(
|
||||
p: &Project,
|
||||
) -> (
|
||||
Vec<Issue>,
|
||||
Option<(
|
||||
crate::analysis::AdmissionLimits,
|
||||
crate::analysis::AdmissionUsage,
|
||||
)>,
|
||||
) {
|
||||
// Bound analysis trees before architectural visitors can clone an element.
|
||||
// Admission itself never enters this public validator recursively.
|
||||
if let Err(failure) = crate::analysis::admit_structure_checked(p, crate::analysis::AdmissionLimits::REFERENCE) {
|
||||
let mut out = Vec::new();
|
||||
error(&mut out, failure.object, &failure.code, &failure.field, failure.message);
|
||||
return out;
|
||||
let environment = match crate::analysis::admit_structure_checked(
|
||||
p,
|
||||
crate::analysis::AdmissionLimits::REFERENCE,
|
||||
) {
|
||||
Ok(environment) => environment,
|
||||
Err(failure) => {
|
||||
let mut out = Vec::new();
|
||||
error(
|
||||
&mut out,
|
||||
failure.object,
|
||||
&failure.code,
|
||||
&failure.field,
|
||||
failure.message,
|
||||
);
|
||||
return (out, None);
|
||||
}
|
||||
};
|
||||
let metadata = (
|
||||
environment.admission_limits(),
|
||||
environment.admission_usage(),
|
||||
);
|
||||
drop(environment);
|
||||
(validate_structure(p), Some(metadata))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod shared_admission_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn successful_static_metadata_survives_architectural_warnings() {
|
||||
let mut project = Project::empty();
|
||||
project.name.clear();
|
||||
let (issues, metadata) = validate_with_usage(&project);
|
||||
let (limits, usage) = metadata.expect("static admission succeeds");
|
||||
assert_eq!(limits, crate::analysis::AdmissionLimits::REFERENCE);
|
||||
assert!(usage.static_events > 0);
|
||||
assert_eq!(usage.stored_expression_nodes, 0);
|
||||
assert!(issues.iter().any(|issue| issue.code == "unnamed"
|
||||
&& issue.element == Some(project.id)
|
||||
&& issue.severity == Severity::Warning));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn static_failure_returns_no_metadata_and_skips_architectural_warnings() {
|
||||
use crate::analysis::{
|
||||
AnalysisType, Binding, CalculationDefinition, Definition, DefinitionBody, ScalarType,
|
||||
};
|
||||
let mut project = Project::empty();
|
||||
project.name.clear();
|
||||
let key = Id::from_u128(10);
|
||||
project.definitions.insert(
|
||||
key,
|
||||
Definition {
|
||||
id: Id::from_u128(11),
|
||||
alias: "f".into(),
|
||||
name: "F".into(),
|
||||
description: String::new(),
|
||||
body: DefinitionBody::Calculation(CalculationDefinition {
|
||||
parameters: Vec::new(),
|
||||
result: AnalysisType::Scalar(ScalarType::Number),
|
||||
body: Binding::Unbound,
|
||||
}),
|
||||
},
|
||||
);
|
||||
let (issues, metadata) = validate_with_usage(&project);
|
||||
assert!(metadata.is_none());
|
||||
assert_eq!(issues.len(), 1);
|
||||
assert_eq!(issues[0].severity, Severity::Error);
|
||||
assert_eq!(issues[0].element, Some(key));
|
||||
assert_eq!(issues[0].code, "identity_mismatch");
|
||||
}
|
||||
validate_structure(p)
|
||||
}
|
||||
|
||||
/// Architecture/header validation without recursively re-entering analysis admission.
|
||||
|
||||
Reference in New Issue
Block a user