tripley-rpc-runtime-core 0.1.2

Core protocol types for Tripley RPC runtime envelopes and identifiers.
Documentation
use std::future::Future;
use std::num::NonZeroU64;
use std::pin::Pin;

use bitflags::bitflags;
use rmpv::Value;
use rpc_runtime_errors::RuntimeError;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

pub const RUNTIME_PROTOCOL_VERSION: u32 = 1;

pub type BoxRpcFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u8)]
pub enum MessageKind {
    Hello = 1,
    HelloAck = 2,
    Request = 3,
    ResponseOk = 4,
    ResponseError = 5,
    Notification = 6,
    Cancel = 7,
    Goodbye = 8,
}

impl MessageKind {
    pub const fn as_u8(self) -> u8 {
        self as u8
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u8)]
pub enum Role {
    Client = 1,
    Server = 2,
    Peer = 3,
}

impl Role {
    pub const fn as_u8(self) -> u8 {
        self as u8
    }
}

bitflags! {
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
    pub struct CapabilityFlags: u64 {
        const SERVER_TO_CLIENT_NOTIFICATION = 1 << 0;
        const NAMED_INSTANCE_RESOLUTION = 1 << 1;
        const SERVICE_ACTIVATION = 1 << 2;
        const GOODBYE = 1 << 3;
        const CANCEL_RESERVED = 1 << 4;
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct RequestId(u64);

impl RequestId {
    pub const fn new(value: u64) -> Self {
        Self(value)
    }

    pub const fn get(self) -> u64 {
        self.0
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct InstanceId(NonZeroU64);

impl InstanceId {
    pub fn new(value: u64) -> Option<Self> {
        NonZeroU64::new(value).map(Self)
    }

    pub const fn get(self) -> u64 {
        self.0.get()
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct MethodId(u32);

impl MethodId {
    pub const fn new(value: u32) -> Self {
        Self(value)
    }

    pub const fn get(self) -> u32 {
        self.0
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct NotificationId(u32);

impl NotificationId {
    pub const fn new(value: u32) -> Self {
        Self(value)
    }

    pub const fn get(self) -> u32 {
        self.0
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ServiceGuid(Uuid);

impl ServiceGuid {
    pub const fn new(value: Uuid) -> Self {
        Self(value)
    }

    pub const fn get(self) -> Uuid {
        self.0
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum Envelope {
    Hello(Hello),
    HelloAck(HelloAck),
    Request(Request),
    ResponseOk(ResponseOk),
    ResponseError(ResponseError),
    Notification(Notification),
    Goodbye(Goodbye),
}

pub type Options = Vec<(String, Value)>;
pub type Payload = Value;

#[derive(Debug, Clone, PartialEq)]
pub struct Hello {
    pub protocol_version: u32,
    pub role: Role,
    pub capability_bits: CapabilityFlags,
    pub max_message_size: u64,
    pub options: Options,
}

#[derive(Debug, Clone, PartialEq)]
pub struct HelloAck {
    pub protocol_version: u32,
    pub accepted_capability_bits: CapabilityFlags,
    pub max_message_size: u64,
    pub options: Options,
}

#[derive(Debug, Clone, PartialEq)]
pub struct Request {
    pub request_id: RequestId,
    pub instance_id: InstanceId,
    pub method_id: MethodId,
    pub payload: Payload,
}

#[derive(Debug, Clone, PartialEq)]
pub struct ResponseOk {
    pub request_id: RequestId,
    pub payload: Payload,
}

#[derive(Debug, Clone, PartialEq)]
pub struct ResponseError {
    pub request_id: RequestId,
    pub error_code: i32,
    pub error_kind: u8,
    pub error_message: Option<String>,
    pub error_details: Payload,
}

#[derive(Debug, Clone, PartialEq)]
pub struct Notification {
    pub instance_id: Option<InstanceId>,
    pub notification_id: NotificationId,
    pub payload: Payload,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Goodbye {
    pub reason_code: u32,
    pub message: Option<String>,
}

pub trait Dispatcher: Send + Sync {
    fn dispatch<'a>(
        &'a self,
        request: Request,
    ) -> BoxRpcFuture<'a, Result<ResponseOk, RuntimeError>>;
}

pub trait NotificationSink: Send + Sync {
    fn notify<'a>(
        &'a self,
        notification: Notification,
    ) -> BoxRpcFuture<'a, Result<(), RuntimeError>>;
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn message_kind_values_are_stable() {
        assert_eq!(MessageKind::Hello.as_u8(), 1);
        assert_eq!(MessageKind::HelloAck.as_u8(), 2);
        assert_eq!(MessageKind::Request.as_u8(), 3);
        assert_eq!(MessageKind::ResponseOk.as_u8(), 4);
        assert_eq!(MessageKind::ResponseError.as_u8(), 5);
        assert_eq!(MessageKind::Notification.as_u8(), 6);
        assert_eq!(MessageKind::Cancel.as_u8(), 7);
        assert_eq!(MessageKind::Goodbye.as_u8(), 8);
    }

    #[test]
    fn role_values_are_stable() {
        assert_eq!(Role::Client.as_u8(), 1);
        assert_eq!(Role::Server.as_u8(), 2);
        assert_eq!(Role::Peer.as_u8(), 3);
    }

    #[test]
    fn capability_bit_values_are_stable() {
        assert_eq!(CapabilityFlags::SERVER_TO_CLIENT_NOTIFICATION.bits(), 1);
        assert_eq!(CapabilityFlags::NAMED_INSTANCE_RESOLUTION.bits(), 2);
        assert_eq!(CapabilityFlags::SERVICE_ACTIVATION.bits(), 4);
        assert_eq!(CapabilityFlags::GOODBYE.bits(), 8);
        assert_eq!(CapabilityFlags::CANCEL_RESERVED.bits(), 16);
    }

    #[test]
    fn instance_id_rejects_zero() {
        assert_eq!(InstanceId::new(0), None);
        assert_eq!(InstanceId::new(7).map(InstanceId::get), Some(7));
    }
}