iscp-rs 1.1.2

iSCPv2 Client Library
Documentation
//! iSCPメッセージ型の定義

#[rustfmt::skip]
#[allow(clippy::all)]
mod proto {
    include!("proto/iscp2.v1.rs");

    #[path = "iscp2.v1.extensions.rs"]
    pub mod extensions;
}

pub use proto::*;

impl DataId {
    pub fn new<SN: ToString, ST: ToString>(name: SN, type_: ST) -> Self {
        Self {
            name: name.to_string(),
            type_: type_.to_string(),
        }
    }

    pub fn validate(&self) -> Result<(), DataIdParseError> {
        let list = ['#', '+', ':'];
        for &c in list.iter() {
            if self.name.contains(c) || self.type_.contains(c) {
                return Err(DataIdParseError::InvalidChar(c));
            }
        }

        Ok(())
    }
}

impl std::str::FromStr for DataId {
    type Err = DataIdParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut split = s.split(':');
        let Some(type_) = split.next() else {
            return Err(DataIdParseError::SplitterNotFound);
        };
        let Some(name) = split.next() else {
            return Err(DataIdParseError::SplitterNotFound);
        };
        if split.next().is_some() {
            return Err(DataIdParseError::MultipleSplitterFound);
        }

        let id = Self {
            type_: type_.to_owned(),
            name: name.to_owned(),
        };
        id.validate()?;
        Ok(id)
    }
}

impl std::fmt::Display for DataId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}:{}", self.type_, self.name)
    }
}

#[derive(Clone, PartialEq, Eq, Debug, thiserror::Error)]
pub enum DataIdParseError {
    #[error("splitter ':' not found")]
    SplitterNotFound,
    #[error("multiple splitter found")]
    MultipleSplitterFound,
    #[error("include invalid character")]
    InvalidChar(char),
}

#[derive(Debug, thiserror::Error)]
#[error("message convert error")]
pub struct MessageConvertError(pub Message);

macro_rules! impl_from {
    ($($member:ident,)*) => {
        $(
            impl From<$member> for Message {
                fn from(m: $member) -> Self {
                    Message {
                        message: Some(proto::message::Message::$member(m)),
                    }
                }
            }
            impl TryFrom<Message> for $member {
                type Error = MessageConvertError;
                fn try_from(m: Message) -> core::result::Result<Self, Self::Error> {
                    match m.message {
                        Some(proto::message::Message::$member(a)) => Ok(a),
                        _ => Err(MessageConvertError(m)),
                    }
                }
            }
        )*
    }
}

impl_from!(
    ConnectRequest,
    ConnectResponse,
    Disconnect,
    DownstreamCall,
    DownstreamChunk,
    DownstreamChunkAck,
    DownstreamChunkAckComplete,
    DownstreamCloseRequest,
    DownstreamCloseResponse,
    DownstreamMetadata,
    DownstreamMetadataAck,
    DownstreamOpenRequest,
    DownstreamOpenResponse,
    DownstreamResumeRequest,
    DownstreamResumeResponse,
    Ping,
    Pong,
    UpstreamCall,
    UpstreamCallAck,
    UpstreamChunk,
    UpstreamChunkAck,
    UpstreamCloseRequest,
    UpstreamCloseResponse,
    UpstreamMetadata,
    UpstreamMetadataAck,
    UpstreamOpenRequest,
    UpstreamOpenResponse,
    UpstreamResumeRequest,
    UpstreamResumeResponse,
);

mod private {
    pub trait Sealed {}
}

pub trait HasRequestId: private::Sealed {
    fn request_id(&self) -> u32;
    fn set_request_id(&mut self, request_id: u32);
}

macro_rules! impl_has_request_id {
    ($($member:ident,)*) => {
        $(
            impl private::Sealed for $member {}
            impl HasRequestId for $member {
                fn request_id(&self) -> u32 {
                    self.request_id
                }

                fn set_request_id(&mut self, request_id: u32) {
                    self.request_id = request_id;
                }
            }
        )*
    }
}

impl_has_request_id!(
    ConnectRequest,
    ConnectResponse,
    DownstreamCloseRequest,
    DownstreamCloseResponse,
    DownstreamMetadata,
    DownstreamMetadataAck,
    DownstreamOpenRequest,
    DownstreamOpenResponse,
    DownstreamResumeRequest,
    DownstreamResumeResponse,
    Ping,
    Pong,
    UpstreamCloseRequest,
    UpstreamCloseResponse,
    UpstreamMetadata,
    UpstreamMetadataAck,
    UpstreamOpenRequest,
    UpstreamOpenResponse,
    UpstreamResumeRequest,
    UpstreamResumeResponse,
);

pub trait HasResultCode: private::Sealed {
    fn result_code(&self) -> Option<ResultCode>;
    fn result_string(&self) -> &str;
}

macro_rules! impl_has_result_code {
    ($($member:ident,)*) => {
        $(
            impl HasResultCode for $member {
                fn result_code(&self) -> Option<ResultCode> {
                    self.result_code.try_into().ok()
                }

                fn result_string(&self) -> &str {
                    &self.result_string
                }
            }
        )*
    }
}

impl_has_result_code!(
    ConnectResponse,
    DownstreamCloseResponse,
    DownstreamMetadataAck,
    DownstreamOpenResponse,
    DownstreamResumeResponse,
    UpstreamCloseResponse,
    UpstreamMetadataAck,
    UpstreamOpenResponse,
    UpstreamResumeResponse,
);

pub trait RequestMessage: Into<Message> + HasRequestId {
    type Response: TryFrom<Message> + HasRequestId + HasResultCode;
}

impl RequestMessage for ConnectRequest {
    type Response = ConnectResponse;
}
impl RequestMessage for DownstreamCloseRequest {
    type Response = DownstreamCloseResponse;
}
impl RequestMessage for DownstreamMetadata {
    type Response = DownstreamMetadataAck;
}
impl RequestMessage for DownstreamOpenRequest {
    type Response = DownstreamOpenResponse;
}
impl RequestMessage for DownstreamResumeRequest {
    type Response = DownstreamResumeResponse;
}
impl RequestMessage for UpstreamCloseRequest {
    type Response = UpstreamCloseResponse;
}
impl RequestMessage for UpstreamMetadata {
    type Response = UpstreamMetadataAck;
}
impl RequestMessage for UpstreamOpenRequest {
    type Response = UpstreamOpenResponse;
}
impl RequestMessage for UpstreamResumeRequest {
    type Response = UpstreamResumeResponse;
}

impl Message {
    pub fn request_id(&self) -> Option<u32> {
        use proto::message::Message::*;
        let request_id = match self.message.as_ref()? {
            ConnectRequest(a) => a.request_id,
            ConnectResponse(a) => a.request_id,
            DownstreamCloseRequest(a) => a.request_id,
            DownstreamCloseResponse(a) => a.request_id,
            DownstreamMetadata(a) => a.request_id,
            DownstreamMetadataAck(a) => a.request_id,
            DownstreamOpenRequest(a) => a.request_id,
            DownstreamOpenResponse(a) => a.request_id,
            DownstreamResumeRequest(a) => a.request_id,
            DownstreamResumeResponse(a) => a.request_id,
            Ping(a) => a.request_id,
            Pong(a) => a.request_id,
            UpstreamCloseRequest(a) => a.request_id,
            UpstreamCloseResponse(a) => a.request_id,
            UpstreamMetadata(a) => a.request_id,
            UpstreamMetadataAck(a) => a.request_id,
            UpstreamOpenRequest(a) => a.request_id,
            UpstreamOpenResponse(a) => a.request_id,
            UpstreamResumeRequest(a) => a.request_id,
            UpstreamResumeResponse(a) => a.request_id,
            _ => {
                return None;
            }
        };
        Some(request_id)
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use std::str::FromStr;

    #[test]
    fn data_id_validate() {
        assert!(DataId::new("name", "type").validate().is_ok());
        assert!(DataId::new("na:me", "type").validate().is_err());
        assert!(DataId::new("na+me", "type").validate().is_err());
        assert!(DataId::new("na#me", "type").validate().is_err());
        assert!(DataId::new("name", "ty:pe").validate().is_err());
        assert!(DataId::new("name", "ty+pe").validate().is_err());
        assert!(DataId::new("name", "ty#pe").validate().is_err());
    }

    #[test]
    fn data_id_parse_str() {
        assert_eq!(
            DataId::from_str("type:name").unwrap(),
            DataId::new("name", "type"),
        );
        assert!(DataId::from_str("type-name").is_err());
        assert!(DataId::from_str("ty:pe:name").is_err());
        assert!(DataId::from_str("ty#pe:name").is_err());
        assert!(DataId::from_str("type:na+me").is_err());
    }

    #[test]
    fn data_id_format() {
        let id = DataId::from_str("type:name").unwrap();
        assert_eq!(format!("{id}"), "type:name");
    }
}