mssf-core 0.8.0

Rust for Azure Service Fabric. Rust safe APIs.
Documentation
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------

use mssf_com::FabricClient::{
    IFabricServiceEndpointsVersion, IFabricServiceNotification,
    IFabricServiceNotificationEventHandler, IFabricServiceNotificationEventHandler_Impl,
};

use crate::types::{ServicePartitionInformation, Uri};

use super::svc_mgmt_client::ResolvedServiceEndpoint;

/// Rust trait to turn rust code into IFabricServiceNotificationEventHandler.
/// Not exposed to user
pub trait ServiceNotificationEventHandler: 'static {
    fn on_notification(&self, notification: ServiceNotification) -> crate::Result<()>;
}

/// Content of the service notification callback.
/// Remarks:
/// If endpoint list is empty, the service is removed.
#[derive(Debug, Clone)]
pub struct ServiceNotification {
    pub service_name: Uri,
    pub partition_info: Option<ServicePartitionInformation>,
    pub partition_id: crate::GUID,
    pub endpoints: Vec<ResolvedServiceEndpoint>,
    pub version: Option<ServiceEndpointsVersion>,
}

impl From<&IFabricServiceNotification> for ServiceNotification {
    fn from(com: &IFabricServiceNotification) -> Self {
        // SF guarantees this is not null.
        let raw = unsafe { com.get_Notification().as_ref().unwrap() };
        let endpoints = crate::iter::vec_from_raw_com(raw.EndpointCount as usize, raw.Endpoints);
        let version = unsafe { com.GetVersion() }
            .ok()
            .map(ServiceEndpointsVersion::from);
        Self {
            service_name: Uri::from(raw.ServiceName),
            partition_info: unsafe {
                // It is possible for partition info to be null,
                // that is why we make the field as an option.
                // See: https://github.com/microsoft/service-fabric/blob/93545a62e8f6c2407bd538c0f092b33419f77c30/src/prod/src/client/ServiceNotificationResult.cpp#L120
                raw.PartitionInfo
                    .as_ref()
                    .map(ServicePartitionInformation::from)
            },
            partition_id: raw.PartitionId,
            endpoints,
            version,
        }
    }
}

/// IFabricServiceEndpointsVersion wrapper.
#[derive(Debug, Clone)]
pub struct ServiceEndpointsVersion {
    com: IFabricServiceEndpointsVersion,
}

impl From<IFabricServiceEndpointsVersion> for ServiceEndpointsVersion {
    fn from(com: IFabricServiceEndpointsVersion) -> Self {
        Self { com }
    }
}

impl From<ServiceEndpointsVersion> for IFabricServiceEndpointsVersion {
    fn from(value: ServiceEndpointsVersion) -> Self {
        value.com
    }
}

impl ServiceEndpointsVersion {
    /// CSharp doc: Zero if this and other are equivalent,
    /// a negative value if this is less than other, and a positive value if this is greater than other.
    ///
    /// This is not usually used in CSharp apps, but the implementation is provided here for completeness.
    /// Ideally one should use mssf_core::client::svc_mgmt_client::ResolvedServicePartition instead, by
    /// doing an additional FabricClient resolve call to retrieve from FabricClient cache.
    pub fn compare(&self, other: &ServiceEndpointsVersion) -> crate::Result<i32> {
        unsafe { self.com.Compare(&other.com) }.map_err(crate::Error::from)
    }
}

impl PartialEq for ServiceEndpointsVersion {
    fn eq(&self, other: &Self) -> bool {
        match self.compare(other) {
            Ok(i) => i == 0,
            Err(_) => false, // error comparing different services
        }
    }
}

impl PartialOrd for ServiceEndpointsVersion {
    /// Compare the version of the resolved result.
    /// a > b means partial_cmp(a,b) == Some(Greater) i.e. a.compare_version(b) > 0.
    /// a is newer and up to date.
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        match self.compare(other) {
            Ok(i) => Some(i.cmp(&0)),
            // If you compare version of different service you get error
            Err(_) => None,
        }
    }
}

// Bridge implementation for the notification handler to turn rust code into SF com object.
#[windows_core::implement(IFabricServiceNotificationEventHandler)]
#[allow(non_camel_case_types)] // Suppress lint for _Impl struct
pub struct ServiceNotificationEventHandlerBridge<T>
where
    T: ServiceNotificationEventHandler,
{
    inner: T,
}

impl<T> ServiceNotificationEventHandlerBridge<T>
where
    T: ServiceNotificationEventHandler,
{
    pub fn new(inner: T) -> Self {
        Self { inner }
    }

    pub fn new_com(inner: T) -> IFabricServiceNotificationEventHandler {
        Self::new(inner).into()
    }
}

impl<T> IFabricServiceNotificationEventHandler_Impl
    for ServiceNotificationEventHandlerBridge_Impl<T>
where
    T: ServiceNotificationEventHandler,
{
    fn OnNotification(
        &self,
        notification: windows_core::Ref<IFabricServiceNotification>,
    ) -> crate::WinResult<()> {
        let com = notification.unwrap();
        let msg = ServiceNotification::from(com);
        self.inner
            .on_notification(msg)
            .map_err(crate::WinError::from)
    }
}

/// Lambda implemnentation of ServiceNotificationEventHandler trait.
/// This is used in FabricClientBuilder to build function into handler.
/// Not exposed to user.
/// This isn't strictly required by the implementation as written. But it leaves open the door to non-lambda implementations in future.
pub struct LambdaServiceNotificationHandler<T>
where
    T: Fn(ServiceNotification) -> crate::Result<()> + 'static,
{
    f: T,
}

impl<T> LambdaServiceNotificationHandler<T>
where
    T: Fn(ServiceNotification) -> crate::Result<()> + 'static,
{
    pub fn new(f: T) -> Self {
        Self { f }
    }
}

impl<T> ServiceNotificationEventHandler for LambdaServiceNotificationHandler<T>
where
    T: Fn(ServiceNotification) -> crate::Result<()> + 'static,
{
    fn on_notification(&self, notification: ServiceNotification) -> crate::Result<()> {
        (self.f)(notification)
    }
}