use std::sync::Arc;
use itertools::Itertools;
use olai_store::{AssociationStore, ObjectStore, ObjectStoreReader};
use uuid::Uuid;
use crate::models::{AssociationLabel, ObjectLabel, PropertyMap, Resource};
use crate::{Object, ResourceIdent, ResourceName, ResourceRef, Result};
fn json_to_property_map(value: serde_json::Value) -> Option<PropertyMap> {
match value {
serde_json::Value::Object(map) => Some(map.into_iter().collect()),
_ => None,
}
}
#[async_trait::async_trait]
pub trait ResourceStoreReader: Send + Sync + 'static {
async fn get(&self, id: &ResourceIdent) -> Result<(Resource, ResourceRef)>;
async fn get_many(&self, ids: &[ResourceIdent]) -> Result<Vec<(Resource, ResourceRef)>> {
let futures = ids.iter().map(|id| self.get(id)).collect_vec();
Ok(futures::future::try_join_all(futures).await?)
}
async fn list(
&self,
label: &ObjectLabel,
namespace: Option<&ResourceName>,
max_results: Option<usize>,
page_token: Option<String>,
) -> Result<(Vec<Resource>, Option<String>)>;
}
#[async_trait::async_trait]
pub trait ResourceStore: ResourceStoreReader + Send + Sync + 'static {
async fn create(&self, resource: Resource) -> Result<(Resource, ResourceRef)>;
async fn delete(&self, id: &ResourceIdent) -> Result<()>;
async fn update(
&self,
id: &ResourceIdent,
resource: Resource,
) -> Result<(Resource, ResourceRef)>;
async fn add_association(
&self,
from: &ResourceIdent,
to: &ResourceIdent,
label: &AssociationLabel,
properties: Option<PropertyMap>,
) -> Result<()>;
async fn remove_association(
&self,
from: &ResourceIdent,
to: &ResourceIdent,
label: &AssociationLabel,
) -> Result<()>;
async fn list_associations(
&self,
resource: &ResourceIdent,
label: &AssociationLabel,
target_label: Option<&ResourceIdent>,
max_results: Option<usize>,
page_token: Option<String>,
) -> Result<(Vec<ResourceIdent>, Option<String>)>;
async fn list_associations_with_properties(
&self,
resource: &ResourceIdent,
label: &AssociationLabel,
target_label: Option<&ResourceIdent>,
max_results: Option<usize>,
page_token: Option<String>,
) -> Result<(Vec<(ResourceIdent, Option<PropertyMap>)>, Option<String>)> {
let (idents, token) = self
.list_associations(resource, label, target_label, max_results, page_token)
.await?;
Ok((idents.into_iter().map(|i| (i, None)).collect(), token))
}
}
pub trait ProvidesResourceStore: Send + Sync + 'static {
fn store(&self) -> &dyn ResourceStore;
}
pub trait ProvidesObjectStore: Send + Sync + 'static {
fn object_store(&self) -> &dyn olai_store::ObjectStore<ObjectLabel>;
}
pub struct ObjectStoreAdapter<S> {
store: S,
}
impl<S> ObjectStoreAdapter<S> {
pub fn new(store: S) -> Self {
Self { store }
}
pub fn into_inner(self) -> S {
self.store
}
}
impl<S> ObjectStoreAdapter<S>
where
S: ObjectStoreReader<ObjectLabel>,
{
async fn resolve_ident(&self, id: &ResourceIdent) -> Result<Uuid> {
let (label, reference): (&ObjectLabel, &ResourceRef) = (id.as_ref(), id.as_ref());
match reference {
ResourceRef::Uuid(uuid) => Ok(*uuid),
ResourceRef::Name(name) => {
let object = self.store.get_by_name(*label, name).await?;
Ok(object.id)
}
ResourceRef::Undefined => {
Err(crate::Error::generic("Cannot resolve undefined resource"))
}
}
}
}
#[async_trait::async_trait]
impl<S> ResourceStoreReader for ObjectStoreAdapter<S>
where
S: ObjectStoreReader<ObjectLabel> + Send + Sync + 'static,
{
async fn get(&self, id: &ResourceIdent) -> Result<(Resource, ResourceRef)> {
let (label, reference): (&ObjectLabel, &ResourceRef) = (id.as_ref(), id.as_ref());
match reference {
ResourceRef::Uuid(uuid) => {
let object = self.store.get(uuid).await?;
Ok((object.try_into()?, ResourceRef::from(id)))
}
ResourceRef::Name(name) => {
let object = self.store.get_by_name(*label, name).await?;
let id_new = ResourceRef::Uuid(object.id);
Ok((object.try_into()?, id_new))
}
ResourceRef::Undefined => Err(crate::Error::generic("Cannot get undefined resource")),
}
}
async fn list(
&self,
label: &ObjectLabel,
namespace: Option<&ResourceName>,
max_results: Option<usize>,
page_token: Option<String>,
) -> Result<(Vec<Resource>, Option<String>)> {
let (objects, token) = self
.store
.list(*label, namespace, max_results, page_token)
.await?;
Ok((
objects
.into_iter()
.map(|object| object.try_into())
.try_collect()?,
token,
))
}
}
#[async_trait::async_trait]
impl<S> ResourceStore for ObjectStoreAdapter<S>
where
S: ObjectStore<ObjectLabel> + AssociationStore<ObjectLabel> + Send + Sync + 'static,
{
async fn create(&self, resource: Resource) -> Result<(Resource, ResourceRef)> {
let object: Object = resource.try_into()?;
let supplied_id = (!object.id.is_nil()).then_some(object.id);
let created = self
.store
.create(object.label, &object.name, object.properties, supplied_id)
.await?;
let id = ResourceRef::Uuid(created.id);
Ok((created.try_into()?, id))
}
async fn delete(&self, id: &ResourceIdent) -> Result<()> {
let uuid = self.resolve_ident(id).await?;
self.store.delete(&uuid).await?;
Ok(())
}
async fn update(
&self,
id: &ResourceIdent,
resource: Resource,
) -> Result<(Resource, ResourceRef)> {
let uuid = self.resolve_ident(id).await?;
let object: Object = resource.try_into()?;
let updated = self.store.update(&uuid, object.properties).await?;
Ok((updated.try_into()?, uuid.into()))
}
async fn add_association(
&self,
from: &ResourceIdent,
to: &ResourceIdent,
label: &AssociationLabel,
properties: Option<PropertyMap>,
) -> Result<()> {
let from_id = self.resolve_ident(from).await?;
let to_id = self.resolve_ident(to).await?;
let props = properties.map(|p| serde_json::Value::Object(p.into_iter().collect()));
self.store
.add(from_id, to_id, label.as_ref(), props)
.await?;
Ok(())
}
async fn remove_association(
&self,
from: &ResourceIdent,
to: &ResourceIdent,
label: &AssociationLabel,
) -> Result<()> {
let from_id = self.resolve_ident(from).await?;
let to_id = self.resolve_ident(to).await?;
self.store.remove(from_id, to_id, label.as_ref()).await?;
Ok(())
}
async fn list_associations(
&self,
resource: &ResourceIdent,
label: &AssociationLabel,
target_label: Option<&ResourceIdent>,
max_results: Option<usize>,
page_token: Option<String>,
) -> Result<(Vec<ResourceIdent>, Option<String>)> {
let resource_id = self.resolve_ident(resource).await?;
let target_obj_label = target_label.map(|r| *r.label());
let (associations, token) = olai_store::AssociationStoreReader::list(
&self.store,
resource_id,
label.as_ref(),
target_obj_label,
max_results,
page_token,
)
.await?;
let idents = associations
.into_iter()
.map(|assoc| assoc.to_label.to_ident(assoc.to_id))
.collect();
Ok((idents, token))
}
async fn list_associations_with_properties(
&self,
resource: &ResourceIdent,
label: &AssociationLabel,
target_label: Option<&ResourceIdent>,
max_results: Option<usize>,
page_token: Option<String>,
) -> Result<(Vec<(ResourceIdent, Option<PropertyMap>)>, Option<String>)> {
let resource_id = self.resolve_ident(resource).await?;
let target_obj_label = target_label.map(|r| *r.label());
let (associations, token) = olai_store::AssociationStoreReader::list(
&self.store,
resource_id,
label.as_ref(),
target_obj_label,
max_results,
page_token,
)
.await?;
let entries = associations
.into_iter()
.map(|assoc| {
let props = assoc.properties.and_then(json_to_property_map);
(assoc.to_label.to_ident(assoc.to_id), props)
})
.collect();
Ok((entries, token))
}
}
#[async_trait::async_trait]
impl<T: ResourceStoreReader> ResourceStoreReader for Arc<T> {
async fn get(&self, id: &ResourceIdent) -> Result<(Resource, ResourceRef)> {
T::get(self, id).await
}
async fn get_many(&self, ids: &[ResourceIdent]) -> Result<Vec<(Resource, ResourceRef)>> {
T::get_many(self, ids).await
}
async fn list(
&self,
label: &ObjectLabel,
namespace: Option<&ResourceName>,
max_results: Option<usize>,
page_token: Option<String>,
) -> Result<(Vec<Resource>, Option<String>)> {
T::list(self, label, namespace, max_results, page_token).await
}
}
#[async_trait::async_trait]
impl<T: ResourceStore> ResourceStore for Arc<T> {
async fn create(&self, resource: Resource) -> Result<(Resource, ResourceRef)> {
T::create(self, resource).await
}
async fn delete(&self, id: &ResourceIdent) -> Result<()> {
T::delete(self, id).await
}
async fn update(
&self,
id: &ResourceIdent,
resource: Resource,
) -> Result<(Resource, ResourceRef)> {
T::update(self, id, resource).await
}
async fn add_association(
&self,
from: &ResourceIdent,
to: &ResourceIdent,
label: &AssociationLabel,
properties: Option<PropertyMap>,
) -> Result<()> {
T::add_association(self, from, to, label, properties).await
}
async fn remove_association(
&self,
from: &ResourceIdent,
to: &ResourceIdent,
label: &AssociationLabel,
) -> Result<()> {
T::remove_association(self, from, to, label).await
}
async fn list_associations(
&self,
resource: &ResourceIdent,
label: &AssociationLabel,
target_label: Option<&ResourceIdent>,
max_results: Option<usize>,
page_token: Option<String>,
) -> Result<(Vec<ResourceIdent>, Option<String>)> {
T::list_associations(self, resource, label, target_label, max_results, page_token).await
}
async fn list_associations_with_properties(
&self,
resource: &ResourceIdent,
label: &AssociationLabel,
target_label: Option<&ResourceIdent>,
max_results: Option<usize>,
page_token: Option<String>,
) -> Result<(Vec<(ResourceIdent, Option<PropertyMap>)>, Option<String>)> {
T::list_associations_with_properties(
self,
resource,
label,
target_label,
max_results,
page_token,
)
.await
}
}
#[async_trait::async_trait]
impl<T: ProvidesResourceStore> ResourceStoreReader for T {
async fn get(&self, id: &ResourceIdent) -> Result<(Resource, ResourceRef)> {
self.store().get(id).await
}
async fn get_many(&self, ids: &[ResourceIdent]) -> Result<Vec<(Resource, ResourceRef)>> {
self.store().get_many(ids).await
}
async fn list(
&self,
label: &ObjectLabel,
namespace: Option<&ResourceName>,
max_results: Option<usize>,
page_token: Option<String>,
) -> Result<(Vec<Resource>, Option<String>)> {
self.store()
.list(label, namespace, max_results, page_token)
.await
}
}
#[async_trait::async_trait]
impl<T: ProvidesResourceStore> ResourceStore for T {
async fn create(&self, resource: Resource) -> Result<(Resource, ResourceRef)> {
self.store().create(resource).await
}
async fn delete(&self, id: &ResourceIdent) -> Result<()> {
self.store().delete(id).await
}
async fn update(
&self,
id: &ResourceIdent,
resource: Resource,
) -> Result<(Resource, ResourceRef)> {
self.store().update(id, resource).await
}
async fn add_association(
&self,
from: &ResourceIdent,
to: &ResourceIdent,
label: &AssociationLabel,
properties: Option<PropertyMap>,
) -> Result<()> {
self.store()
.add_association(from, to, label, properties)
.await
}
async fn remove_association(
&self,
from: &ResourceIdent,
to: &ResourceIdent,
label: &AssociationLabel,
) -> Result<()> {
self.store().remove_association(from, to, label).await
}
async fn list_associations(
&self,
resource: &ResourceIdent,
label: &AssociationLabel,
target_label: Option<&ResourceIdent>,
max_results: Option<usize>,
page_token: Option<String>,
) -> Result<(Vec<ResourceIdent>, Option<String>)> {
self.store()
.list_associations(resource, label, target_label, max_results, page_token)
.await
}
async fn list_associations_with_properties(
&self,
resource: &ResourceIdent,
label: &AssociationLabel,
target_label: Option<&ResourceIdent>,
max_results: Option<usize>,
page_token: Option<String>,
) -> Result<(Vec<(ResourceIdent, Option<PropertyMap>)>, Option<String>)> {
self.store()
.list_associations_with_properties(
resource,
label,
target_label,
max_results,
page_token,
)
.await
}
}