use serde_json::{json, Value};
use crate::error::ImError;
use crate::outbound::registry::{require_str, OutboundCommand, OutboundRegistration};
fn channel_condition_wire_key(key: &str) -> String {
let mut output = String::with_capacity(key.len());
let mut uppercase_next = false;
for character in key.chars() {
if character == '_' {
uppercase_next = true;
} else if uppercase_next {
output.extend(character.to_uppercase());
uppercase_next = false;
} else {
output.push(character);
}
}
output
}
fn channel_query_condition(args: &Value) -> Value {
let Some(condition) = args.get("condition").and_then(Value::as_object) else {
return json!({});
};
Value::Object(
condition
.iter()
.map(|(key, value)| (channel_condition_wire_key(key), value.clone()))
.collect(),
)
}
fn require_str_array(args: &Value, key: &str, cmd: &str) -> Result<Value, ImError> {
args.get(key)
.and_then(Value::as_array)
.filter(|a| !a.is_empty() && a.iter().all(Value::is_string))
.cloned()
.map(Value::Array)
.ok_or_else(|| ImError::Parse(format!("{cmd}: 缺/空 {key}(非空字符串数组)")))
}
macro_rules! read_cmd {
($cmd_struct:ident, $reg:ident, $name:literal, $build:expr) => {
struct $cmd_struct;
impl OutboundCommand for $cmd_struct {
fn name(&self) -> &'static str {
$name
}
fn build(&self, args: &Value) -> Result<(&'static str, Value), ImError> {
let f: fn(&Value, &'static str) -> Result<(&'static str, Value), ImError> = $build;
f(args, $name)
}
fn is_read(&self) -> bool {
true
}
}
inventory::submit! {
OutboundRegistration {
name: $name,
command: &$cmd_struct,
}
}
};
}
read_cmd!(
LoadNoticeCommand,
LOAD_NOTICE_REG,
"im_channel_load_notice",
|args, cmd| {
let channel_id = require_str(args, "channel_id", cmd)?;
Ok(("channel/load/notice", json!({ "channelId": channel_id })))
}
);
read_cmd!(
LoadPostPinnedCommand,
LOAD_POST_PINNED_REG,
"im_channel_load_post_pinned",
|args, cmd| {
let channel_id = require_str(args, "channel_id", cmd)?;
Ok((
"channel/load/postPinned",
json!({ "channelId": channel_id }),
))
}
);
read_cmd!(
LoadAdminCommand,
LOAD_ADMIN_REG,
"im_channel_load_admin",
|args, cmd| {
let channel_id = require_str(args, "channel_id", cmd)?;
Ok(("channel/load/admin", json!({ "channelId": channel_id })))
}
);
read_cmd!(
LoadIncrementByChannelIdCommand,
LOAD_INCREMENT_BY_CHANNEL_ID_REG,
"im_channel_load_increment_by_channel_id",
|args, cmd| {
let channel_id = require_str(args, "channel_id", cmd)?;
Ok((
"channel/load/incrementByChannelId",
json!({ "channelId": channel_id }),
))
}
);
read_cmd!(
RequestChannelIncrementCommand,
REQUEST_CHANNEL_INCREMENT_REG,
"im_request_channel_increment",
|args, cmd| {
let channel_id = require_str(args, "channel_id", cmd)?;
let timestamp = args.get("timestamp").and_then(Value::as_i64).unwrap_or(0);
let cursors = args.get("cursors").cloned().unwrap_or_else(|| json!([]));
if !cursors.is_array() {
return Err(ImError::Parse(format!("{cmd}: cursors 必须是数组")));
}
Ok((
"channels/load/increment",
json!({ "timeStamp": timestamp, "channelId": channel_id, "cursors": cursors }),
))
}
);
read_cmd!(
ChannelQueryCommand,
CHANNEL_QUERY_REG,
"im_channel_query",
|args, cmd| {
let mut body = channel_query_condition(args);
let obj = body
.as_object_mut()
.ok_or_else(|| ImError::Parse(format!("{cmd}: condition 必须是 object")))?;
obj.insert(
"pageNumber".to_string(),
json!(args.get("page_number").and_then(Value::as_i64).unwrap_or(0)),
);
obj.insert(
"pageSize".to_string(),
json!(args.get("page_size").and_then(Value::as_i64).unwrap_or(0)),
);
obj.insert(
"offset".to_string(),
json!(args.get("offset").and_then(Value::as_i64).unwrap_or(0)),
);
Ok(("channel/query", body))
}
);
read_cmd!(
ChannelOnlineStatusCommand,
CHANNEL_ONLINE_STATUS_REG,
"im_channel_online_status",
|args, cmd| {
let channel_ids = require_str_array(args, "channel_ids", cmd)?;
Ok(("channel/onlineStatus", json!({ "channelIds": channel_ids })))
}
);
read_cmd!(
MembersByIdsCommand,
MEMBERS_BY_IDS_REG,
"im_channels_members_by_ids",
|args, cmd| {
let channel_ids = require_str_array(args, "channel_ids", cmd)?;
Ok((
"channels/member/byIds",
json!({ "channelIds": channel_ids }),
))
}
);
read_cmd!(
MemberSnapshotCommand,
MEMBER_SNAPSHOT_REG,
"im_channel_member_snapshot",
|args, cmd| {
let channel_id = require_str(args, "channel_id", cmd)?;
let start_time = args
.get("start_time")
.and_then(Value::as_i64)
.ok_or_else(|| ImError::Parse(format!("{cmd}: 缺 start_time(int 毫秒)")))?;
let end_time = args
.get("end_time")
.and_then(Value::as_i64)
.ok_or_else(|| ImError::Parse(format!("{cmd}: 缺 end_time(int 毫秒)")))?;
Ok((
"channel/member/snapshot",
json!({ "channelId": channel_id, "startTime": start_time, "endTime": end_time }),
))
}
);
#[cfg(test)]
#[path = "read_tests.rs"]
mod tests;