wresp 0.1.3

RESP (REdis Serialization Protocol) response writer and serializer
//! 集群域 RESP 错误文案与重定向帧写出(对标 libs/cluster/CmdStrings.cs)
//!
//! 集群专属文案单源:wnode / wedb 两消费方共引本表;通用文案与
//! [`crate::cmd_strings`] 单源,此处不重复。

use itoa::Buffer;

/// libs/cluster/CmdStrings.cs:RESP_ERR_CROSSSLOT
pub const RESP_ERR_CROSSSLOT: &str = "CROSSSLOT Keys in request do not hash to the same slot";
/// libs/cluster/CmdStrings.cs:RESP_ERR_CLUSTERDOWN
pub const RESP_ERR_CLUSTERDOWN: &str = "CLUSTERDOWN Hash slot not served";
/// libs/cluster/CmdStrings.cs:RESP_ERR_TRYAGAIN
pub const RESP_ERR_TRYAGAIN: &str = "TRYAGAIN Multiple keys request during rehashing of slot";
/// libs/cluster/CmdStrings.cs:RESP_ERR_INVALID_SLOT
pub const RESP_ERR_INVALID_SLOT: &str = "ERR Invalid or out of range slot";
/// libs/cluster/CmdStrings.cs:RESP_ERR_GENERIC_SLOT_OUT_OFF_RANGE
pub const RESP_ERR_GENERIC_SLOT_OUT_OFF_RANGE: &str = "ERR Slot out of range";
/// libs/cluster/CmdStrings.cs:RESP_ERR_GENERIC_CONFIG_EPOCH_ASSIGNMENT
pub const RESP_ERR_GENERIC_CONFIG_EPOCH_ASSIGNMENT: &str =
  "ERR The user can assign a config epoch only when the node does not know any other node";
/// libs/cluster/CmdStrings.cs:RESP_ERR_GENERIC_CONFIG_UPDATE
pub const RESP_ERR_GENERIC_CONFIG_UPDATE: &str = "ERR Updating the config epoch";
/// libs/cluster/CmdStrings.cs:RESP_ERR_GENERIC_CANNOT_FORGET_MYSELF
pub const RESP_ERR_GENERIC_CANNOT_FORGET_MYSELF: &str =
  "ERR I tried hard but I can't forget myself";
/// libs/cluster/CmdStrings.cs:RESP_ERR_GENERIC_CANNOT_FORGET_MY_PRIMARY
pub const RESP_ERR_GENERIC_CANNOT_FORGET_MY_PRIMARY: &str = "ERR Can't forget my primary";
/// libs/cluster/CmdStrings.cs:RESP_ERR_GENERIC_CANNOT_FAILOVER_FROM_NON_MASTER
pub const RESP_ERR_GENERIC_CANNOT_FAILOVER_FROM_NON_MASTER: &str =
  "ERR Cannot failover a non-master node";
/// libs/cluster/CmdStrings.cs:RESP_ERR_GENERIC_UNKNOWN_ENDPOINT
pub const RESP_ERR_GENERIC_UNKNOWN_ENDPOINT: &str = "ERR Unknown endpoint";
/// libs/cluster/CmdStrings.cs:RESP_ERR_GENERIC_SLOT_STATE
pub const RESP_ERR_GENERIC_SLOT_STATE: &str = "ERR Invalid slot state";
/// libs/cluster/CmdStrings.cs:RESP_ERR_MULTI_LOG_DISABLED
pub const RESP_ERR_MULTI_LOG_DISABLED: &str = "ERR Multi-log disabled";
/// libs/cluster/CmdStrings.cs:RESP_ERR_IOERR
pub const RESP_ERR_IOERR: &str = "IOERR Migrate keys failed";
/// libs/cluster/CmdStrings.cs:RESP_ERR_GENERIC_VALUE_IS_NOT_BOOLEAN
pub const RESP_ERR_GENERIC_VALUE_IS_NOT_BOOLEAN: &str = "ERR value is not a boolean.";

/// 写出 MOVED/ASK 重定向错误行 `-<kind> <slot> <endpoint>:<port>\r\n`(零堆分配;
/// 对标 C# RespWriteUtils.TryWriteError 组帧 `$"MOVED {slot} {endpoint}:{port}"`)
#[inline]
pub fn write_redirect_error(
  output: &mut Vec<u8>,
  kind: &str,
  slot: u16,
  endpoint: &str,
  port: i32,
) {
  let mut buf = Buffer::new();
  output.push(b'-');
  output.extend_from_slice(kind.as_bytes());
  output.push(b' ');
  output.extend_from_slice(buf.format(slot).as_bytes());
  output.push(b' ');
  output.extend_from_slice(endpoint.as_bytes());
  output.push(b':');
  output.extend_from_slice(buf.format(port).as_bytes());
  output.extend_from_slice(b"\r\n");
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn redirect_error_frames_slot_and_endpoint() {
    let mut out = Vec::new();
    write_redirect_error(&mut out, "MOVED", 3999, "127.0.0.1", 7001);
    assert_eq!(out, b"-MOVED 3999 127.0.0.1:7001\r\n");

    let mut out = Vec::new();
    write_redirect_error(&mut out, "ASK", 12000, "node2.example.com", 6379);
    assert_eq!(out, b"-ASK 12000 node2.example.com:6379\r\n");
  }

  #[test]
  fn cluster_error_constants_match_csharp() {
    // libs/cluster/CmdStrings.cs:75-77 原文
    assert_eq!(
      RESP_ERR_CROSSSLOT,
      "CROSSSLOT Keys in request do not hash to the same slot"
    );
    assert_eq!(RESP_ERR_CLUSTERDOWN, "CLUSTERDOWN Hash slot not served");
    assert_eq!(
      RESP_ERR_TRYAGAIN,
      "TRYAGAIN Multiple keys request during rehashing of slot"
    );
    assert_eq!(
      RESP_ERR_GENERIC_CONFIG_UPDATE,
      "ERR Updating the config epoch"
    );
  }
}