use std::{any::TypeId, collections::HashMap};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_json::Value;
use crate::{ApiError, ApiResult, MimeType};
use super::ResponseBody;
#[derive(Debug)]
pub struct Json;
impl Json {
pub(crate) fn do_try_parse<T>(text: String) -> ApiResult<T>
where
T: 'static + DeserializeOwned,
{
let type_id = TypeId::of::<T>();
if type_id == TypeId::of::<()>() {
serde_json::from_value(Value::Null).map_err(|_| ApiError::Impossible)
} else if type_id == TypeId::of::<String>() {
let value = serde_json::Value::String(text);
serde_json::from_value(value).map_err(|_| ApiError::Impossible)
} else {
serde_json::from_str(&text).map_err(ApiError::DecodeJson)
}
}
pub fn try_parse<T>(body: ResponseBody) -> ApiResult<T>
where
T: 'static + DeserializeOwned,
{
let type_id = TypeId::of::<T>();
if type_id == TypeId::of::<()>() {
return serde_json::from_value(Value::Null).map_err(|_| ApiError::Impossible);
}
match body {
ResponseBody::Json(json) => {
if type_id == TypeId::of::<String>() {
let value = serde_json::Value::String(json.to_string());
serde_json::from_value(value).map_err(ApiError::DecodeJson)
} else {
serde_json::from_value(json).map_err(ApiError::DecodeJson)
}
}
ResponseBody::Text(text) => {
log::debug!("Treat text as json for decoding");
Self::do_try_parse(text)
}
_ => Err(ApiError::IncompatibleContentType(
MimeType::Json,
body.mime_type(),
)),
}
}
}
pub trait JsonExtractor {
fn require_headers() -> bool {
false
}
fn try_extract<T>(self) -> ApiResult<T>
where
T: DeserializeOwned;
}
impl TryFrom<ResponseBody> for Value {
type Error = ApiError;
fn try_from(body: ResponseBody) -> Result<Self, Self::Error> {
body.parse_json()
}
}
impl JsonExtractor for Value {
fn try_extract<T>(self) -> ApiResult<T>
where
T: DeserializeOwned,
{
serde_json::from_value(self).map_err(|_| ApiError::IllegalJson(Value::Null))
}
}
impl TryFrom<ResponseBody> for String {
type Error = ApiError;
fn try_from(body: ResponseBody) -> Result<Self, Self::Error> {
match body {
ResponseBody::Empty => Ok("".to_string()),
ResponseBody::Json(json) => {
let json = match json {
Value::Object(mut map) => {
map.remove("__headers__");
Value::Object(map)
}
_ => json,
};
Ok(json.to_string())
}
ResponseBody::Xml(xml) => Ok(xml),
ResponseBody::Text(text) => Ok(text),
}
}
}
impl JsonExtractor for String {
fn try_extract<T>(self) -> ApiResult<T>
where
T: DeserializeOwned,
{
serde_json::from_value(Value::String(self)).map_err(|_| ApiError::IllegalJson(Value::Null))
}
}
pub type WholePayload = Value;
#[derive(Debug, Serialize, 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 TryFrom<ResponseBody> for CodeDataMessage {
type Error = ApiError;
fn try_from(body: ResponseBody) -> Result<Self, Self::Error> {
body.parse_json()
}
}
impl JsonExtractor for CodeDataMessage {
fn try_extract<T>(self) -> ApiResult<T>
where
T: DeserializeOwned,
{
match self.code {
0 => {
match self.data {
Some(data) => {
serde_json::from_value(data).map_err(|_| ApiError::IllegalJson(Value::Null))
}
None => serde_json::from_value(Value::Null)
.map_err(|_| ApiError::IllegalJson(Value::Null)),
}
}
code => {
Err(ApiError::ServiceError(code, self.message))
}
}
}
}
#[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"));
}
}