use std::collections::HashMap;
use serde::{de::DeserializeOwned, Deserialize};
use serde_json::Value;
use crate::{ApiError, ApiResult};
pub trait JsonExtractor {
fn require_headers() -> bool {
false
}
fn try_extract<T>(value: Value) -> ApiResult<T>
where
T: DeserializeOwned;
}
impl JsonExtractor for Value {
fn try_extract<T>(value: Value) -> ApiResult<T>
where
T: DeserializeOwned,
{
serde_json::from_value(value).map_err(|e| e.into())
}
}
pub type WholePayload = Value;
#[derive(Debug, Deserialize)]
pub struct CodeDataMessage<T = Option<Value>> {
pub code: i64,
pub data: T,
#[serde(alias = "msg")]
pub message: Option<String>,
#[serde(rename = "__headers__", default)]
headers: HashMap<String, String>,
#[serde(flatten)]
extra: HashMap<String, Value>,
}
impl<T> CodeDataMessage<T> {
pub fn is_success(&self) -> bool {
self.code == 0
}
pub fn get_header(&self, name: &str) -> Option<&str> {
self.headers.get(name).map(|v| v.as_str())
}
pub fn get_extra<D>(&self, name: &str) -> Option<D>
where
D: DeserializeOwned,
{
self.extra
.get(name)
.and_then(|v| serde_json::from_value(v.clone()).ok())
}
pub fn get_request_id(&self) -> Option<&str> {
self.get_header("X-Request-ID")
}
pub fn get_trace_id(&self) -> Option<&str> {
self.get_header("X-Trace-ID")
}
pub fn get_span_id(&self) -> Option<&str> {
self.get_header("X-Span-ID")
}
}
impl JsonExtractor for CodeDataMessage {
fn try_extract<T>(value: Value) -> ApiResult<T>
where
T: DeserializeOwned,
{
let mut value = value;
match value.get("code").and_then(|c| c.as_i64()) {
Some(0) => match value.get_mut("data") {
Some(data) => serde_json::from_value(data.take()).map_err(|e| e.into()),
None => serde_json::from_value(Value::Null).map_err(|e| e.into()),
},
Some(code) => {
let message = value
.get("message")
.or_else(|| value.get("msg"))
.and_then(|m| m.as_str())
.map(|m| m.to_string());
Err(ApiError::BusinessError(code, message))
}
None => Err(ApiError::InvalidJson(value)),
}
}
}
#[cfg(test)]
mod tests {
use serde::Deserialize;
use serde_json::Value;
use super::CodeDataMessage;
#[derive(Debug, Deserialize)]
#[allow(unused)]
struct Payload {
pub key: u32,
}
#[test]
fn test_parse_null() {
let v: Value = serde_json::from_str("null").unwrap();
println!("v = {:?}", v);
let v: Value = serde_json::from_value(Value::Null).unwrap();
println!("v = {:?}", v);
let v: Option<Value> = serde_json::from_str("null").unwrap();
println!("v = {:?}", v);
let v: Option<Value> = serde_json::from_value(Value::Null).unwrap();
println!("v = {:?}", v);
}
#[test]
fn test_cdm_data_miss_2_option_value() {
let cdm: CodeDataMessage<Option<Value>> = serde_json::from_str(
r#"
{
"code": 0
}
"#,
)
.unwrap();
println!("test_cdm_data_miss_2_option_value = {:?}", cdm);
}
#[test]
fn test_cdm_data_null_2_option_value() {
let cdm: CodeDataMessage<Option<Value>> = serde_json::from_str(
r#"
{
"code": 0,
"data": null
}
"#,
)
.unwrap();
println!("test_cdm_data_null_2_option_value = {:?}", cdm);
}
#[test]
fn test_cdm_data_json_2_option_value() {
let cdm: CodeDataMessage<Option<Value>> = serde_json::from_str(
r#"
{
"code": 0,
"data": {
"key": 1
}
}
"#,
)
.unwrap();
println!("test_cdm_data_json_2_option_value = {:?}", cdm);
}
#[test]
#[should_panic]
fn test_cdm_data_miss_2_value() {
let cdm: CodeDataMessage<Value> = serde_json::from_str(
r#"
{
"code": 0
}
"#,
)
.unwrap();
println!("test_cdm_data_miss_2_value = {:?}", cdm);
}
#[test]
fn test_cdm_data_null_2_value() {
let cdm: CodeDataMessage<Value> = serde_json::from_str(
r#"
{
"code": 0,
"data": null
}
"#,
)
.unwrap();
println!("test_cdm_data_null_2_value = {:?}", cdm);
}
#[test]
fn test_cdm_data_json_2_value() {
let cdm: CodeDataMessage<Value> = serde_json::from_str(
r#"
{
"code": 0,
"data": {
"key": 1
}
}
"#,
)
.unwrap();
println!("test_cdm_data_json_2_value = {:?}", cdm);
}
#[test]
fn test_cdm_data_miss_2_option_payload() {
let cdm: CodeDataMessage<Option<Payload>> = serde_json::from_str(
r#"
{
"code": 0
}
"#,
)
.unwrap();
println!("test_cdm_data_miss_2_option_payload = {:?}", cdm);
}
#[test]
fn test_cdm_data_null_2_option_payload() {
let cdm: CodeDataMessage<Option<Payload>> = serde_json::from_str(
r#"
{
"code": 0,
"data": null
}
"#,
)
.unwrap();
println!("test_cdm_data_null_2_option_payload = {:?}", cdm);
}
#[test]
fn test_cdm_data_json_2_option_payload() {
let cdm: CodeDataMessage<Option<Payload>> = serde_json::from_str(
r#"
{
"code": 0,
"data": {
"key": 1
}
}
"#,
)
.unwrap();
println!("test_cdm_data_json_2_option_payload = {:?}", cdm);
}
#[test]
#[should_panic]
fn test_cdm_data_miss_2_payload() {
let cdm: CodeDataMessage<Payload> = serde_json::from_str(
r#"
{
"code": 0
}
"#,
)
.unwrap();
println!("test_cdm_data_miss_2_payload = {:?}", cdm);
}
#[test]
#[should_panic]
fn test_cdm_data_null_2_payload() {
let cdm: CodeDataMessage<Payload> = serde_json::from_str(
r#"
{
"code": 0,
"data": null
}
"#,
)
.unwrap();
println!("test_cdm_data_null_2_payload = {:?}", cdm);
}
#[test]
fn test_cdm_data_json_2_payload() {
let cdm: CodeDataMessage<Payload> = serde_json::from_str(
r#"
{
"code": 0,
"data": {
"key": 1
}
}
"#,
)
.unwrap();
println!("test_cdm_data_json_2_payload = {:?}", cdm);
}
#[test]
fn test_cdm_extra() {
let cdm: CodeDataMessage = serde_json::from_str(
r#"
{
"code": 0,
"num": 1,
"text": "string"
}
"#,
)
.unwrap();
println!("{:?}", cdm);
println!("extra.num = {:?}", cdm.get_extra::<u32>("num"));
println!("extra.text = {:?}", cdm.get_extra::<String>("text"));
}
}