use serde_json::{json, Value};
use crate::error::ImError;
use crate::outbound::registry::{require_str, OutboundCommand, OutboundRegistration};
fn require_member_array(args: &Value, cmd: &str) -> Result<Value, ImError> {
args.get("users")
.and_then(Value::as_array)
.filter(|a| !a.is_empty() && a.iter().all(Value::is_object))
.cloned()
.map(Value::Array)
.ok_or_else(|| ImError::Parse(format!("{cmd}: 缺/空 users(非空 object 数组)")))
}
struct ChangeDisplayNameCommand;
impl OutboundCommand for ChangeDisplayNameCommand {
fn name(&self) -> &'static str {
"im_channel_change_display_name"
}
fn build(&self, args: &Value) -> Result<(&'static str, Value), ImError> {
let channel_id = require_str(args, "channel_id", self.name())?;
let display_name = require_str(args, "display_name", self.name())?;
Ok((
"channel/change/displayName",
json!({ "id": channel_id, "displayName": display_name }),
))
}
}
inventory::submit! {
OutboundRegistration {
name: "im_channel_change_display_name",
command: &ChangeDisplayNameCommand,
}
}
struct ChangeOrientCommand;
impl OutboundCommand for ChangeOrientCommand {
fn name(&self) -> &'static str {
"im_channel_change_orient"
}
fn build(&self, args: &Value) -> Result<(&'static str, Value), ImError> {
let channel_id = require_str(args, "channel_id", self.name())?;
let orient = args
.get("orient")
.and_then(Value::as_str)
.ok_or_else(|| ImError::Parse(format!("{}: 缺 orient(string)", self.name())))?;
if orient.chars().count() > 30 {
return Err(ImError::Parse(format!(
"{}: orient 最多 30 个字符",
self.name()
)));
}
Ok((
"channel/change/orient",
json!({ "id": channel_id, "orient": orient }),
))
}
}
inventory::submit! {
OutboundRegistration {
name: "im_channel_change_orient",
command: &ChangeOrientCommand,
}
}
struct ChangePurposeCommand;
impl OutboundCommand for ChangePurposeCommand {
fn name(&self) -> &'static str {
"im_channel_change_purpose"
}
fn build(&self, args: &Value) -> Result<(&'static str, Value), ImError> {
let channel_id = require_str(args, "channel_id", self.name())?;
let purpose = require_str(args, "purpose", self.name())?;
Ok((
"channel/change/purpose",
json!({ "id": channel_id, "purpose": purpose }),
))
}
}
inventory::submit! {
OutboundRegistration {
name: "im_channel_change_purpose",
command: &ChangePurposeCommand,
}
}
struct AddMangerCommand;
impl OutboundCommand for AddMangerCommand {
fn name(&self) -> &'static str {
"im_channel_add_manger"
}
fn build(&self, args: &Value) -> Result<(&'static str, Value), ImError> {
let channel_id = require_str(args, "channel_id", self.name())?;
let users = require_member_array(args, self.name())?;
Ok((
"channel/add/manger",
json!({ "channelId": channel_id, "users": users }),
))
}
}
inventory::submit! {
OutboundRegistration {
name: "im_channel_add_manger",
command: &AddMangerCommand,
}
}
struct RemoveMangerCommand;
impl OutboundCommand for RemoveMangerCommand {
fn name(&self) -> &'static str {
"im_channel_remove_manger"
}
fn build(&self, args: &Value) -> Result<(&'static str, Value), ImError> {
let channel_id = require_str(args, "channel_id", self.name())?;
let users = require_member_array(args, self.name())?;
Ok((
"channel/remove/manger",
json!({ "channelId": channel_id, "users": users }),
))
}
}
inventory::submit! {
OutboundRegistration {
name: "im_channel_remove_manger",
command: &RemoveMangerCommand,
}
}
struct SetMangerCommand;
impl OutboundCommand for SetMangerCommand {
fn name(&self) -> &'static str {
"im_channel_set_manger"
}
fn build(&self, args: &Value) -> Result<(&'static str, Value), ImError> {
let channel_id = require_str(args, "channel_id", self.name())?;
let user_id = require_str(args, "user_id", self.name())?;
let team_id = args.get("team_id").and_then(Value::as_str).unwrap_or("");
let set = args
.get("set")
.and_then(Value::as_bool)
.ok_or_else(|| ImError::Parse(format!("{}: 缺 set(bool)", self.name())))?;
let role = if set { "ADMIN" } else { "MEMBER" };
let endpoint = if set {
"channel/add/manger"
} else {
"channel/remove/manger"
};
let users = json!([{ "id": user_id, "name": "", "role": role, "teamId": team_id }]);
Ok((endpoint, json!({ "channelId": channel_id, "users": users })))
}
}
inventory::submit! {
OutboundRegistration {
name: "im_channel_set_manger",
command: &SetMangerCommand,
}
}
struct EnableApprovalCommand;
impl OutboundCommand for EnableApprovalCommand {
fn name(&self) -> &'static str {
"im_channel_enable_approval"
}
fn build(&self, args: &Value) -> Result<(&'static str, Value), ImError> {
let channel_id = require_str(args, "channel_id", self.name())?;
let approval_status = args
.get("approval_status")
.and_then(Value::as_bool)
.ok_or_else(|| {
ImError::Parse(format!("{}: 缺 approval_status(bool)", self.name()))
})?;
Ok((
"channels/enableApproval",
json!({ "channelId": channel_id, "approvalStatus": approval_status }),
))
}
}
inventory::submit! {
OutboundRegistration {
name: "im_channel_enable_approval",
command: &EnableApprovalCommand,
}
}
struct ViewChannelsCommand;
impl OutboundCommand for ViewChannelsCommand {
fn name(&self) -> &'static str {
"im_channels_view"
}
fn is_read(&self) -> bool {
true
}
fn build(&self, args: &Value) -> Result<(&'static str, Value), ImError> {
let channels = require_canonical_root_channels(args, self.name())?;
Ok(("channels/view", json!({ "channels": channels })))
}
}
fn require_canonical_root_channels(args: &Value, command: &str) -> Result<Value, ImError> {
let channels = args
.get("channels")
.and_then(Value::as_array)
.filter(|items| !items.is_empty())
.ok_or_else(|| {
ImError::Parse(format!(
"{command}: 缺/空 channels(非空 canonical object 数组)"
))
})?;
let mut seen = std::collections::HashSet::with_capacity(channels.len());
for channel in channels {
let object = channel
.as_object()
.ok_or_else(|| ImError::Parse(format!("{command}: channel 必须是 object")))?;
if object.len() != 2 || !object.contains_key("id") || !object.contains_key("isRoot") {
return Err(ImError::Parse(format!(
"{command}: channel 只能包含 id/isRoot canonical 字段"
)));
}
let id = object
.get("id")
.and_then(Value::as_str)
.filter(|id| !id.is_empty() && id.trim() == *id)
.ok_or_else(|| ImError::Parse(format!("{command}: id 必须是非空 canonical 字符串")))?;
if crate::state::ChannelId::from_str(id).is_none() {
return Err(ImError::Parse(format!(
"{command}: 非 canonical channel id: {id}"
)));
}
if object.get("isRoot").and_then(Value::as_bool) != Some(true) {
return Err(ImError::Parse(format!("{command}: isRoot 必须为 true")));
}
if !seen.insert(id) {
return Err(ImError::Parse(format!("{command}: channel id 重复: {id}")));
}
}
Ok(Value::Array(channels.to_vec()))
}
inventory::submit! {
OutboundRegistration {
name: "im_channels_view",
command: &ViewChannelsCommand,
}
}
#[cfg(test)]
#[path = "change_dedicated_tests.rs"]
mod tests;