use std::borrow::Cow;
use std::collections::BTreeSet;
use serde::Serialize;
use serde_json::Value;
use crate::attributes::{
Boundary, ConservationKind, QaKind, QualityTarget, TechnicalBinding, VerificationFate,
};
use crate::canonical::{Digest, Domain, digest};
use crate::ids::NodeId;
use crate::names::TagName;
use crate::node::Node;
use crate::tree::{
Component, Constraint, Criterion, Details, Feature, FeatureGroup, Product, QualityAttribute,
Requirement, RootComponent, Section,
};
pub(crate) type Visit<'a> = dyn FnMut(Option<NodeId>, &NodeView<'_>, Digest) + 'a;
#[derive(Serialize)]
pub(crate) struct NodeView<'a>(
&'static str,
Cow<'a, str>,
Option<&'a str>,
BTreeSet<&'a str>,
Option<QaKind>,
Option<&'a QualityTarget>,
Option<&'a TechnicalBinding>,
Option<ConservationKind>,
Option<VerificationFate>,
Option<Boundary>,
&'a [Digest],
);
impl NodeView<'_> {
pub(crate) fn to_value(&self) -> Value {
serde_json::to_value(self).expect("a content view serialises")
}
}
struct Fields<'a> {
text: Cow<'a, str>,
body: Option<&'a str>,
tags: &'a [TagName],
qa_kind: Option<QaKind>,
quality_target: Option<&'a QualityTarget>,
binding: Option<&'a TechnicalBinding>,
conservation_kind: Option<ConservationKind>,
verification_fate: Option<VerificationFate>,
boundary: Option<Boundary>,
}
impl<'a> Fields<'a> {
const fn bare(text: Cow<'a, str>, body: Option<&'a str>, tags: &'a [TagName]) -> Self {
Self {
text,
body,
tags,
qa_kind: None,
quality_target: None,
binding: None,
conservation_kind: None,
verification_fate: None,
boundary: None,
}
}
fn plain(text: impl Into<Cow<'a, str>>, details: &'a Details) -> Self {
Self::bare(text.into(), details.body.as_deref(), &details.tags)
}
fn of(node: Node<'a>) -> Self {
match node {
Node::Product(node) => Self::plain(&node.name, &node.details),
Node::FeatureGroup(node) => Self::plain(&node.name, &node.details),
Node::Feature(node) => Self::plain(&node.name, &node.details),
Node::Requirement(node) => Self::plain(node.statement.render(), &node.details),
Node::Criterion(node) => Self::plain(&node.name, &node.details),
Node::Component(node) => Self {
boundary: node.boundary,
..Self::plain(&node.name, &node.details)
},
Node::Constraint(node) => Self {
binding: node.binding.as_ref(),
conservation_kind: node.conservation_kind,
verification_fate: node.verification_fate,
..Self::plain(&node.name, &node.details)
},
Node::QualityAttribute(node) => Self {
qa_kind: Some(node.kind),
quality_target: Some(&node.target),
verification_fate: node.verification_fate,
..Self::plain(&node.name, &node.details)
},
Node::Section(node) => Self::bare(Cow::Borrowed(&node.name), Some(&node.body), &[]),
}
}
}
pub trait ContentHash {
fn node(&self) -> Node<'_>;
fn content_view(&self) -> Value {
let node = self.node();
let children = child_hashes(node, &mut |_, _, _| {});
view(node, &children).to_value()
}
fn content_hash(&self) -> Digest {
let node = self.node();
digest(
Domain::Node,
&view(node, &child_hashes(node, &mut |_, _, _| {})),
)
}
}
impl ContentHash for RootComponent {
fn node(&self) -> Node<'_> {
Node::Component(&self.component)
}
}
impl ContentHash for Node<'_> {
fn node(&self) -> Node<'_> {
*self
}
}
macro_rules! content_hash {
($($kind:ident),*) => {$(
impl ContentHash for $kind {
fn node(&self) -> Node<'_> {
Node::$kind(self)
}
}
)*};
}
content_hash!(
Product,
FeatureGroup,
Feature,
Requirement,
Criterion,
Component,
Constraint,
QualityAttribute,
Section
);
fn view<'a>(node: Node<'a>, children: &'a [Digest]) -> NodeView<'a> {
let fields = Fields::of(node);
NodeView(
node.kind().as_str(),
fields.text,
fields.body,
fields.tags.iter().map(TagName::as_str).collect(),
fields.qa_kind,
fields.quality_target,
fields.binding,
fields.conservation_kind,
fields.verification_fate,
fields.boundary,
children,
)
}
fn child_hashes(node: Node<'_>, visit: &mut Visit<'_>) -> Vec<Digest> {
node.children()
.into_iter()
.map(|child| hash_with(child, visit))
.collect()
}
pub(crate) fn hash_with(node: Node<'_>, visit: &mut Visit<'_>) -> Digest {
let children = child_hashes(node, visit);
let view = view(node, &children);
let hash = digest(Domain::Node, &view);
visit(node.id(), &view, hash);
hash
}