use core::panic;
use std::collections::{btree_map, BTreeMap, HashSet};
use borsh::{BorshDeserialize, BorshSerialize};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use super::Link;
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct TemplateInput {
type_link: Link,
offset: usize,
}
impl TemplateInput {
pub fn type_link(&self) -> &Link {
&self.type_link
}
pub fn offset(&self) -> &usize {
&self.offset
}
}
#[derive(Debug, Default, Clone, BorshSerialize, BorshDeserialize)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct TransactionTemplate {
preencoded_bytes: Vec<u8>,
inputs: Vec<(String, TemplateInput)>,
}
impl TransactionTemplate {
pub fn from_bytes(preencoded_bytes: Vec<u8>) -> Self {
Self {
preencoded_bytes,
inputs: vec![],
}
}
pub fn from_input(name: String, field_index: usize) -> Self {
Self {
inputs: vec![(
name,
TemplateInput {
type_link: Link::IndexedPlaceholder(field_index),
offset: 0,
},
)],
preencoded_bytes: vec![],
}
}
pub fn preencoded_bytes(&self) -> &[u8] {
&self.preencoded_bytes
}
pub fn inputs(&self) -> &[(String, TemplateInput)] {
&self.inputs
}
pub fn concat(templates: Vec<Self>) -> Self {
let mut ret = Self::default();
let mut prev_bytes_len = 0usize;
for t in templates {
ret.preencoded_bytes.extend(t.preencoded_bytes);
for (name, TemplateInput { type_link, offset }) in t.inputs {
if ret.inputs.iter().any(|input| input.0 == name) {
panic!("Schema transaction template contained duplicate input binding name: {name}");
}
ret.inputs.push((
name,
TemplateInput {
type_link,
offset: offset + prev_bytes_len,
},
));
}
prev_bytes_len = ret.preencoded_bytes.len();
}
ret
}
pub fn prepend_discriminant(&mut self, discriminant: u8) {
self.preencoded_bytes.insert(0, discriminant);
for (_, TemplateInput { offset, .. }) in self.inputs.iter_mut() {
*offset += 1;
}
}
}
#[derive(Debug, Default, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct AttributeAndChildTemplateSet {
pub attribute_templates: TransactionTemplateSet,
pub type_templates: TransactionTemplateSet,
}
#[derive(Debug, Default, Clone, BorshSerialize, BorshDeserialize)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct TransactionTemplateSet(pub BTreeMap<String, TransactionTemplate>);
impl TransactionTemplateSet {
pub fn fill_links(
field_templates: Vec<(String, TransactionTemplate)>,
child_links: Vec<Link>,
) -> Self {
let mut attribute_templates = BTreeMap::<String, TransactionTemplate>::default();
for (name, mut template) in field_templates.into_iter() {
for (_, input) in template.inputs.iter_mut() {
match input.type_link {
Link::IndexedPlaceholder(i) => input.type_link = child_links[i].clone(),
Link::Placeholder => panic!("Templates should not have unindexed Placeholder links. This is a bug in a hand-written implementation."),
_ => ()
}
}
if let btree_map::Entry::Vacant(e) = attribute_templates.entry(name.clone()) {
e.insert(template);
} else {
panic!("Duplicate template definitions (name: \"{name}\" in attributes. This should never happen (macro should have already errored out).")
}
}
Self(attribute_templates)
}
pub fn concatenate_template_sets(
template_sets: Vec<AttributeAndChildTemplateSet>,
type_name_for_diagnostics: &'static str,
) -> Self {
let mut final_template_set = Self::default();
let attribute_template_names_set: HashSet<_> = template_sets
.iter()
.flat_map(|t| t.attribute_templates.0.keys().cloned())
.collect();
let type_template_names_set: HashSet<_> = template_sets
.iter()
.flat_map(|t| t.type_templates.0.keys().cloned())
.collect();
let type_only_names_set: HashSet<_> = type_template_names_set
.difference(&attribute_template_names_set)
.cloned()
.collect();
let mut zipped_templates: Vec<_> = template_sets.into_iter().map(|mut separated| {
for (name, template) in separated.type_templates.0.into_iter() {
if let btree_map::Entry::Vacant(e) = separated.attribute_templates.0.entry(name.clone()) {
e.insert(template);
} else {
panic!("Field type's template definitions for \"{}\" overlap with field's own template annotations.", name);
}
}
separated.attribute_templates
}).collect();
fn collect_concated_templates<F: Fn(&String)>(
names: HashSet<String>,
all_templates: &mut [TransactionTemplateSet],
panic_if_needed: F,
) -> TransactionTemplateSet {
let mut ret = TransactionTemplateSet::default();
'template: for name in names {
let mut template_vec = Vec::<TransactionTemplate>::new();
for template_set in all_templates.iter_mut() {
match template_set.0.remove(&name) {
None => {
panic_if_needed(&name);
break 'template;
}
Some(template) => template_vec.push(template),
}
}
ret.0
.insert(name, TransactionTemplate::concat(template_vec));
}
ret
}
final_template_set.0.extend(collect_concated_templates(attribute_template_names_set, &mut zipped_templates, |name| {
panic!("Partial template transaction definition! Metadata for transaction \"{}\" is found on some, but not all, fields of data type {}.\nDouble-check the transaction template attributes and child type definitions. Any template defined on an attribute on any one field must be present for every field, either directly from annotations or from a field's type", name, type_name_for_diagnostics);
}).0);
final_template_set.0.extend(
collect_concated_templates(type_only_names_set, &mut zipped_templates, |_| {}).0,
);
final_template_set
}
pub fn filter_enum_variant_templates(
mut self,
filter: Vec<String>,
inherit_all: bool,
variant_name_for_diagnostics: &'static str,
) -> Self {
let mut filter_set: HashSet<String> = HashSet::from_iter(filter);
self.0
.retain(|name, _| filter_set.remove(name) || inherit_all);
if !filter_set.is_empty() {
panic!("Enum variant {variant_name_for_diagnostics} specified template \"{}\" which was not defined on the variant's fields", filter_set.iter().next().unwrap());
}
self
}
pub fn merge_enum_template_sets(
template_sets: Vec<TransactionTemplateSet>,
type_name_for_diagnostics: &'static str,
) -> Self {
let mut final_template_set = Self::default();
for (discriminant, set) in template_sets.into_iter().enumerate() {
for (name, mut template) in set.0.into_iter() {
if final_template_set.0.contains_key(&name) {
panic!("Different variants of the enum {} both contained definitions for the transaction template \"{}\". At this time, only one enum branch can be defined for any transaction template.", type_name_for_diagnostics, name);
}
template.prepend_discriminant(
discriminant
.try_into()
.expect("Enum discriminants are only supported up to size u8"),
);
final_template_set.0.insert(name, template);
}
}
final_template_set
}
}