im-core 0.1.0

Rust IM SDK for Awiki clients built on Agent Network Protocol (ANP)
Documentation
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct IdentityId(String);

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Did(String);

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Handle(String);

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct PeerRef(String);

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct GroupRef(String);

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct MessageId(String);

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ThreadId(String);

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Cursor(String);

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Page<T> {
    pub items: Vec<T>,
    pub next_cursor: Option<Cursor>,
    pub has_more: bool,
}

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

impl IdentityId {
    pub fn parse(input: impl AsRef<str>) -> crate::ImResult<Self> {
        parse_non_empty("identity_id", input).map(Self)
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl Did {
    pub fn parse(input: impl AsRef<str>) -> crate::ImResult<Self> {
        let value = parse_non_empty("did", input)?;
        if !value.starts_with("did:") {
            return Err(crate::ImError::invalid_input(
                Some("did".to_string()),
                "DID must start with did:",
            ));
        }
        Ok(Self(value))
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl Handle {
    pub fn parse(input: impl AsRef<str>, default_domain: &str) -> crate::ImResult<Self> {
        let value = parse_non_empty("handle", input)?;
        let normalized = if value.contains('@') || value.contains('.') || default_domain.is_empty()
        {
            value
        } else {
            format!("{value}.{default_domain}")
        };
        Ok(Self(
            normalized.trim_start_matches('@').to_ascii_lowercase(),
        ))
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl PeerRef {
    pub fn parse(input: impl AsRef<str>, default_domain: &str) -> crate::ImResult<Self> {
        let value = parse_non_empty("peer", input)?;
        if value.starts_with("did:") {
            return Ok(Self(value));
        }
        Ok(Self(
            Handle::parse(value, default_domain)?.as_str().to_string(),
        ))
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl GroupRef {
    pub fn parse(input: impl AsRef<str>) -> crate::ImResult<Self> {
        parse_non_empty("group", input).map(Self)
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl MessageId {
    pub fn parse(input: impl AsRef<str>) -> crate::ImResult<Self> {
        parse_non_empty("message_id", input).map(Self)
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl ThreadId {
    pub fn parse(input: impl AsRef<str>) -> crate::ImResult<Self> {
        parse_non_empty("thread_id", input).map(Self)
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl Cursor {
    pub fn parse(input: impl AsRef<str>) -> crate::ImResult<Self> {
        parse_non_empty("cursor", input).map(Self)
    }

    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl PageLimit {
    pub fn new(value: u32) -> crate::ImResult<Self> {
        if value == 0 {
            return Err(crate::ImError::invalid_input(
                Some("limit".to_string()),
                "limit must be greater than zero",
            ));
        }
        Ok(Self(value.min(100)))
    }
}

fn parse_non_empty(field: &'static str, input: impl AsRef<str>) -> crate::ImResult<String> {
    let value = input.as_ref().trim();
    if value.is_empty() {
        return Err(crate::ImError::invalid_input(
            Some(field.to_string()),
            format!("{field} must not be empty"),
        ));
    }
    Ok(value.to_string())
}