packages feed

temporal-sdk-core-2025.10.1.0: rust/src/ephemeral_server.rs

use crate::runtime::{Capability, HsCallback, MVar, Runtime, RuntimeRef};
use crate::worker::{CUnit, Unit};
use ffi_convert::*;
use serde::Deserialize;
use std::ffi::{c_char, CStr};
use std::time::Duration;
use temporal_sdk_core::ephemeral_server::*;

pub struct EphemeralServerRef {
    pub(crate) server: EphemeralServer,
    runtime: Runtime,
}

impl RawPointerConverter<EphemeralServerRef> for EphemeralServerRef {
    fn into_raw_pointer(self) -> *const EphemeralServerRef {
        convert_into_raw_pointer(self)
    }

    fn into_raw_pointer_mut(self) -> *mut EphemeralServerRef {
        convert_into_raw_pointer_mut(self)
    }

    unsafe fn from_raw_pointer(
        ptr: *const EphemeralServerRef,
    ) -> Result<Self, UnexpectedNullPointerError> {
        unsafe { take_back_from_raw_pointer(ptr) }
    }

    unsafe fn from_raw_pointer_mut(
        ptr: *mut EphemeralServerRef,
    ) -> Result<Self, UnexpectedNullPointerError> {
        unsafe { take_back_from_raw_pointer_mut(ptr) }
    }
}

/// Where to find an executable. Can be a path or download.
#[derive(Deserialize)]
#[serde(tag = "type", content = "contents", remote = "EphemeralExe")]
pub enum EphemeralExeDef {
    /// Existing path on the filesystem for the executable.
    ExistingPath(String),
    /// Download the executable if not already there.
    CachedDownload {
        /// Which version to download.
        #[serde(with = "EphemeralExeVersionDef")]
        version: EphemeralExeVersion,
        /// Destination directory or the user temp directory if none set.
        dest_dir: Option<String>,
        ttl: Option<Duration>
    },
}

/// Which version of the exe to download.
#[derive(Deserialize)]
#[serde(tag = "type", content = "contents", remote = "EphemeralExeVersion")]
pub enum EphemeralExeVersionDef {
    /// Use a default version for the given SDK name and version.
    SDKDefault {
        /// Name of the SDK to get the default for.
        sdk_name: String,
        /// Version of the SDK to get the default for.
        sdk_version: String,
    },
    /// Specific version.
    Fixed(String),
}

#[derive(Deserialize)]
#[serde(remote = "TemporalDevServerConfig")]
pub struct TemporalDevServerConfigDef {
    /// Required path to executable or download info.
    #[serde(with = "EphemeralExeDef")]
    pub exe: EphemeralExe,
    /// Namespace to use.
    pub namespace: String,
    /// IP to bind to.
    pub ip: String,
    /// Port to use or obtains a free one if none given.
    pub port: Option<u16>,
    /// Sqlite DB filename if persisting or non-persistent if none.
    pub db_filename: Option<String>,
    /// Whether to enable the UI.
    pub ui: bool,
    /// What port to run the UI on.
    pub ui_port: Option<u16>,
    /// Log format and level
    pub log: (String, String),
    /// Additional arguments to Temporalite.
    pub extra_args: Vec<String>,
}

// TODO: [publish-crate]
/// # Safety
///
/// Haskell FFI bridge invariants.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn hs_temporal_start_dev_server(
    runtime: *mut RuntimeRef,
    json_string: *const c_char,
    mvar: *mut MVar,
    cap: Capability,
    error_slot: *mut *mut CArray<u8>,
    result_slot: *mut *mut EphemeralServerRef,
) {
    let runtime_ref = unsafe { runtime.as_ref().unwrap() };
    let runtime = &runtime_ref.runtime;
    let mut de = serde_json::Deserializer::from_str(unsafe {
        std::str::from_utf8_unchecked(CStr::from_ptr(json_string).to_bytes())
    });
    let conf =
        TemporalDevServerConfigDef::deserialize(&mut de).expect("Failed to deserialize config");
    let hs: HsCallback<EphemeralServerRef, CArray<u8>> = HsCallback {
        cap,
        mvar,
        error_slot,
        result_slot,
    };
    runtime.future_result_into_hs(hs, async move {
        let result = conf.start_server().await;
        match result {
            Ok(server) => Ok(EphemeralServerRef {
                server,
                runtime: runtime.clone(),
            }),
            Err(e) => Err(
                CArray::c_repr_of(format!("Failed to start server: {}", e).into_bytes())
                    .expect("Failed to convert error to CArray"),
            ),
        }
    })
}

// TODO: [publish-crate]
/// # Safety
///
/// Haskell FFI bridge invariants.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn hs_temporal_shutdown_ephemeral_server(
    server: *mut EphemeralServerRef,
    mvar: *mut MVar,
    cap: Capability,
    error_slot: *mut *mut CArray<u8>,
    result_slot: *mut *mut CUnit,
) {
    let server_ref = unsafe { Box::from_raw(server) };
    let mut server = server_ref.server;
    let hs: HsCallback<CUnit, CArray<u8>> = HsCallback {
        cap,
        mvar,
        error_slot,
        result_slot,
    };
    server_ref.runtime.future_result_into_hs(hs, async move {
        let result = server.shutdown().await;
        match result {
            Ok(()) => Ok(CUnit::c_repr_of(Unit {}).unwrap()),
            Err(e) => Err(CArray::c_repr_of(
                format!("Failed to shutdown server: {}", e).into_bytes(),
            )
            .expect("Failed to convert error to CArray")),
        }
    })
}

/// Configuration for the test server.
#[derive(Deserialize)]
#[serde(remote = "TestServerConfig")]
pub struct TestServerConfigDef {
    /// Required path to executable or download info.
    #[serde(with = "EphemeralExeDef")]
    pub exe: EphemeralExe,
    /// Port to use or obtains a free one if none given.
    pub port: Option<u16>,
    /// Additional arguments to the test server.
    pub extra_args: Vec<String>,
}

// TODO: [publish-crate]
/// # Safety
///
/// Haskell FFI bridge invariants.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn hs_temporal_start_test_server(
    runtime: *mut RuntimeRef,
    json_string: *const c_char,
    mvar: *mut MVar,
    cap: Capability,
    error_slot: *mut *mut CArray<u8>,
    result_slot: *mut *mut EphemeralServerRef,
) {
    let runtime_ref = unsafe { runtime.as_ref().unwrap() };
    let runtime = &runtime_ref.runtime;
    let mut de = serde_json::Deserializer::from_str(unsafe {
        std::str::from_utf8_unchecked(CStr::from_ptr(json_string).to_bytes())
    });
    let conf = TestServerConfigDef::deserialize(&mut de).expect("Failed to deserialize config");
    let hs: HsCallback<EphemeralServerRef, CArray<u8>> = HsCallback {
        cap,
        mvar,
        error_slot,
        result_slot,
    };
    runtime.future_result_into_hs(hs, async move {
        let result = conf.start_server().await;
        match result {
            Ok(server) => Ok(EphemeralServerRef {
                server,
                runtime: runtime.clone(),
            }),
            Err(e) => Err(
                CArray::c_repr_of(format!("Failed to start server: {}", e).into_bytes())
                    .expect("Failed to convert error to CArray"),
            ),
        }
    })
}