use async_trait::async_trait;
use energyml_core::reflect::TypeInfo;
use energyml_core::registry::DynObject;
use crate::array::{ArrayData, ArrayLocation, ArrayMetadata};
use crate::error::Result;
use crate::resource::{Dataspace, ObjectHeader, ObjectSummary};
#[cfg(target_arch = "wasm32")]
pub use async_trait::async_trait as storage_async_trait;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct Contents {
pub objects: Vec<ObjectSummary>,
pub edges: Vec<crate::graph::Edge>,
}
#[allow(clippy::double_must_use)]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
pub trait EnergymlStorage {
fn store_kind(&self) -> &'static str;
async fn dataspaces(&self) -> Result<Vec<Dataspace>>;
async fn resources(&self) -> Result<Vec<ObjectSummary>>;
async fn contents(&self) -> Result<Contents> {
Ok(Contents::default())
}
async fn headers(&self, uris: &[String]) -> Vec<Result<ObjectHeader>>;
async fn get_objects(&self, uris: &[String]) -> Vec<Result<Box<dyn DynObject>>>;
async fn supported_types(&self) -> Vec<&'static TypeInfo>;
fn arrays_of(&self, object: &dyn energyml_core::value::Reflect) -> Vec<ArrayLocation>;
async fn array_metadata(&self, location: &ArrayLocation) -> Result<ArrayMetadata>;
async fn read_array(&self, location: &ArrayLocation) -> Result<ArrayData>;
fn diagnostics(&self) -> Vec<crate::error::Diagnostic>;
fn capabilities(&self) -> crate::capabilities::Capabilities {
crate::capabilities::Capabilities::default()
}
async fn get_raw(
&self,
uris: &[String],
format: crate::raw::RawFormat,
) -> Vec<Result<crate::raw::RawObject>> {
let _ = format;
uris.iter()
.map(|_| {
Err(crate::error::Error::Unsupported {
operation: "reading objects as bytes",
store: self.store_kind(),
})
})
.collect()
}
async fn resources_in(&self, context: &crate::context::Context) -> Result<crate::context::Found> {
let dataspaces = self.dataspaces().await?;
let [dataspace] = dataspaces.as_slice() else {
return Err(crate::error::Error::Unsupported {
operation: "walking a context over several dataspaces",
store: self.store_kind(),
});
};
let mut resources = self.resources().await?;
let mut edges = self.edges(&[]).await?;
if self.capabilities().contained_objects {
let contents = self.contents().await?;
resources.extend(contents.objects);
edges.extend(contents.edges);
}
crate::context::discover(&dataspace.uri, resources, edges, context)
}
async fn deleted_resources(
&self,
filter: &crate::context::DeletedFilter,
) -> Result<Vec<crate::context::DeletedResource>> {
let _ = filter;
Err(crate::error::Error::Unsupported {
operation: "listing deleted objects",
store: self.store_kind(),
})
}
async fn get_object(&self, uri: &str) -> Result<Box<dyn DynObject>> {
let mut results = self.get_objects(std::slice::from_ref(&uri.to_string())).await;
results
.pop()
.unwrap_or_else(|| Err(crate::error::Error::NotFound(uri.to_string())))
}
async fn header(&self, uri: &str) -> Result<ObjectHeader> {
let mut results = self.headers(std::slice::from_ref(&uri.to_string())).await;
results
.pop()
.unwrap_or_else(|| Err(crate::error::Error::NotFound(uri.to_string())))
}
async fn references(&self, uris: &[String]) -> Result<Vec<crate::graph::Edge>> {
let contents = if self.capabilities().contained_objects {
self.contents().await?
} else {
Contents::default()
};
let by_uri: std::collections::HashMap<&str, &ObjectSummary> = contents
.objects
.iter()
.map(|entry| (entry.uri.as_str(), entry))
.collect();
let mut parts: Vec<String> = Vec::new();
for uri in uris {
match by_uri.get(uri.as_str()) {
Some(entry) => parts.extend(crate::graph::parts_holding(&by_uri, entry)),
None => parts.push(uri.clone()),
}
}
if !uris.is_empty() && parts.is_empty() {
return Ok(Vec::new());
}
let held = read_parts(self, &parts).await?;
let graph = crate::graph::build(
held.iter()
.map(|(summary, object)| (summary, object.as_reflect())),
);
let written = crate::graph::attribute_to_writers(
graph.edges,
held.iter()
.map(|(summary, object)| (summary, object.as_reflect())),
&contents.objects,
);
if uris.is_empty() {
return Ok(written);
}
Ok(written
.into_iter()
.filter(|edge| {
uris.contains(&edge.from)
|| by_uri
.get(edge.from.as_str())
.is_some_and(|entry| uris.iter().any(|uri| crate::graph::within(&by_uri, entry, uri)))
})
.collect())
}
async fn edges(&self, uris: &[String]) -> Result<Vec<crate::graph::Edge>> {
let held: std::collections::HashSet<String> = self
.resources()
.await?
.into_iter()
.map(|summary| summary.uri)
.collect();
let held_edges: Vec<crate::graph::Edge> = self
.references(uris)
.await?
.into_iter()
.filter(|edge| held.contains(&edge.to))
.collect();
Ok(match self.kind_rules() {
Some(rules) => crate::kinds::judge_edges(rules.as_ref(), held_edges),
None => held_edges,
})
}
fn kind_rules(&self) -> Option<std::sync::Arc<dyn crate::kinds::KindRules>> {
None
}
async fn graph(&self, uris: &[String]) -> Result<crate::graph::Graph> {
let all = self.resources().await?;
let mut wanted: Vec<ObjectSummary> = if uris.is_empty() {
all
} else {
all.into_iter().filter(|s| uris.contains(&s.uri)).collect()
};
let mut edges = self.references(uris).await?;
if self.capabilities().contained_objects {
let contents = self.contents().await?;
let by_uri: std::collections::HashMap<&str, &ObjectSummary> = contents
.objects
.iter()
.map(|entry| (entry.uri.as_str(), entry))
.collect();
let drawn: Vec<ObjectSummary> = contents
.objects
.iter()
.filter(|entry| {
uris.is_empty()
|| uris.contains(&entry.uri)
|| uris.iter().any(|uri| crate::graph::within(&by_uri, entry, uri))
})
.cloned()
.collect();
let drawn_uris: std::collections::HashSet<&str> =
drawn.iter().map(|entry| entry.uri.as_str()).collect();
edges.extend(
contents
.edges
.iter()
.filter(|edge| drawn_uris.contains(edge.from.as_str()))
.cloned(),
);
wanted.extend(drawn);
}
let of_wanted: Vec<String> = wanted.iter().map(|s| s.uri.clone()).collect();
let headers = self.headers(&of_wanted).await;
let titles: std::collections::HashMap<String, String> = headers
.into_iter()
.flatten()
.filter_map(|header| {
header
.title
.filter(|t| !t.trim().is_empty())
.map(|title| (header.summary.uri, title))
})
.collect();
Ok(crate::graph::assemble(
wanted.iter(),
&|uri: &str| titles.get(uri).cloned(),
edges,
))
}
async fn derived_graph(&self, uris: &[String]) -> Result<crate::graph::Graph> {
let held = read_parts(self, uris).await?;
Ok(crate::graph::build(
held.iter()
.map(|(summary, object)| (summary, object.as_reflect())),
))
}
}
async fn read_parts<S: EnergymlStorage + ?Sized>(
store: &S,
uris: &[String],
) -> Result<Vec<(ObjectSummary, Box<dyn DynObject>)>> {
let all = store.resources().await?;
let wanted: Vec<ObjectSummary> = if uris.is_empty() {
all
} else {
all.into_iter().filter(|s| uris.contains(&s.uri)).collect()
};
let of_wanted: Vec<String> = wanted.iter().map(|s| s.uri.clone()).collect();
let objects = store.get_objects(&of_wanted).await;
Ok(wanted
.into_iter()
.zip(objects)
.filter_map(|(summary, object)| object.ok().map(|object| (summary, object)))
.collect())
}
pub trait LocalPackage {
fn attachments(&self) -> Vec<String>;
fn read_attachment(&self, part: &str) -> Result<Vec<u8>>;
fn relationships(&self, part: &str) -> Result<Vec<crate::resource::Relationship>>;
}