use std::{
collections::HashMap,
ffi::OsString,
fs::File,
io::BufReader,
path::{Component, Path, PathBuf},
sync::Arc,
};
use anyhow::bail;
use ron_pfnsec_fork::ser::PrettyConfig;
use serde::{Deserialize, Serialize};
use async_trait::async_trait;
use crate::{bundle::BundleOutput, diag::DiagnosticOutput, template::ReadOutput, util::RON};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum OutputValuePlan {
Set,
Delete,
}
pub type OutputValueExec = Option<String>;
pub type OutputMapPlan = HashMap<String, OutputValuePlan>;
pub type OutputMapExec = HashMap<String, OutputValueExec>;
pub type OutputMap = HashMap<String, String>;
#[derive(Serialize, Deserialize)]
pub enum OutputMapFile {
PointerToVirtual(PathBuf),
OutputMap(OutputMap),
}
impl OutputMapFile {
pub fn path(prefix: &Path, addr: &Path) -> PathBuf {
let mut output = prefix.to_path_buf();
output.push(".outputs");
if let Some(parent) = addr.parent() {
for comp in parent.components() {
if let Component::Normal(_) = comp {
output.push(comp)
}
}
}
let mut new_filename = OsString::new();
if let Some(fname) = addr.file_name() {
new_filename.push(fname);
} else {
}
new_filename.push(".out.ron");
output.push(new_filename);
output
}
pub fn read(prefix: &Path, addr: &Path) -> anyhow::Result<Option<Self>> {
let output_path = Self::path(prefix, addr);
if output_path.is_file() {
let file = File::open(&output_path)?;
let reader = BufReader::new(file);
let output: Self = RON.from_reader(reader)?;
return Ok(Some(output));
}
Ok(None)
}
pub fn read_recurse(prefix: &Path, addr: &Path) -> anyhow::Result<Option<Self>> {
let output_path = Self::path(prefix, addr);
if output_path.is_file() {
let file = File::open(&output_path)?;
let reader = BufReader::new(file);
let output: Self = RON.from_reader(reader)?;
match &output {
OutputMapFile::PointerToVirtual(virt_addr) => {
return Self::read_recurse(prefix, virt_addr);
}
OutputMapFile::OutputMap(_) => return Ok(Some(output)),
}
}
Ok(None)
}
pub fn write(&self, prefix: &Path, addr: &Path) -> anyhow::Result<PathBuf> {
let output_path = Self::path(prefix, addr);
let pretty_config = PrettyConfig::default();
let contents = RON.to_string_pretty(self, pretty_config)?;
if let Some(parent) = output_path.parent() {
std::fs::create_dir_all(parent)?;
}
if output_path.exists() {
std::fs::remove_file(&output_path)?;
}
std::fs::write(&output_path, contents)?;
Ok(output_path)
}
pub fn write_recurse(&self, prefix: &Path, addr: &Path) -> anyhow::Result<()> {
let output_path = Self::path(prefix, addr);
if output_path.is_file() {
let contents = std::fs::read_to_string(&output_path)?;
let output: Self = RON.from_str(&contents)?;
match &output {
OutputMapFile::PointerToVirtual(virtual_address) => {
return self.write_recurse(prefix, virtual_address);
}
OutputMapFile::OutputMap(_) => {
if let Some(parent) = output_path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(&output_path, RON.to_string_pretty(self, PrettyConfig::default())?)?;
}
}
} else {
if let Some(parent) = output_path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(&output_path, RON.to_string_pretty(self, PrettyConfig::default())?)?;
}
Ok(())
}
pub fn resolve(prefix: &Path, addr: &Path) -> anyhow::Result<Option<VirtualAddress>> {
let Some(output) = Self::read(prefix, addr)? else {
return Ok(None);
};
match output {
OutputMapFile::PointerToVirtual(virtual_address) => Self::resolve(prefix, &virtual_address),
OutputMapFile::OutputMap(_) => Ok(Some(VirtualAddress(addr.to_path_buf()))),
}
}
pub fn get(prefix: &Path, addr: &Path, key: &str) -> anyhow::Result<Option<String>> {
let Some(output) = Self::read(prefix, addr)? else {
return Ok(None);
};
match output {
OutputMapFile::PointerToVirtual(virtual_address) => Self::get(prefix, &virtual_address, key),
OutputMapFile::OutputMap(map) => Ok(map.get(key).cloned()),
}
}
pub fn apply_output_map(prefix: &Path, addr: &Path, output_map_exec: &OutputMapExec) -> anyhow::Result<Option<PathBuf>> {
let original = Self::read_recurse(prefix, addr)?.unwrap_or(OutputMapFile::OutputMap(HashMap::new()));
let OutputMapFile::OutputMap(mut original_map) = original else {
bail!(
"apply_output_map({}, {}): resolved to a link file!",
prefix.display(),
addr.display()
);
};
for (key, value) in output_map_exec {
match value {
Some(value) => {
original_map.insert(key.clone(), value.clone());
}
None => {
original_map.remove(key);
}
}
}
if original_map.is_empty() {
Ok(None)
} else {
OutputMapFile::OutputMap(original_map).write_recurse(prefix, addr)?;
Ok(Some(Self::path(prefix, addr)))
}
}
pub fn write_link(prefix: &Path, phy_addr: &Path, virt_addr: &Path) -> anyhow::Result<PathBuf> {
let output_map = Self::PointerToVirtual(virt_addr.to_path_buf());
output_map.write(prefix, phy_addr)?;
Ok(Self::path(prefix, phy_addr))
}
pub fn delete(prefix: &Path, addr: &Path) -> anyhow::Result<Option<PathBuf>> {
let path = Self::path(prefix, addr);
if path.is_file() {
std::fs::remove_file(&path)?;
Ok(Some(path))
} else {
Ok(None)
}
}
}
pub mod parse;
pub mod spawn;
pub mod r#type;
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Copy, Clone)]
pub enum FilterOutput {
Config,
Resource,
Bundle,
None,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct VirtualAddress(pub PathBuf);
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PhysicalAddress(pub PathBuf);
#[derive(Debug, Serialize, Deserialize)]
pub struct GetResourceOutput {
pub resource_definition: Vec<u8>,
pub outputs: Option<OutputMap>,
}
impl GetResourceOutput {
pub async fn write(self, prefix: &Path, phy_addr: &Path, virt_addr: &Path) -> anyhow::Result<Vec<PathBuf>> {
let mut res = Vec::new();
let body = self.resource_definition;
let res_path = prefix.join(virt_addr);
if let Some(parent) = res_path.parent() {
tokio::fs::create_dir_all(parent).await?;
}
tokio::fs::write(&res_path, body).await?;
res.push(res_path);
if let Some(outputs) = self.outputs
&& !outputs.is_empty() {
let output_map_file = OutputMapFile::OutputMap(outputs);
res.push(output_map_file.write(prefix, virt_addr)?);
if virt_addr != phy_addr {
res.push(OutputMapFile::write_link(prefix, phy_addr, virt_addr)?);
}
}
Ok(res)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DocIdent {
Struct { name: String },
Field { parent: String, name: String },
}
#[derive(Debug, Serialize, Deserialize)]
pub struct GetDocOutput {
pub markdown: String,
}
impl From<&'static str> for GetDocOutput {
fn from(value: &'static str) -> Self {
Self {
markdown: value.to_string(),
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct OpPlanOutput {
pub op_definition: String,
pub writes_outputs: Vec<String>,
pub friendly_message: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OpExecOutput {
pub outputs: Option<OutputMapExec>,
pub friendly_message: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TaskExecOutput {
pub outputs: Option<HashMap<String, Option<String>>>,
pub friendly_message: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SkeletonOutput {
pub addr: PathBuf,
pub body: Vec<u8>,
}
pub type ConnectorOutbox = tokio::sync::broadcast::Sender<Option<String>>;
pub type ConnectorInbox = tokio::sync::broadcast::Receiver<Option<String>>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListOutput {
pub addr: PathBuf,
pub body: Option<Vec<u8>>,
}
pub type ListResultOutbox = tokio::sync::mpsc::Sender<ListOutput>;
pub type ListResultInbox = tokio::sync::mpsc::Receiver<ListOutput>;
#[derive(Debug, Serialize, Deserialize)]
pub enum VirtToPhyOutput {
NotPresent,
Deferred(Vec<ReadOutput>),
Present(PathBuf),
Null(PathBuf),
}
#[async_trait]
pub trait Connector: Send + Sync {
async fn new(name: &str, prefix: &Path, outbox: ConnectorOutbox) -> Result<Arc<dyn Connector>, anyhow::Error>
where
Self: Sized;
async fn init(&self) -> Result<(), anyhow::Error>;
async fn filter(&self, addr: &Path) -> Result<FilterOutput, anyhow::Error>;
async fn list(&self, subpath: &Path) -> anyhow::Result<Vec<PathBuf>>;
async fn subpaths(&self) -> anyhow::Result<Vec<PathBuf>> {
Ok(vec![PathBuf::from("./")])
}
async fn get(&self, addr: &Path) -> Result<Option<GetResourceOutput>, anyhow::Error>;
async fn plan(
&self,
addr: &Path,
current: Option<Vec<u8>>,
desired: Option<Vec<u8>>,
) -> Result<Vec<OpPlanOutput>, anyhow::Error>;
async fn op_exec(&self, addr: &Path, op: &str) -> Result<OpExecOutput, anyhow::Error>;
async fn addr_virt_to_phy(&self, addr: &Path) -> Result<VirtToPhyOutput, anyhow::Error> {
Ok(VirtToPhyOutput::Null(addr.into()))
}
async fn addr_phy_to_virt(&self, addr: &Path) -> Result<Option<PathBuf>, anyhow::Error> {
Ok(Some(addr.into()))
}
async fn get_skeletons(&self) -> Result<Vec<SkeletonOutput>, anyhow::Error> {
Ok(Vec::new())
}
async fn get_docstring(&self, _addr: &Path, _ident: DocIdent) -> anyhow::Result<Option<GetDocOutput>> {
Ok(None)
}
async fn eq(&self, addr: &Path, a: &[u8], b: &[u8]) -> Result<bool, anyhow::Error>;
async fn diag(&self, addr: &Path, a: &[u8]) -> Result<DiagnosticOutput, anyhow::Error>;
async fn unbundle(&self, _addr: &Path, _bundle: &[u8]) -> anyhow::Result<Vec<BundleOutput>> {
Ok(Vec::new())
}
}
pub trait Resource: Send + Sync {
fn to_bytes(&self) -> Result<Vec<u8>, anyhow::Error>;
fn from_bytes(addr: &impl ResourceAddress, s: &[u8]) -> Result<Self, anyhow::Error>
where
Self: Sized;
}
pub trait ResourceAddress: Send + Sync + Clone + std::fmt::Debug {
fn to_path_buf(&self) -> PathBuf;
fn from_path(path: &Path) -> Result<Self, anyhow::Error>
where
Self: Sized;
fn get_output(&self, prefix: &Path, key: &str) -> anyhow::Result<Option<String>> {
let addr = self.to_path_buf();
OutputMapFile::get(prefix, &addr, key)
}
fn phy_to_virt(&self, prefix: &Path) -> anyhow::Result<Option<Self>> {
let Some(virt_addr) = OutputMapFile::resolve(prefix, &self.to_path_buf())? else {
return Ok(None);
};
Ok(Some(Self::from_path(&virt_addr.0)?))
}
}
pub trait ConnectorOp: Send + Sync + std::fmt::Debug {
fn to_string(&self) -> Result<String, anyhow::Error>;
fn from_str(s: &str) -> Result<Self, anyhow::Error>
where
Self: Sized;
fn friendly_plan_message(&self) -> Option<String> {
None
}
fn friendly_exec_message(&self) -> Option<String> {
None
}
}
#[async_trait]
impl Connector for Arc<dyn Connector> {
async fn new(name: &str, prefix: &Path, outbox: ConnectorOutbox) -> Result<Arc<dyn Connector>, anyhow::Error> {
return Self::new(name, prefix, outbox).await;
}
async fn init(&self) -> Result<(), anyhow::Error> {
Connector::init(self.as_ref()).await
}
async fn filter(&self, addr: &Path) -> Result<FilterOutput, anyhow::Error> {
Connector::filter(self.as_ref(), addr).await
}
async fn list(&self, subpath: &Path) -> anyhow::Result<Vec<PathBuf>> {
Connector::list(self.as_ref(), subpath).await
}
async fn subpaths(&self) -> anyhow::Result<Vec<PathBuf>> {
Connector::subpaths(self.as_ref()).await
}
async fn get(&self, addr: &Path) -> Result<Option<GetResourceOutput>, anyhow::Error> {
Connector::get(self.as_ref(), addr).await
}
async fn plan(
&self,
addr: &Path,
current: Option<Vec<u8>>,
desired: Option<Vec<u8>>,
) -> Result<Vec<OpPlanOutput>, anyhow::Error> {
Connector::plan(self.as_ref(), addr, current, desired).await
}
async fn op_exec(&self, addr: &Path, op: &str) -> Result<OpExecOutput, anyhow::Error> {
Connector::op_exec(self.as_ref(), addr, op).await
}
async fn addr_virt_to_phy(&self, addr: &Path) -> Result<VirtToPhyOutput, anyhow::Error> {
Connector::addr_virt_to_phy(self.as_ref(), addr).await
}
async fn addr_phy_to_virt(&self, addr: &Path) -> Result<Option<PathBuf>, anyhow::Error> {
Connector::addr_phy_to_virt(self.as_ref(), addr).await
}
async fn get_docstring(&self, addr: &Path, ident: DocIdent) -> Result<Option<GetDocOutput>, anyhow::Error> {
Connector::get_docstring(self.as_ref(), addr, ident).await
}
async fn get_skeletons(&self) -> Result<Vec<SkeletonOutput>, anyhow::Error> {
Connector::get_skeletons(self.as_ref()).await
}
async fn eq(&self, addr: &Path, a: &[u8], b: &[u8]) -> Result<bool, anyhow::Error> {
Connector::eq(self.as_ref(), addr, a, b).await
}
async fn diag(&self, addr: &Path, a: &[u8]) -> Result<DiagnosticOutput, anyhow::Error> {
Connector::diag(self.as_ref(), addr, a).await
}
async fn unbundle(&self, addr: &Path, bundle: &[u8]) -> anyhow::Result<Vec<BundleOutput>> {
Connector::unbundle(self.as_ref(), addr, bundle).await
}
}