use itertools::Itertools;
use std::collections::HashMap;
use unitycatalog_common::models::ObjectLabel;
use unitycatalog_common::models::shares::v1::*;
use unitycatalog_common::models::{ResourceIdent, ResourceName, ResourceRef};
use super::{RequestContext, SecuredAction};
pub use crate::codegen::shares::ShareHandler;
use crate::policy::{Permission, Policy, process_resources};
use crate::store::ResourceStore;
use crate::{Error, Result};
#[async_trait::async_trait]
impl<T: ResourceStore + Policy<RequestContext>> ShareHandler<RequestContext> for T {
#[tracing::instrument(skip(self, context), fields(resource_name))]
async fn create_share(
&self,
request: CreateShareRequest,
context: RequestContext,
) -> Result<Share> {
tracing::Span::current().record("resource_name", &request.name);
self.check_required(&request, &context).await?;
let resource = Share {
name: request.name,
comment: request.comment,
..Default::default()
};
Ok(self.create(resource.into()).await?.0.try_into()?)
}
#[tracing::instrument(skip(self, context), fields(resource_name))]
async fn delete_share(
&self,
request: DeleteShareRequest,
context: RequestContext,
) -> Result<()> {
tracing::Span::current().record("resource_name", &request.name);
self.check_required(&request, &context).await?;
Ok(self.delete(&request.resource()).await?)
}
#[tracing::instrument(skip(self, context))]
async fn list_shares(
&self,
request: ListSharesRequest,
context: RequestContext,
) -> Result<ListSharesResponse> {
self.check_required(&request, &context).await?;
let (mut resources, next_page_token) = self
.list(
&ObjectLabel::Share,
None,
request.max_results.map(|v| v as usize),
request.page_token,
)
.await?;
process_resources(self, &context, &Permission::Read, &mut resources).await?;
Ok(ListSharesResponse {
shares: resources.into_iter().map(|r| r.try_into()).try_collect()?,
next_page_token,
..Default::default()
})
}
#[tracing::instrument(skip(self, context), fields(resource_name))]
async fn get_share(&self, request: GetShareRequest, context: RequestContext) -> Result<Share> {
tracing::Span::current().record("resource_name", &request.name);
self.check_required(&request, &context).await?;
Ok(self.get(&request.resource()).await?.0.try_into()?)
}
#[tracing::instrument(skip(self, context), fields(resource_name))]
async fn update_share(
&self,
request: UpdateShareRequest,
context: RequestContext,
) -> Result<Share> {
tracing::Span::current().record("resource_name", &request.name);
self.check_required(&request, &context).await?;
let ident = request.resource();
let current: Share = self.get(&ident).await?.0.try_into()?;
let mut data_objects: HashMap<String, DataObject> = current
.objects
.into_iter()
.map(|d| (d.name.clone(), d))
.collect();
for update in request.updates.iter() {
match update.action.as_known().unwrap_or_default() {
Action::ADD => {
if let Some(obj) = update.data_object.as_option() {
if data_objects.contains_key(&obj.name) {
return Err(Error::AlreadyExists);
}
data_objects.insert(obj.name.clone(), obj.clone());
}
}
Action::REMOVE => {
if let Some(obj) = update.data_object.as_option() {
data_objects.remove(&obj.name);
}
}
Action::UPDATE => {
if let Some(obj) = update.data_object.as_option() {
if let Some(existing) = data_objects.get_mut(&obj.name) {
*existing = obj.clone();
} else {
return Err(Error::NotFound);
}
}
}
Action::ACTION_UNSPECIFIED => {
return Err(Error::InvalidArgument("Unspecified action".to_string()));
}
}
}
let resource = Share {
name: request.new_name.unwrap_or_else(|| request.name.clone()),
comment: request.comment.or(current.comment),
owner: request.owner.or(current.owner),
objects: data_objects.into_values().collect(),
..Default::default()
};
Ok(self.update(&ident, resource.into()).await?.0.try_into()?)
}
#[tracing::instrument(skip(self, context), fields(resource_name))]
async fn get_permissions(
&self,
request: GetPermissionsRequest,
context: RequestContext,
) -> Result<GetPermissionsResponse> {
tracing::Span::current().record("resource_name", &request.name);
self.check_required(&request, &context).await?;
let _ = self.get(&request.resource()).await?; Ok(GetPermissionsResponse {
privilege_assignments: vec![],
next_page_token: None,
..Default::default()
})
}
#[tracing::instrument(skip(self, context), fields(resource_name))]
async fn update_permissions(
&self,
request: UpdatePermissionsRequest,
context: RequestContext,
) -> Result<UpdatePermissionsResponse> {
tracing::Span::current().record("resource_name", &request.name);
self.check_required(&request, &context).await?;
let _ = self.get(&request.resource()).await?; Ok(UpdatePermissionsResponse {
privilege_assignments: vec![],
..Default::default()
})
}
}
impl SecuredAction for CreateShareRequest {
fn resource(&self) -> ResourceIdent {
ResourceIdent::share(ResourceName::new([self.name.as_str()]))
}
fn permission(&self) -> &'static Permission {
&Permission::Create
}
}
impl SecuredAction for ListSharesRequest {
fn resource(&self) -> ResourceIdent {
ResourceIdent::share(ResourceRef::Undefined)
}
fn permission(&self) -> &'static Permission {
&Permission::Read
}
}
impl SecuredAction for GetShareRequest {
fn resource(&self) -> ResourceIdent {
ResourceIdent::share(ResourceName::new([self.name.as_str()]))
}
fn permission(&self) -> &'static Permission {
&Permission::Read
}
}
impl SecuredAction for UpdateShareRequest {
fn resource(&self) -> ResourceIdent {
ResourceIdent::share(ResourceName::new([self.name.as_str()]))
}
fn permission(&self) -> &'static Permission {
&Permission::Manage
}
}
impl SecuredAction for DeleteShareRequest {
fn resource(&self) -> ResourceIdent {
ResourceIdent::share(ResourceName::new([self.name.as_str()]))
}
fn permission(&self) -> &'static Permission {
&Permission::Manage
}
}
impl SecuredAction for GetPermissionsRequest {
fn resource(&self) -> ResourceIdent {
ResourceIdent::share(ResourceName::new([self.name.as_str()]))
}
fn permission(&self) -> &'static Permission {
&Permission::Read
}
}
impl SecuredAction for UpdatePermissionsRequest {
fn resource(&self) -> ResourceIdent {
ResourceIdent::share(ResourceName::new([self.name.as_str()]))
}
fn permission(&self) -> &'static Permission {
&Permission::Manage
}
}