spotify_cli/rpc/
protocol.rs1use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8use crate::io::output::Response;
9
10#[derive(Debug, Clone, Deserialize)]
12pub struct RpcRequest {
13 pub jsonrpc: String,
14 pub method: String,
15 #[serde(default)]
16 pub params: Option<Value>,
17 pub id: Option<Value>,
18}
19
20impl RpcRequest {
21 pub fn is_notification(&self) -> bool {
23 self.id.is_none()
24 }
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct RpcResponse {
30 pub jsonrpc: String,
31 #[serde(skip_serializing_if = "Option::is_none")]
32 pub result: Option<Value>,
33 #[serde(skip_serializing_if = "Option::is_none")]
34 pub error: Option<RpcError>,
35 pub id: Value,
36}
37
38impl RpcResponse {
39 pub fn success(id: Value, result: Value) -> Self {
41 Self {
42 jsonrpc: "2.0".to_string(),
43 result: Some(result),
44 error: None,
45 id,
46 }
47 }
48
49 pub fn error(id: Value, code: i32, message: &str, data: Option<Value>) -> Self {
51 Self {
52 jsonrpc: "2.0".to_string(),
53 result: None,
54 error: Some(RpcError {
55 code,
56 message: message.to_string(),
57 data,
58 }),
59 id,
60 }
61 }
62
63 pub fn from_response(id: Value, response: Response) -> Self {
65 match response.status {
66 crate::io::output::Status::Success => {
67 let result = serde_json::json!({
68 "message": response.message,
69 "payload": response.payload,
70 });
71 Self::success(id, result)
72 }
73 crate::io::output::Status::Error => {
74 let data = response.error.map(|e| {
75 serde_json::json!({
76 "kind": format!("{:?}", e.kind),
77 "details": e.details,
78 })
79 });
80 Self::error(id, response.code as i32, &response.message, data)
81 }
82 }
83 }
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct RpcError {
89 pub code: i32,
90 pub message: String,
91 #[serde(skip_serializing_if = "Option::is_none")]
92 pub data: Option<Value>,
93}
94
95#[derive(Debug, Clone, Serialize)]
97pub struct RpcNotification {
98 pub jsonrpc: String,
99 pub method: String,
100 #[serde(skip_serializing_if = "Option::is_none")]
101 pub params: Option<Value>,
102}
103
104impl RpcNotification {
105 pub fn new(method: &str, params: Option<Value>) -> Self {
106 Self {
107 jsonrpc: "2.0".to_string(),
108 method: method.to_string(),
109 params,
110 }
111 }
112}
113
114pub mod error_codes {
116 pub const PARSE_ERROR: i32 = -32700;
117 pub const INVALID_REQUEST: i32 = -32600;
118 pub const METHOD_NOT_FOUND: i32 = -32601;
119 pub const INVALID_PARAMS: i32 = -32602;
120 pub const INTERNAL_ERROR: i32 = -32603;
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn parse_request_with_params() {
129 let json = r#"{"jsonrpc": "2.0", "method": "player.play", "params": {"uri": "spotify:track:123"}, "id": 1}"#;
130 let req: RpcRequest = serde_json::from_str(json).unwrap();
131 assert_eq!(req.method, "player.play");
132 assert!(req.params.is_some());
133 assert!(!req.is_notification());
134 }
135
136 #[test]
137 fn parse_notification() {
138 let json = r#"{"jsonrpc": "2.0", "method": "player.next"}"#;
139 let req: RpcRequest = serde_json::from_str(json).unwrap();
140 assert!(req.is_notification());
141 }
142
143 #[test]
144 fn serialize_success_response() {
145 let resp = RpcResponse::success(serde_json::json!(1), serde_json::json!({"status": "ok"}));
146 let json = serde_json::to_string(&resp).unwrap();
147 assert!(json.contains("result"));
148 assert!(!json.contains("error"));
149 }
150
151 #[test]
152 fn serialize_error_response() {
153 let resp = RpcResponse::error(serde_json::json!(1), -32601, "Method not found", None);
154 let json = serde_json::to_string(&resp).unwrap();
155 assert!(json.contains("error"));
156 assert!(!json.contains("result"));
157 }
158
159 #[test]
160 fn serialize_notification() {
161 let notif = RpcNotification::new(
162 "event.trackChanged",
163 Some(serde_json::json!({"track": "test"})),
164 );
165 let json = serde_json::to_string(¬if).unwrap();
166 assert!(json.contains("event.trackChanged"));
167 assert!(!json.contains("id"));
168 }
169}