use std::mem::take;
use std::sync::Arc;
use anyhow::Result;
use indexmap::IndexMap;
use std::marker::Send;
use std::marker::Sync;
use crate::{instance::*, repository::*};
#[derive(Clone)]
pub struct Class {
pub name: String,
pub attrs: IndexMap<String, Attr>,
pub subclasses: SubclassesSpecifier,
pub hierarchy: Vec<String>,
pub collects: Vec<CollectionSpecifier>,
pub html_body: Option<String>,
pub html_header: Option<String>,
}
#[derive(Clone, PartialEq)]
pub enum SubclassesSpecifier {
List(Vec<String>),
Var(String),
Empty(),
}
#[derive(Clone, PartialEq)]
pub struct CollectionSpecifier {
pub class_name: String,
pub virtual_attribute: Option<CollectionAttribute>,
}
#[derive(Clone, PartialEq)]
pub struct CollectionAttribute {
pub attr_name: String,
pub is_public: bool,
pub is_optional: bool,
pub is_array: bool,
}
#[derive(Clone)]
pub struct Attr {
pub cmd: std::sync::Arc<dyn AttrCommand + Sync + Send>,
pub is_public: bool,
pub is_optional: bool,
pub is_array: bool,
}
pub trait AttrCommand {
fn apply(
&self,
ctx: &mut Context,
instance: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
entity: &str,
) -> Result<()>;
fn revert(
&self,
ctx: &mut Context,
instance: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
entity: &str,
) -> Result<()>;
fn value(&self) -> Option<String> {
None
}
}
pub trait InjectCommand {
fn inject(
&self,
instance: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
caller: &str,
) -> Result<()>;
fn eject(
&self,
instance: &SandboxBuilder,
tx: &mut ReadWriteTransaction,
euid: &str,
caller: &str,
) -> Result<()>;
}
#[derive(Clone)]
pub struct Injectors {
pub prependers: Vec<Arc<dyn InjectCommand + Send + Sync>>,
pub appenders: Vec<Arc<dyn InjectCommand + Send + Sync>>,
}
pub struct ClassBuilder {
pub name: String,
pub parent: String,
pub attrs: IndexMap<String, Attr>,
pub subclasses: SubclassesSpecifier,
pub hierarchy: Vec<String>,
pub collects: Vec<CollectionSpecifier>,
pub html_body: Option<String>,
pub html_header: Option<String>,
expanded: bool,
}
impl ClassBuilder {
pub fn new() -> Self {
ClassBuilder {
name: String::new(),
parent: String::new(),
attrs: indexmap::IndexMap::new(),
subclasses: SubclassesSpecifier::Empty(),
hierarchy: vec![],
collects: vec![],
expanded: false,
html_body: None,
html_header: None,
}
}
pub fn name(&mut self, name: &str) -> &mut Self {
self.name = name.to_string();
if self.hierarchy.contains(&self.name) {
log::error!("Invalid hierarchy detected for class {}", name)
} else {
self.hierarchy.push(self.name.clone());
}
self
}
pub fn add_attr(&mut self, key: String, attr: Attr) -> &mut Self {
if self.attrs.contains_key(&key) {
let existing_attr = &self.attrs[&key];
if (
existing_attr.is_array,
existing_attr.is_public,
existing_attr.is_optional,
) != (attr.is_array, attr.is_public, attr.is_optional)
{
log::warn!(
"Overriding attribute {} in {} has conflicting metadata.",
key,
self.name
);
}
self.attrs[&key] = attr;
} else {
self.attrs.insert(key.to_owned(), attr);
}
self
}
pub fn html_body(&mut self, body: String) -> &mut Self {
self.html_body = Some(body);
self
}
pub fn html_header(&mut self, header: String) -> &mut Self {
self.html_header = Some(header);
self
}
pub fn subclass_item(&mut self, class_name_to_collect: &str) {
if let SubclassesSpecifier::List(subclasses_list) = &mut self.subclasses {
subclasses_list.push(class_name_to_collect.to_string());
} else {
self.subclasses = SubclassesSpecifier::List(vec![class_name_to_collect.to_string()]);
}
}
pub fn subclass_var(&mut self, class_name_to_collect: &str) {
self.subclasses = SubclassesSpecifier::Var(class_name_to_collect.to_string());
}
pub fn collect(&mut self, spec: CollectionSpecifier) {
self.collects.push(spec);
}
pub fn expand(
&mut self,
instance: &SandboxInstance,
expand_with_class_name: &str,
) -> &mut Self {
let expand_class = &instance.classes.get(expand_with_class_name).unwrap();
for (k, v) in &expand_class.attrs {
self.attrs.insert(k.to_string(), v.clone());
}
self.expanded = true;
self
}
pub fn extends(&mut self, instance: &SandboxInstance, parent_class_name: &str) -> &mut Self {
self.parent = parent_class_name.to_string();
let mut parent_class_name_mut = parent_class_name;
while !parent_class_name_mut.is_empty() {
self.hierarchy.push(parent_class_name_mut.to_string());
let parent_class = instance.classes.get(parent_class_name_mut).unwrap();
parent_class_name_mut = if parent_class.hierarchy.len() < 2 {
""
} else {
&parent_class.hierarchy[1]
};
if let Some(parent_html_body) = &parent_class.html_body {
self.html_body = Some(parent_html_body.clone());
}
if let Some(parent_html_header) = &parent_class.html_header {
self.html_header = Some(parent_html_header.clone());
}
self.collects = parent_class.collects.clone();
}
self
}
pub fn conclude(&mut self, instance: &SandboxInstance) -> &mut Self {
if !self.expanded && !self.parent.is_empty() {
let my_attrs = take(&mut self.attrs);
self.expand(instance, &self.parent.clone());
self.attrs.extend(my_attrs);
}
self
}
pub fn build(self) -> Class {
Class {
name: self.name,
attrs: self.attrs,
subclasses: self.subclasses,
hierarchy: self.hierarchy,
collects: self.collects,
html_body: self.html_body,
html_header: self.html_header,
}
}
}
impl Default for ClassBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, PartialEq)]
pub struct AppendPayload<'a> {
pub class_override: Option<&'a str>,
pub appended_uid: Option<String>,
}
#[derive(Debug, PartialEq)]
pub struct RerollPayload<'a> {
pub existing_uid: String,
pub class_override: Option<&'a str>,
pub new_uid: Option<String>,
}
#[derive(Debug, PartialEq)]
pub enum Context<'a> {
Rolling,
Appending(AppendPayload<'a>),
Rerolling(RerollPayload<'a>),
Unrolling,
Restoring,
}