use serde_json::{json, Map, Value};
use crate::error::ImError;
use crate::outbound::registry::{require_str, Gateway, OutboundCommand, OutboundRegistration};
macro_rules! vote_cmd {
($cmd_struct:ident, $reg:ident, $name:literal, $read: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 {
$read
}
fn gateway(&self) -> Gateway {
Gateway::Default
}
}
inventory::submit! {
OutboundRegistration {
name: $name,
command: &$cmd_struct,
}
}
};
}
fn id_body(args: &Value, cmd: &'static str) -> Result<Value, ImError> {
let id = require_str(args, "id", cmd)?;
Ok(json!({ "id": id }))
}
fn to_java_wire_value(value: &Value) -> Value {
match value {
Value::Array(items) => Value::Array(items.iter().map(to_java_wire_value).collect()),
Value::Object(object) => Value::Object(
object
.iter()
.map(|(key, value)| (snake_to_camel(key), to_java_wire_value(value)))
.collect(),
),
other => other.clone(),
}
}
fn snake_to_camel(key: &str) -> String {
let mut result = String::with_capacity(key.len());
let mut uppercase = false;
for character in key.chars() {
if character == '_' {
uppercase = true;
continue;
}
if uppercase {
result.extend(character.to_uppercase());
uppercase = false;
} else {
result.push(character);
}
}
result
}
fn business_object(args: &Value, cmd: &'static str) -> Result<Value, ImError> {
let object = args
.as_object()
.filter(|object| !object.is_empty())
.ok_or_else(|| ImError::Parse(format!("{cmd}: args 须为非空 object")))?;
let mut body = object.clone();
for key in [
"req_id",
"reqId",
"operation_id",
"operationId",
"client_mutation_id",
"clientMutationId",
] {
body.remove(key);
}
if body.is_empty() {
return Err(ImError::Parse(format!(
"{cmd}: args 不能只包含 transport 字段"
)));
}
Ok(to_java_wire_value(&Value::Object(body)))
}
fn carry_optional(
args: &Value,
body: &mut Map<String, Value>,
input_keys: &[&str],
wire_key: &str,
) {
for key in input_keys {
if let Some(v) = args.get(*key) {
if !v.is_null() {
body.insert(wire_key.to_string(), to_java_wire_value(v));
return;
}
}
}
}
fn optional_post_id() -> [&'static str; 2] {
["post_id", "postId"]
}
fn copy_optional_post_id(args: &Value, body: &mut Map<String, Value>) {
carry_optional(args, body, &optional_post_id(), "postId");
}
vote_cmd!(
VoteCreateCommand,
VOTE_CREATE_REG,
"im_vote_create",
false,
|args, cmd| { Ok(("vote/createVote", business_object(args, cmd)?)) }
);
vote_cmd!(
VoteReadCommand,
VOTE_READ_REG,
"im_vote_read",
true,
|args, cmd| { Ok(("vote/readVote", id_body(args, cmd)?)) }
);
vote_cmd!(
VoteDeleteCommand,
VOTE_DELETE_REG,
"im_vote_delete",
false,
|args, cmd| { Ok(("vote/deleteVote", id_body(args, cmd)?)) }
);
vote_cmd!(
AverageReadCommand,
AVERAGE_READ_REG,
"im_average_read",
true,
|args, cmd| { Ok(("average/read", id_body(args, cmd)?)) }
);
vote_cmd!(
AverageDeleteCommand,
AVERAGE_DELETE_REG,
"im_average_delete",
false,
|args, cmd| { Ok(("average/delete", id_body(args, cmd)?)) }
);
vote_cmd!(
VoteDoCommand,
VOTE_DO_REG,
"im_vote_do",
false,
|args, cmd| {
let id = require_str(args, "id", cmd)?;
let indexes = args
.get("indexes")
.filter(|v| v.is_array())
.ok_or_else(|| ImError::Parse(format!("{cmd}: 缺/非 array 必填字段 'indexes'")))?;
let mut body = serde_json::Map::new();
body.insert("id".to_string(), json!(id));
body.insert("indexes".to_string(), indexes.clone());
copy_optional_post_id(args, &mut body);
Ok(("vote/vote", Value::Object(body)))
}
);
vote_cmd!(
VoteCloseCommand,
VOTE_CLOSE_REG,
"im_vote_close",
false,
|args, cmd| { Ok(("vote/closeVote", id_body(args, cmd)?)) }
);
vote_cmd!(
AverageAttendCommand,
AVERAGE_ATTEND_REG,
"im_average_attend",
false,
|args, cmd| {
let id = require_str(args, "id", cmd)?;
let score = args
.get("score")
.filter(|v| v.is_number())
.ok_or_else(|| ImError::Parse(format!("{cmd}: 缺/非 number 必填字段 'score'")))?;
let mut body = serde_json::Map::new();
body.insert("id".to_string(), json!(id));
body.insert("score".to_string(), score.clone());
copy_optional_post_id(args, &mut body);
Ok(("average/attend", Value::Object(body)))
}
);
vote_cmd!(
AverageCloseCommand,
AVERAGE_CLOSE_REG,
"im_average_close",
false,
|args, cmd| {
let id = require_str(args, "id", cmd)?;
let mut body = serde_json::Map::new();
body.insert("id".to_string(), json!(id));
copy_optional_post_id(args, &mut body);
Ok(("average/close", Value::Object(body)))
}
);
vote_cmd!(
AveragePublishCommand,
AVERAGE_PUBLISH_REG,
"im_average_publish",
false,
|args, cmd| { Ok(("average/publish", business_object(args, cmd)?)) }
);
vote_cmd!(
VoteUpdateFinishTimeCommand,
VOTE_UPDATE_FINISH_TIME_REG,
"im_vote_update_finish_time",
false,
|args, cmd| {
let id = require_str(args, "id", cmd)?;
let finish_time = args
.get("finish_time")
.or_else(|| args.get("finishTime"))
.filter(|value| value.is_number())
.ok_or_else(|| ImError::Parse(format!("{cmd}: 缺/非 number 必填字段 'finish_time'")))?;
Ok((
"vote/updateFinishTime",
json!({ "id": id, "finishTime": finish_time }),
))
}
);
vote_cmd!(
VoteAddMembersCommand,
VOTE_ADD_MEMBERS_REG,
"im_vote_add_members",
false,
|args, cmd| {
let id = require_str(args, "id", cmd)?;
let user_ids = args
.get("user_ids")
.or_else(|| args.get("userIds"))
.filter(|value| value.is_array())
.ok_or_else(|| ImError::Parse(format!("{cmd}: 缺/非 array 必填字段 'user_ids'")))?;
Ok((
"vote/addMembersToVote",
json!({ "id": id, "userIds": user_ids }),
))
}
);
vote_cmd!(
AverageModifyCutoffCommand,
AVERAGE_MODIFY_CUTOFF_REG,
"im_average_modify_cutoff",
false,
|args, cmd| {
let id = require_str(args, "id", cmd)?;
let cutoff = args
.get("cutoff")
.filter(|value| value.is_number())
.ok_or_else(|| ImError::Parse(format!("{cmd}: 缺/非 number 必填字段 'cutoff'")))?;
Ok((
"average/modifyCutoff",
json!({ "id": id, "cutoff": cutoff }),
))
}
);
vote_cmd!(
AverageAddMemberCommand,
AVERAGE_ADD_MEMBER_REG,
"im_average_add_member",
false,
|args, cmd| {
let id = require_str(args, "id", cmd)?;
let members = args
.get("members")
.filter(|value| value.is_array())
.ok_or_else(|| ImError::Parse(format!("{cmd}: 缺/非 array 必填字段 'members'")))?;
Ok((
"average/addMember",
json!({ "id": id, "members": to_java_wire_value(members) }),
))
}
);
#[cfg(test)]
#[path = "vote_score_tests.rs"]
mod tests;