use serde::{Deserialize, Serialize};
use super::{ClauseStatus, ConformanceEntry, ProjectIndex, RfcPhase, RfcStatus};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConformanceMeta {
pub id: String,
pub title: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tags: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RequirementBinding {
#[serde(rename = "ref")]
pub clause_ref: String,
pub version: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConformanceContent {
pub path: String,
pub selector: String,
pub requirements: Vec<RequirementBinding>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub guards: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConformanceSpec {
pub govctl: ConformanceMeta,
pub case: ConformanceContent,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum ConformanceApplicability {
Stale,
Provisional,
Candidate,
Current,
}
impl std::fmt::Display for ConformanceApplicability {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let value = match self {
Self::Stale => "stale",
Self::Provisional => "provisional",
Self::Candidate => "candidate",
Self::Current => "current",
};
formatter.write_str(value)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct ConformanceTraceRequirement {
#[serde(rename = "ref")]
pub clause_ref: String,
pub version: String,
pub requirement_applicability: ConformanceApplicability,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct ConformanceTraceCase {
pub id: String,
pub title: String,
pub tags: Vec<String>,
pub path: String,
pub selector: String,
pub requirements: Vec<ConformanceTraceRequirement>,
pub guards: Vec<String>,
pub requirement_applicability: ConformanceApplicability,
}
pub fn derive_conformance_trace(
entry: &ConformanceEntry,
index: &ProjectIndex,
) -> ConformanceTraceCase {
let mut requirements = entry
.spec
.case
.requirements
.iter()
.map(|binding| ConformanceTraceRequirement {
clause_ref: binding.clause_ref.clone(),
version: binding.version.clone(),
requirement_applicability: requirement_applicability(binding, index),
})
.collect::<Vec<_>>();
requirements.sort_by(|left, right| {
left.clause_ref
.cmp(&right.clause_ref)
.then_with(|| left.version.cmp(&right.version))
});
let mut tags = entry.meta().tags.clone();
tags.sort();
tags.dedup();
let mut guards = entry.spec.case.guards.clone();
guards.sort();
guards.dedup();
let requirement_applicability = requirements
.iter()
.map(|requirement| requirement.requirement_applicability)
.min()
.unwrap_or(ConformanceApplicability::Stale);
ConformanceTraceCase {
id: entry.meta().id.clone(),
title: entry.meta().title.clone(),
tags,
path: entry.spec.case.path.clone(),
selector: entry.spec.case.selector.clone(),
requirements,
guards,
requirement_applicability,
}
}
fn requirement_applicability(
binding: &RequirementBinding,
index: &ProjectIndex,
) -> ConformanceApplicability {
let Some((rfc_id, clause_id)) = binding.clause_ref.split_once(':') else {
return ConformanceApplicability::Stale;
};
let Some(rfc) = index.rfcs.iter().find(|entry| entry.rfc.rfc_id == rfc_id) else {
return ConformanceApplicability::Stale;
};
let Some(clause) = rfc
.clauses
.iter()
.find(|entry| entry.spec.clause_id == clause_id)
else {
return ConformanceApplicability::Stale;
};
if clause.spec.status != ClauseStatus::Active || binding.version != rfc.rfc.version {
return ConformanceApplicability::Stale;
}
match (rfc.rfc.status, rfc.rfc.phase, clause.spec.since.is_some()) {
(RfcStatus::Draft, _, _) => ConformanceApplicability::Provisional,
(RfcStatus::Normative, RfcPhase::Spec, true) => ConformanceApplicability::Candidate,
(RfcStatus::Normative, RfcPhase::Impl | RfcPhase::Test | RfcPhase::Stable, true) => {
ConformanceApplicability::Current
}
_ => ConformanceApplicability::Stale,
}
}