[go: up one dir, main page]

haste-fhir-ops 0.3.0

Haste Health FHIR operations library.
use std::{pin::Pin, sync::Arc};

use haste_fhir_client::request::InvocationRequest;
use haste_fhir_model::r4::generated::resources::{Parameters, ParametersParameter, Resource};
use haste_fhir_operation_error::OperationOutcomeError;
use haste_jwt::{ProjectId, TenantId};

#[cfg(feature = "derive")]
pub mod derive;

pub enum Param<
    T: TryFrom<Vec<ParametersParameter>, Error = OperationOutcomeError>
        + Into<Vec<ParametersParameter>>,
> {
    Value(T),
    Parameters(Parameters),
}

impl<
    T: TryFrom<Vec<ParametersParameter>, Error = OperationOutcomeError>
        + Into<Vec<ParametersParameter>>,
> Param<T>
{
    pub fn as_parameters(self) -> Parameters {
        match self {
            Param::Value(v) => Parameters {
                parameter: Some(v.into()),
                ..Default::default()
            },
            Param::Parameters(p) => p,
        }
    }
}

pub trait OperationInvocation<CTX: Send>: Send + Sync {
    fn execute<'a>(
        &self,
        ctx: CTX,
        tenant: TenantId,
        project: ProjectId,
        request: &'a InvocationRequest,
    ) -> Pin<Box<dyn Future<Output = Result<Resource, OperationOutcomeError>> + Send + 'a>>;
    fn code<'a>(&'a self) -> &'a str;
}

pub struct OperationExecutor<
    CTX: Send,
    I: TryFrom<Vec<ParametersParameter>, Error = OperationOutcomeError>
        + Into<Vec<ParametersParameter>>
        + Send,
    O: TryFrom<Vec<ParametersParameter>, Error = OperationOutcomeError> + Into<Resource> + Send,
> {
    _ctx: std::marker::PhantomData<CTX>,
    code: String,
    executor: Arc<
        Box<
            dyn Fn(
                    CTX,
                    TenantId,
                    ProjectId,
                    &InvocationRequest,
                    I,
                )
                    -> Pin<Box<dyn Future<Output = Result<O, OperationOutcomeError>> + Send>>
                + Send
                + Sync,
        >,
    >,
}

impl<
    CTX: Send,
    I: TryFrom<Vec<ParametersParameter>, Error = OperationOutcomeError>
        + Into<Vec<ParametersParameter>>
        + Send,
    O: TryFrom<Vec<ParametersParameter>, Error = OperationOutcomeError> + Into<Resource> + Send,
> OperationExecutor<CTX, I, O>
{
    pub fn new(
        code: String,
        executor: Box<
            dyn Fn(
                    CTX,
                    TenantId,
                    ProjectId,
                    &InvocationRequest,
                    I,
                )
                    -> Pin<Box<dyn Future<Output = Result<O, OperationOutcomeError>> + Send>>
                + Send
                + Sync,
        >,
    ) -> Self {
        Self {
            _ctx: std::marker::PhantomData,
            executor: Arc::new(executor),
            code,
        }
    }
}

impl<
    CTX: Send + Sync + 'static,
    I: TryFrom<Vec<ParametersParameter>, Error = OperationOutcomeError>
        + Into<Vec<ParametersParameter>>
        + Send
        + 'static,
    O: TryFrom<Vec<ParametersParameter>, Error = OperationOutcomeError>
        + Into<Resource>
        + Send
        + 'static,
> OperationInvocation<CTX> for OperationExecutor<CTX, I, O>
{
    fn execute<'a>(
        &self,
        ctx: CTX,
        tenant: TenantId,
        project: ProjectId,
        request: &'a InvocationRequest,
    ) -> Pin<Box<dyn Future<Output = Result<Resource, OperationOutcomeError>> + Send + 'a>> {
        let executor = self.executor.clone();
        Box::pin(async move {
            let parameters = match request {
                InvocationRequest::Instance(instance_request) => &instance_request.parameters,
                InvocationRequest::Type(type_request) => &type_request.parameters,
                InvocationRequest::System(system_request) => &system_request.parameters,
            };

            let input = I::try_from(parameters.parameter.clone().unwrap_or_default())?;

            let output = (executor)(ctx, tenant, project, request, input).await?;

            Ok(output.into())
        })
    }

    fn code<'a>(&'a self) -> &'a str {
        &self.code
    }
}