olai-uc-server 0.0.3

Unity Catalog REST server with pluggable storage backends.
Documentation
use itertools::Itertools;

use unitycatalog_common::models::functions::v1::*;
use unitycatalog_common::models::{ObjectLabel, ResourceIdent, ResourceName};

use super::{RequestContext, SecuredAction};
use crate::Result;
pub use crate::codegen::functions::FunctionHandler;
use crate::policy::{Permission, Policy, process_resources};
use crate::store::ResourceStore;

#[async_trait::async_trait]
impl<T: ResourceStore + Policy<RequestContext>> FunctionHandler<RequestContext> for T {
    #[tracing::instrument(skip(self, context), fields(resource_name))]
    async fn list_functions(
        &self,
        request: ListFunctionsRequest,
        context: RequestContext,
    ) -> Result<ListFunctionsResponse> {
        self.check_required(&request, &context).await?;
        let (mut resources, next_page_token) = self
            .list(
                &ObjectLabel::Function,
                Some(&ResourceName::new([
                    &request.catalog_name,
                    &request.schema_name,
                ])),
                request.max_results.map(|v| v as usize),
                request.page_token,
            )
            .await?;
        process_resources(self, &context, &Permission::Read, &mut resources).await?;
        Ok(ListFunctionsResponse {
            functions: 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 create_function(
        &self,
        request: CreateFunctionRequest,
        context: RequestContext,
    ) -> Result<Function> {
        self.check_required(&request, &context).await?;
        let function_info = request
            .function_info
            .ok_or_else(|| crate::Error::invalid_argument("function_info must be provided"))?;
        tracing::Span::current().record("resource_name", &function_info.name);
        let full_name = format!(
            "{}.{}.{}",
            function_info.catalog_name, function_info.schema_name, function_info.name
        );
        let resource = Function {
            name: function_info.name,
            catalog_name: function_info.catalog_name,
            schema_name: function_info.schema_name,
            full_name,
            data_type: function_info.data_type,
            full_data_type: function_info.full_data_type,
            input_params: Some(function_info.input_params.into_option().unwrap_or_default()).into(),
            parameter_style: function_info.parameter_style,
            is_deterministic: function_info.is_deterministic,
            sql_data_access: function_info.sql_data_access,
            is_null_call: function_info.is_null_call,
            security_type: function_info.security_type,
            routine_body: function_info.routine_body,
            routine_definition: function_info.routine_definition,
            routine_body_language: function_info.routine_body_language,
            comment: function_info.comment,
            properties: function_info.properties,
            ..Default::default()
        };
        Ok(self.create(resource.into()).await?.0.try_into()?)
    }

    #[tracing::instrument(skip(self, context), fields(resource_name))]
    async fn get_function(
        &self,
        request: GetFunctionRequest,
        context: RequestContext,
    ) -> Result<Function> {
        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_function(
        &self,
        request: UpdateFunctionRequest,
        context: RequestContext,
    ) -> Result<Function> {
        tracing::Span::current().record("resource_name", &request.name);
        self.check_required(&request, &context).await?;
        let ident = request.resource();
        let resource = Function {
            owner: request.owner,
            ..Default::default()
        };
        Ok(self.update(&ident, resource.into()).await?.0.try_into()?)
    }

    #[tracing::instrument(skip(self, context), fields(resource_name))]
    async fn delete_function(
        &self,
        request: DeleteFunctionRequest,
        context: RequestContext,
    ) -> Result<()> {
        tracing::Span::current().record("resource_name", &request.name);
        self.check_required(&request, &context).await?;
        Ok(self.delete(&request.resource()).await?)
    }
}

impl SecuredAction for CreateFunctionRequest {
    fn resource(&self) -> ResourceIdent {
        let info = self.function_info.as_option();
        ResourceIdent::function(ResourceName::new([
            info.map(|i| i.catalog_name.as_str()).unwrap_or_default(),
            info.map(|i| i.schema_name.as_str()).unwrap_or_default(),
            info.map(|i| i.name.as_str()).unwrap_or_default(),
        ]))
    }

    fn permission(&self) -> &'static Permission {
        &Permission::Create
    }
}

impl SecuredAction for ListFunctionsRequest {
    fn resource(&self) -> ResourceIdent {
        ResourceIdent::function(ResourceName::new([
            self.catalog_name.as_str(),
            self.schema_name.as_str(),
        ]))
    }

    fn permission(&self) -> &'static Permission {
        &Permission::Read
    }
}

impl SecuredAction for GetFunctionRequest {
    fn resource(&self) -> ResourceIdent {
        ResourceIdent::function(ResourceName::from_naive_str_split(self.name.as_str()))
    }

    fn permission(&self) -> &'static Permission {
        &Permission::Read
    }
}

impl SecuredAction for UpdateFunctionRequest {
    fn resource(&self) -> ResourceIdent {
        ResourceIdent::function(ResourceName::from_naive_str_split(self.name.as_str()))
    }

    fn permission(&self) -> &'static Permission {
        &Permission::Manage
    }
}

impl SecuredAction for DeleteFunctionRequest {
    fn resource(&self) -> ResourceIdent {
        ResourceIdent::function(ResourceName::from_naive_str_split(self.name.as_str()))
    }

    fn permission(&self) -> &'static Permission {
        &Permission::Manage
    }
}