use crate::instantiate::{
Analysis, InstantiationRefusal, Pruned, check_declarations, check_value, check_variant,
prune_analysis, resolve_profiles, select_template,
};
use rustev_contract::{
Document,
ids::DefinitionId,
parameter::{Lock, ParamDefault, ParameterDecl},
plan::{ParameterSource, ProfileEntry, Variant},
profile::Profile,
version::VersionRange,
};
use serde::Serialize;
use serde_json::Value;
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct ProfileReport {
pub definition: SelectedDefinition,
pub candidates: Vec<Candidate>,
pub chain: Vec<ProfileEntry>,
pub parameters: Vec<ParameterReport>,
pub locked: Vec<String>,
pub changes: Vec<String>,
pub variants: Vec<VariantReport>,
pub first_refused: Option<String>,
}
impl ProfileReport {
pub fn is_refused(&self) -> bool {
self.first_refused.is_some()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct SelectedDefinition {
pub name: String,
pub version: String,
pub definition_id: DefinitionId,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct Candidate {
pub version: String,
pub satisfies: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct ParameterReport {
#[serde(flatten)]
pub declaration: ParameterDecl,
pub value: Value,
pub source: ParameterSource,
pub changeable: Option<Lock>,
pub changed: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(untagged)]
pub enum VariantReport {
Pruned {
name: String,
pruned: Pruned,
},
Refused {
name: String,
refused: VariantRefusal,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct VariantRefusal {
#[serde(serialize_with = "instantiate_stage")]
pub stage: u8,
pub code: String,
pub subject: String,
pub detail: String,
}
fn instantiate_stage<S: serde::Serializer>(_: &u8, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str("instantiate")
}
pub fn profile_check(
definitions: &[&[u8]],
profile: Option<&[u8]>,
bases: &[&[u8]],
variant: Option<&str>,
pin: Option<&str>,
) -> Result<ProfileReport, InstantiationRefusal> {
let selection = select_template(definitions, profile, pin)?;
let declarations = check_declarations(&selection.template)?;
let overlay = resolve_profiles(&selection, &declarations, profile, bases)?;
let selected_variant = if variant.is_some() {
check_variant(&selection.template, variant)?
} else {
Variant::None
};
let range = profile.map(|p| {
Profile::parse(p)
.expect("validated profile")
.definition
.version
.parse::<VersionRange>()
.expect("validated range")
});
let mut report = ProfileReport {
definition: SelectedDefinition {
name: selection.template.name.clone(),
version: selection.template.version.to_string(),
definition_id: selection.template.id.clone(),
},
candidates: selection
.candidate_versions
.iter()
.map(|v| Candidate {
version: v.to_string(),
satisfies: range.as_ref().is_none_or(|r| r.satisfies(v)),
})
.collect(),
chain: overlay.chain.clone(),
parameters: declarations
.iter()
.zip(&overlay.parameters)
.map(|(d, p)| {
let changed = match &d.default {
ParamDefault::None => true,
ParamDefault::Value(v) => {
check_value(d, v).expect("validated default").0 != p.value
}
};
ParameterReport {
declaration: d.clone(),
value: p.value.clone(),
source: p.source.clone(),
changeable: (d.lock != Lock::Fixed).then_some(d.lock),
changed,
}
})
.collect(),
locked: declarations
.iter()
.filter(|d| d.lock == Lock::Fixed)
.map(|d| d.name.clone())
.collect(),
changes: overlay
.parameters
.iter()
.filter(|p| matches!(p.source, ParameterSource::Profile(_)))
.map(|p| p.name.clone())
.collect(),
variants: vec![],
first_refused: None,
};
let names = if variant.is_none() {
selection.template.json["variants"]
.as_array()
.cloned()
.unwrap_or_default()
} else {
vec![]
};
let mut analysis = Analysis {
selection,
declarations,
overlay,
variant: selected_variant,
};
for name in names {
let name = name.as_str().expect("validated variant").to_owned();
analysis.variant = Variant::Named(name.clone());
report.variants.push(match prune_analysis(&analysis) {
Ok(pruned) => VariantReport::Pruned { name, pruned },
Err(e) => {
report.first_refused.get_or_insert_with(|| name.clone());
VariantReport::Refused {
name,
refused: VariantRefusal {
stage: e.stage,
code: e.code.to_string(),
subject: e.subject,
detail: e.detail,
},
}
}
});
}
Ok(report)
}