use serde::de::{self, Deserializer, MapAccess, Visitor};
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Default, Debug, Clone, Serialize)]
pub struct Vip {
pub vip_type: u8,
pub vip_status: u8,
pub vip_due_date: u64,
pub label: VipLabel,
pub nickname_color: String,
pub vip_pay_type: Option<u8>,
pub role: Option<u8>,
pub is_tv_vip: Option<bool>,
pub tv_vip_status: Option<u8>,
pub tv_vip_pay_type: Option<u8>,
pub tv_due_date: Option<u64>,
pub mid: Option<u64>,
pub name: Option<String>,
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct VipLabel {
#[serde(default)]
pub text: String,
#[serde(default)]
pub label_theme: String,
#[serde(default)]
pub text_color: String,
#[serde(default)]
pub bg_style: u32,
#[serde(default)]
pub bg_color: String,
}
impl<'de> Deserialize<'de> for Vip {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct VipVisitor;
impl<'de> Visitor<'de> for VipVisitor {
type Value = Vip;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a Vip object")
}
fn visit_map<M>(self, mut map: M) -> Result<Vip, M::Error>
where
M: MapAccess<'de>,
{
let mut vip_type = None;
let mut vip_status = None;
let mut vip_due_date = None;
let mut label = None;
let mut nickname_color = None;
let mut vip_pay_type = None;
let mut role = None;
let mut is_tv_vip = None;
let mut tv_vip_status = None;
let mut tv_vip_pay_type = None;
let mut tv_due_date = None;
let mut mid = None;
let mut name = None;
while let Some(key) = map.next_key::<String>()? {
match key.as_str() {
"vipType" | "vip_type" | "type" => {
if vip_type.is_some() {
return Err(de::Error::duplicate_field("vip_type"));
}
vip_type = Some(next_u8_value(&mut map, "vip_type")?);
}
"vip_status" | "vipStatus" | "status" => {
if vip_status.is_none() {
vip_status = Some(next_u8_value(&mut map, "vip_status")?);
} else {
let _: serde_json::Value = map.next_value()?;
}
}
"vipDueDate" | "due_date" | "vip_due_date" => {
if vip_due_date.is_none() {
vip_due_date = Some(next_u64_value(&mut map, "vip_due_date")?);
} else {
let _: serde_json::Value = map.next_value()?;
}
}
"label" => {
if label.is_some() {
return Err(de::Error::duplicate_field("label"));
}
label = Some(map.next_value()?);
}
"nickname_color" => {
if nickname_color.is_some() {
return Err(de::Error::duplicate_field("nickname_color"));
}
nickname_color = Some(map.next_value()?);
}
"vip_pay_type" => {
if vip_pay_type.is_some() {
return Err(de::Error::duplicate_field("vip_pay_type"));
}
vip_pay_type = next_optional_u8_value(&mut map, "vip_pay_type")?;
}
"role" => {
if role.is_some() {
return Err(de::Error::duplicate_field("role"));
}
role = next_optional_u8_value(&mut map, "role")?;
}
"is_tv_vip" => {
if is_tv_vip.is_some() {
return Err(de::Error::duplicate_field("is_tv_vip"));
}
is_tv_vip = Some(map.next_value()?);
}
"tv_vip_status" => {
if tv_vip_status.is_some() {
return Err(de::Error::duplicate_field("tv_vip_status"));
}
tv_vip_status = next_optional_u8_value(&mut map, "tv_vip_status")?;
}
"tv_vip_pay_type" => {
if tv_vip_pay_type.is_some() {
return Err(de::Error::duplicate_field("tv_vip_pay_type"));
}
tv_vip_pay_type = next_optional_u8_value(&mut map, "tv_vip_pay_type")?;
}
"tv_due_date" => {
if tv_due_date.is_some() {
return Err(de::Error::duplicate_field("tv_due_date"));
}
tv_due_date = next_optional_u64_value(&mut map, "tv_due_date")?;
}
"mid" => {
if mid.is_some() {
return Err(de::Error::duplicate_field("mid"));
}
mid = next_optional_u64_value(&mut map, "mid")?;
}
"name" => {
if name.is_some() {
return Err(de::Error::duplicate_field("name"));
}
name = Some(map.next_value()?);
}
_ => {
let _: serde_json::Value = map.next_value()?;
}
}
}
let vip_type = vip_type.ok_or_else(|| de::Error::missing_field("vip_type"))?;
let vip_status =
vip_status.ok_or_else(|| de::Error::missing_field("vip_status"))?;
let vip_due_date = vip_due_date.unwrap_or_default();
let label = label.unwrap_or_default();
let nickname_color = nickname_color.unwrap_or_default();
Ok(Vip {
vip_type,
vip_status,
vip_due_date,
label,
nickname_color,
vip_pay_type,
role,
is_tv_vip,
tv_vip_status,
tv_vip_pay_type,
tv_due_date,
mid,
name,
})
}
}
deserializer.deserialize_map(VipVisitor)
}
}
fn next_u8_value<'de, M>(map: &mut M, field: &'static str) -> Result<u8, M::Error>
where
M: MapAccess<'de>,
{
parse_u8_value(map.next_value()?, field)
}
fn next_u64_value<'de, M>(map: &mut M, field: &'static str) -> Result<u64, M::Error>
where
M: MapAccess<'de>,
{
parse_u64_value(map.next_value()?, field)
}
fn next_optional_u8_value<'de, M>(map: &mut M, field: &'static str) -> Result<Option<u8>, M::Error>
where
M: MapAccess<'de>,
{
match map.next_value()? {
serde_json::Value::Null => Ok(None),
value => parse_u8_value(value, field).map(Some),
}
}
fn next_optional_u64_value<'de, M>(
map: &mut M,
field: &'static str,
) -> Result<Option<u64>, M::Error>
where
M: MapAccess<'de>,
{
match map.next_value()? {
serde_json::Value::Null => Ok(None),
value => parse_u64_value(value, field).map(Some),
}
}
fn parse_u8_value<E>(value: serde_json::Value, field: &'static str) -> Result<u8, E>
where
E: de::Error,
{
let raw = parse_u64_value::<E>(value, field)?;
u8::try_from(raw).map_err(|_| E::custom(format!("{field} must fit in u8")))
}
fn parse_u64_value<E>(value: serde_json::Value, field: &'static str) -> Result<u64, E>
where
E: de::Error,
{
match value {
serde_json::Value::Number(number) => number
.as_u64()
.ok_or_else(|| E::custom(format!("{field} must be a non-negative integer"))),
serde_json::Value::String(text) => text
.parse::<u64>()
.map_err(|_| E::custom(format!("{field} must be a numeric string"))),
_ => Err(E::custom(format!("{field} must be a string or number"))),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn vip_deserializes_numeric_fields_from_strings() {
let vip: Vip = serde_json::from_str(
r##"{
"type": "2",
"status": "1",
"due_date": "1813334400000",
"label": {
"text": "年度大会员",
"label_theme": "annual_vip",
"text_color": "#FFFFFF",
"bg_style": 1,
"bg_color": "#FB7299"
},
"nickname_color": "#FB7299",
"role": "3",
"tv_due_date": "0",
"tv_vip_pay_type": "0",
"tv_vip_status": "0",
"vip_pay_type": "0",
"mid": "4279370"
}"##,
)
.expect("vip should parse string numeric fields");
assert_eq!(vip.vip_type, 2);
assert_eq!(vip.vip_status, 1);
assert_eq!(vip.vip_due_date, 1813334400000);
assert_eq!(vip.role, Some(3));
assert_eq!(vip.mid, Some(4279370));
}
#[test]
fn vip_label_defaults_missing_display_fields() {
let label: VipLabel = serde_json::from_str(
r##"{
"bg_style": 1,
"text_color": ""
}"##,
)
.expect("vip label should tolerate omitted display fields");
assert_eq!(label.text, "");
assert_eq!(label.label_theme, "");
assert_eq!(label.text_color, "");
assert_eq!(label.bg_style, 1);
assert_eq!(label.bg_color, "");
}
#[test]
fn vip_deserializes_compact_relation_payload() {
let vip: Vip = serde_json::from_str(
r#"{
"vipType": 0,
"vipStatus": 0
}"#,
)
.expect("relation-list vip payload should parse");
assert_eq!(vip.vip_type, 0);
assert_eq!(vip.vip_status, 0);
assert_eq!(vip.vip_due_date, 0);
assert_eq!(vip.nickname_color, "");
assert_eq!(vip.label.text, "");
}
}