use std::fmt;
use serde::{Deserialize, Serialize};
use crate::v1::schema as v1;
#[cfg(feature = "unstable_protocol_v2")]
use crate::v2::schema as v2;
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
#[allow(
clippy::exhaustive_enums,
reason = "JSON-RPC defines exactly these ID types"
)]
pub enum RawJsonRpcId {
Null,
Number(i64),
Str(String),
}
impl fmt::Display for RawJsonRpcId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Null => f.write_str("null"),
Self::Number(id) => write!(f, "{id}"),
Self::Str(id) => f.write_str(id),
}
}
}
impl From<i64> for RawJsonRpcId {
fn from(id: i64) -> Self {
Self::Number(id)
}
}
impl From<String> for RawJsonRpcId {
fn from(id: String) -> Self {
Self::Str(id)
}
}
impl From<v1::RequestId> for RawJsonRpcId {
fn from(id: v1::RequestId) -> Self {
match id {
v1::RequestId::Null => Self::Null,
v1::RequestId::Number(id) => Self::Number(id),
v1::RequestId::Str(id) => Self::Str(id),
}
}
}
impl From<RawJsonRpcId> for v1::RequestId {
fn from(id: RawJsonRpcId) -> Self {
match id {
RawJsonRpcId::Null => Self::Null,
RawJsonRpcId::Number(id) => Self::Number(id),
RawJsonRpcId::Str(id) => Self::Str(id),
}
}
}
#[cfg(feature = "unstable_protocol_v2")]
impl From<v2::RequestId> for RawJsonRpcId {
fn from(id: v2::RequestId) -> Self {
match id {
v2::RequestId::Number(id) => Self::Number(id),
v2::RequestId::Str(id) => Self::Str(id),
}
}
}
#[cfg(feature = "unstable_protocol_v2")]
impl TryFrom<RawJsonRpcId> for v2::RequestId {
type Error = crate::Error;
fn try_from(id: RawJsonRpcId) -> Result<Self, Self::Error> {
match id {
RawJsonRpcId::Null => Err(null_v2_request_id()),
RawJsonRpcId::Number(id) => Ok(Self::Number(id)),
RawJsonRpcId::Str(id) => Ok(Self::Str(id)),
}
}
}
pub(crate) fn null_v2_request_id() -> crate::Error {
crate::Error::invalid_request()
.data("ACP protocol v2 request ids must be a string or an integer, not null")
}
#[cfg(test)]
mod tests {
use serde_json::{Value, json};
use super::*;
fn wire_ids() -> Vec<Value> {
vec![
Value::Null,
json!(0),
json!(1),
json!(-1),
json!(9_007_199_254_740_993_i64),
json!(i64::MAX),
json!(i64::MIN),
json!("1"),
json!(""),
json!("ünïcødé \"quoted\""),
]
}
#[test]
fn wire_form_round_trips_exactly() {
for wire in wire_ids() {
let text = wire.to_string();
let id: RawJsonRpcId = serde_json::from_str(&text).unwrap();
assert_eq!(serde_json::to_string(&id).unwrap(), text);
}
assert_ne!(
serde_json::from_str::<RawJsonRpcId>("1").unwrap(),
serde_json::from_str::<RawJsonRpcId>(r#""1""#).unwrap()
);
}
#[test]
fn non_ids_are_rejected() {
for text in [
"9223372036854775808",
"-9223372036854775809",
"1.5",
"1e3",
"true",
"[]",
"{}",
] {
assert!(
serde_json::from_str::<RawJsonRpcId>(text).is_err(),
"{text}"
);
assert!(
serde_json::from_str::<v1::RequestId>(text).is_err(),
"{text}"
);
}
}
#[test]
fn v1_conversions_are_lossless_and_match_the_wire() {
for wire in wire_ids() {
let v1_id: v1::RequestId = serde_json::from_value(wire.clone()).unwrap();
let id = RawJsonRpcId::from(v1_id.clone());
assert_eq!(serde_json::to_value(&id).unwrap(), wire);
assert_eq!(id.to_string(), v1_id.to_string());
assert_eq!(v1::RequestId::from(id), v1_id);
}
}
#[cfg(feature = "unstable_protocol_v2")]
#[test]
fn v2_conversions_are_lossless_and_reject_null() {
for wire in wire_ids() {
let id: RawJsonRpcId = serde_json::from_value(wire.clone()).unwrap();
if let Ok(v2_id) = serde_json::from_value::<v2::RequestId>(wire.clone()) {
assert_eq!(RawJsonRpcId::from(v2_id.clone()), id);
assert_eq!(v2::RequestId::try_from(id).unwrap(), v2_id);
} else {
assert_eq!(id, RawJsonRpcId::Null);
let error = v2::RequestId::try_from(id).unwrap_err();
assert_eq!(error.code, crate::ErrorCode::INVALID_REQUEST);
}
}
}
}