agent_client_protocol/jsonrpc/
raw_id.rs1use std::fmt;
4
5use serde::{Deserialize, Serialize};
6
7use crate::v1::schema as v1;
8#[cfg(feature = "unstable_protocol_v2")]
9use crate::v2::schema as v2;
10
11#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
41#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
42#[serde(untagged)]
43#[allow(
44 clippy::exhaustive_enums,
45 reason = "JSON-RPC defines exactly these ID types"
46)]
47pub enum RawJsonRpcId {
48 Null,
50 Number(i64),
52 Str(String),
54}
55
56impl fmt::Display for RawJsonRpcId {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 match self {
61 Self::Null => f.write_str("null"),
62 Self::Number(id) => write!(f, "{id}"),
63 Self::Str(id) => f.write_str(id),
64 }
65 }
66}
67
68impl From<i64> for RawJsonRpcId {
69 fn from(id: i64) -> Self {
70 Self::Number(id)
71 }
72}
73
74impl From<String> for RawJsonRpcId {
75 fn from(id: String) -> Self {
76 Self::Str(id)
77 }
78}
79
80impl From<v1::RequestId> for RawJsonRpcId {
81 fn from(id: v1::RequestId) -> Self {
82 match id {
83 v1::RequestId::Null => Self::Null,
84 v1::RequestId::Number(id) => Self::Number(id),
85 v1::RequestId::Str(id) => Self::Str(id),
86 }
87 }
88}
89
90impl From<RawJsonRpcId> for v1::RequestId {
91 fn from(id: RawJsonRpcId) -> Self {
92 match id {
93 RawJsonRpcId::Null => Self::Null,
94 RawJsonRpcId::Number(id) => Self::Number(id),
95 RawJsonRpcId::Str(id) => Self::Str(id),
96 }
97 }
98}
99
100#[cfg(feature = "unstable_protocol_v2")]
101impl From<v2::RequestId> for RawJsonRpcId {
102 fn from(id: v2::RequestId) -> Self {
103 match id {
104 v2::RequestId::Number(id) => Self::Number(id),
105 v2::RequestId::Str(id) => Self::Str(id),
106 }
107 }
108}
109
110#[cfg(feature = "unstable_protocol_v2")]
111impl TryFrom<RawJsonRpcId> for v2::RequestId {
112 type Error = crate::Error;
113
114 fn try_from(id: RawJsonRpcId) -> Result<Self, Self::Error> {
118 match id {
119 RawJsonRpcId::Null => Err(null_v2_request_id()),
120 RawJsonRpcId::Number(id) => Ok(Self::Number(id)),
121 RawJsonRpcId::Str(id) => Ok(Self::Str(id)),
122 }
123 }
124}
125
126pub(crate) fn null_v2_request_id() -> crate::Error {
128 crate::Error::invalid_request()
129 .data("ACP protocol v2 request ids must be a string or an integer, not null")
130}
131
132#[cfg(test)]
133mod tests {
134 use serde_json::{Value, json};
135
136 use super::*;
137
138 fn wire_ids() -> Vec<Value> {
139 vec![
140 Value::Null,
141 json!(0),
142 json!(1),
143 json!(-1),
144 json!(9_007_199_254_740_993_i64),
145 json!(i64::MAX),
146 json!(i64::MIN),
147 json!("1"),
148 json!(""),
149 json!("ünïcødé \"quoted\""),
150 ]
151 }
152
153 #[test]
154 fn wire_form_round_trips_exactly() {
155 for wire in wire_ids() {
156 let text = wire.to_string();
157 let id: RawJsonRpcId = serde_json::from_str(&text).unwrap();
158 assert_eq!(serde_json::to_string(&id).unwrap(), text);
159 }
160 assert_ne!(
161 serde_json::from_str::<RawJsonRpcId>("1").unwrap(),
162 serde_json::from_str::<RawJsonRpcId>(r#""1""#).unwrap()
163 );
164 }
165
166 #[test]
167 fn non_ids_are_rejected() {
168 for text in [
169 "9223372036854775808",
170 "-9223372036854775809",
171 "1.5",
172 "1e3",
173 "true",
174 "[]",
175 "{}",
176 ] {
177 assert!(
178 serde_json::from_str::<RawJsonRpcId>(text).is_err(),
179 "{text}"
180 );
181 assert!(
182 serde_json::from_str::<v1::RequestId>(text).is_err(),
183 "{text}"
184 );
185 }
186 }
187
188 #[test]
189 fn v1_conversions_are_lossless_and_match_the_wire() {
190 for wire in wire_ids() {
191 let v1_id: v1::RequestId = serde_json::from_value(wire.clone()).unwrap();
192 let id = RawJsonRpcId::from(v1_id.clone());
193 assert_eq!(serde_json::to_value(&id).unwrap(), wire);
194 assert_eq!(id.to_string(), v1_id.to_string());
195 assert_eq!(v1::RequestId::from(id), v1_id);
196 }
197 }
198
199 #[cfg(feature = "unstable_protocol_v2")]
200 #[test]
201 fn v2_conversions_are_lossless_and_reject_null() {
202 for wire in wire_ids() {
203 let id: RawJsonRpcId = serde_json::from_value(wire.clone()).unwrap();
204 if let Ok(v2_id) = serde_json::from_value::<v2::RequestId>(wire.clone()) {
205 assert_eq!(RawJsonRpcId::from(v2_id.clone()), id);
206 assert_eq!(v2::RequestId::try_from(id).unwrap(), v2_id);
207 } else {
208 assert_eq!(id, RawJsonRpcId::Null);
209 let error = v2::RequestId::try_from(id).unwrap_err();
210 assert_eq!(error.code, crate::ErrorCode::INVALID_REQUEST);
211 }
212 }
213 }
214}