1use tracing::debug;
7
8use crate::cli::commands::{self, ArtistQuery, ArtistView, SearchFilters, SearchOptions};
9use crate::io::output::{ErrorKind, Response};
10
11use super::protocol::RpcRequest;
12
13pub struct Dispatcher;
15
16impl Dispatcher {
17 pub fn new() -> Self {
18 Self
19 }
20
21 pub async fn dispatch(&self, request: &RpcRequest) -> Response {
23 debug!(method = %request.method, "Dispatching RPC request");
24
25 let params = request.params.as_ref();
26
27 match request.method.as_str() {
28 "ping" => Response::success(200, "pong"),
32 "version" => Response::success_with_payload(
33 200,
34 "Version info",
35 serde_json::json!({
36 "version": env!("CARGO_PKG_VERSION"),
37 "name": env!("CARGO_PKG_NAME"),
38 }),
39 ),
40
41 "auth.login" => {
45 let force = params
46 .and_then(|p| p.get("force"))
47 .and_then(|v| v.as_bool())
48 .unwrap_or(false);
49 commands::auth_login(force).await
50 }
51 "auth.logout" => commands::auth_logout().await,
52 "auth.refresh" => commands::auth_refresh().await,
53 "auth.status" => commands::auth_status().await,
54
55 "player.status" => {
59 let id_only = params
60 .and_then(|p| p.get("id_only"))
61 .and_then(|v| v.as_str());
62 commands::player_status(id_only).await
63 }
64 "player.play" => {
65 let uri = params.and_then(|p| p.get("uri")).and_then(|v| v.as_str());
66 let pin = params.and_then(|p| p.get("pin")).and_then(|v| v.as_str());
67 commands::player_play(uri, pin).await
68 }
69 "player.pause" => commands::player_pause().await,
70 "player.toggle" => commands::player_toggle().await,
71 "player.next" => commands::player_next().await,
72 "player.previous" => commands::player_previous().await,
73 "player.seek" => {
74 let position = params
75 .and_then(|p| p.get("position"))
76 .and_then(|v| v.as_str())
77 .unwrap_or("0");
78 commands::player_seek(position).await
79 }
80 "player.volume" => {
81 let percent = params
82 .and_then(|p| p.get("percent"))
83 .and_then(|v| v.as_u64())
84 .unwrap_or(50) as u8;
85 commands::player_volume(percent).await
86 }
87 "player.shuffle" => {
88 let state = params
89 .and_then(|p| p.get("state"))
90 .and_then(|v| v.as_str())
91 .unwrap_or("on");
92 commands::player_shuffle(state).await
93 }
94 "player.repeat" => {
95 let mode = params
96 .and_then(|p| p.get("mode"))
97 .and_then(|v| v.as_str())
98 .unwrap_or("off");
99 commands::player_repeat(mode).await
100 }
101 "player.devices" => commands::player_devices_list().await,
102 "player.transfer" => {
103 let device = params
104 .and_then(|p| p.get("device"))
105 .and_then(|v| v.as_str());
106 if let Some(dev) = device {
107 commands::player_devices_transfer(dev).await
108 } else {
109 Response::err(400, "Missing 'device' parameter", ErrorKind::Validation)
110 }
111 }
112 "player.recent" => commands::player_recent().await,
113
114 "queue.list" => commands::player_queue_list().await,
118 "queue.add" => {
119 let uri = params.and_then(|p| p.get("uri")).and_then(|v| v.as_str());
120 let now_playing = params
121 .and_then(|p| p.get("now_playing"))
122 .and_then(|v| v.as_bool())
123 .unwrap_or(false);
124 commands::player_queue_add(uri, now_playing).await
125 }
126
127 "search" => {
131 let query = params
132 .and_then(|p| p.get("query"))
133 .and_then(|v| v.as_str())
134 .unwrap_or("");
135 let types: Vec<String> = params
136 .and_then(|p| p.get("types"))
137 .and_then(|v| v.as_array())
138 .map(|arr| {
139 arr.iter()
140 .filter_map(|v| v.as_str().map(String::from))
141 .collect()
142 })
143 .unwrap_or_default();
144 let limit = params
145 .and_then(|p| p.get("limit"))
146 .and_then(|v| v.as_u64())
147 .unwrap_or(20) as u8;
148 let pins_only = params
149 .and_then(|p| p.get("pins_only"))
150 .and_then(|v| v.as_bool())
151 .unwrap_or(false);
152 let exact = params
153 .and_then(|p| p.get("exact"))
154 .and_then(|v| v.as_bool())
155 .unwrap_or(false);
156 let play = params
157 .and_then(|p| p.get("play"))
158 .and_then(|v| v.as_bool())
159 .unwrap_or(false);
160 let sort = params
161 .and_then(|p| p.get("sort"))
162 .and_then(|v| v.as_bool())
163 .unwrap_or(false);
164
165 let options = SearchOptions {
166 limit,
167 pins_only,
168 exact,
169 filters: SearchFilters {
170 artist: params
171 .and_then(|p| p.get("artist"))
172 .and_then(|v| v.as_str())
173 .map(String::from),
174 album: params
175 .and_then(|p| p.get("album"))
176 .and_then(|v| v.as_str())
177 .map(String::from),
178 track: params
179 .and_then(|p| p.get("track"))
180 .and_then(|v| v.as_str())
181 .map(String::from),
182 year: params
183 .and_then(|p| p.get("year"))
184 .and_then(|v| v.as_str())
185 .map(String::from),
186 genre: params
187 .and_then(|p| p.get("genre"))
188 .and_then(|v| v.as_str())
189 .map(String::from),
190 isrc: params
191 .and_then(|p| p.get("isrc"))
192 .and_then(|v| v.as_str())
193 .map(String::from),
194 upc: params
195 .and_then(|p| p.get("upc"))
196 .and_then(|v| v.as_str())
197 .map(String::from),
198 new: params
199 .and_then(|p| p.get("new"))
200 .and_then(|v| v.as_bool())
201 .unwrap_or(false),
202 hipster: params
203 .and_then(|p| p.get("hipster"))
204 .and_then(|v| v.as_bool())
205 .unwrap_or(false),
206 },
207 play,
208 sort,
209 };
210
211 commands::search_command(query, &types, options).await
212 }
213
214 "pin.add" => {
218 let resource_type = params
219 .and_then(|p| p.get("type"))
220 .and_then(|v| v.as_str())
221 .unwrap_or("track");
222 let url_or_id = params
223 .and_then(|p| p.get("id"))
224 .and_then(|v| v.as_str())
225 .unwrap_or("");
226 let alias = params
227 .and_then(|p| p.get("alias"))
228 .and_then(|v| v.as_str())
229 .unwrap_or("");
230 let tags = params.and_then(|p| p.get("tags")).and_then(|v| v.as_str());
231 commands::pin_add(resource_type, url_or_id, alias, tags).await
232 }
233 "pin.remove" => {
234 let alias_or_id = params
235 .and_then(|p| p.get("id"))
236 .and_then(|v| v.as_str())
237 .unwrap_or("");
238 commands::pin_remove(alias_or_id).await
239 }
240 "pin.list" => {
241 let resource_type = params.and_then(|p| p.get("type")).and_then(|v| v.as_str());
242 commands::pin_list(resource_type).await
243 }
244
245 "playlist.list" => {
249 let limit = params
250 .and_then(|p| p.get("limit"))
251 .and_then(|v| v.as_u64())
252 .unwrap_or(20) as u8;
253 let offset = params
254 .and_then(|p| p.get("offset"))
255 .and_then(|v| v.as_u64())
256 .unwrap_or(0) as u32;
257 commands::playlist_list(limit, offset).await
258 }
259 "playlist.get" => {
260 let id = params
261 .and_then(|p| p.get("id"))
262 .and_then(|v| v.as_str())
263 .unwrap_or("");
264 commands::playlist_get(id).await
265 }
266 "playlist.create" => {
267 let name = params
268 .and_then(|p| p.get("name"))
269 .and_then(|v| v.as_str())
270 .unwrap_or("New Playlist");
271 let description = params
272 .and_then(|p| p.get("description"))
273 .and_then(|v| v.as_str());
274 let public = params
275 .and_then(|p| p.get("public"))
276 .and_then(|v| v.as_bool())
277 .unwrap_or(false);
278 commands::playlist_create(name, description, public).await
279 }
280 "playlist.add" => {
281 let id = params
282 .and_then(|p| p.get("id"))
283 .and_then(|v| v.as_str())
284 .unwrap_or("");
285 let uris: Vec<String> = params
286 .and_then(|p| p.get("uris"))
287 .and_then(|v| v.as_array())
288 .map(|arr| {
289 arr.iter()
290 .filter_map(|v| v.as_str().map(String::from))
291 .collect()
292 })
293 .unwrap_or_default();
294 let now_playing = params
295 .and_then(|p| p.get("now_playing"))
296 .and_then(|v| v.as_bool())
297 .unwrap_or(false);
298 let position = params
299 .and_then(|p| p.get("position"))
300 .and_then(|v| v.as_u64())
301 .map(|p| p as u32);
302 let dry_run = params
303 .and_then(|p| p.get("dry_run"))
304 .and_then(|v| v.as_bool())
305 .unwrap_or(false);
306 commands::playlist_add(id, &uris, now_playing, position, dry_run).await
307 }
308 "playlist.remove" => {
309 let id = params
310 .and_then(|p| p.get("id"))
311 .and_then(|v| v.as_str())
312 .unwrap_or("");
313 let uris: Vec<String> = params
314 .and_then(|p| p.get("uris"))
315 .and_then(|v| v.as_array())
316 .map(|arr| {
317 arr.iter()
318 .filter_map(|v| v.as_str().map(String::from))
319 .collect()
320 })
321 .unwrap_or_default();
322 let dry_run = params
323 .and_then(|p| p.get("dry_run"))
324 .and_then(|v| v.as_bool())
325 .unwrap_or(false);
326 commands::playlist_remove(id, &uris, dry_run).await
327 }
328 "playlist.edit" => {
329 let id = params
330 .and_then(|p| p.get("id"))
331 .and_then(|v| v.as_str())
332 .unwrap_or("");
333 let name = params.and_then(|p| p.get("name")).and_then(|v| v.as_str());
334 let description = params
335 .and_then(|p| p.get("description"))
336 .and_then(|v| v.as_str());
337 let public = params
338 .and_then(|p| p.get("public"))
339 .and_then(|v| v.as_bool());
340 commands::playlist_edit(id, name, description, public).await
341 }
342 "playlist.reorder" => {
343 let id = params
344 .and_then(|p| p.get("id"))
345 .and_then(|v| v.as_str())
346 .unwrap_or("");
347 let from = params
348 .and_then(|p| p.get("from"))
349 .and_then(|v| v.as_u64())
350 .unwrap_or(0) as u32;
351 let to = params
352 .and_then(|p| p.get("to"))
353 .and_then(|v| v.as_u64())
354 .unwrap_or(0) as u32;
355 let count = params
356 .and_then(|p| p.get("count"))
357 .and_then(|v| v.as_u64())
358 .unwrap_or(1) as u32;
359 commands::playlist_reorder(id, from, to, count).await
360 }
361 "playlist.follow" => {
362 let id = params
363 .and_then(|p| p.get("id"))
364 .and_then(|v| v.as_str())
365 .unwrap_or("");
366 let public = params
367 .and_then(|p| p.get("public"))
368 .and_then(|v| v.as_bool())
369 .unwrap_or(true);
370 commands::playlist_follow(id, public).await
371 }
372 "playlist.unfollow" => {
373 let id = params
374 .and_then(|p| p.get("id"))
375 .and_then(|v| v.as_str())
376 .unwrap_or("");
377 commands::playlist_unfollow(id).await
378 }
379 "playlist.duplicate" => {
380 let id = params
381 .and_then(|p| p.get("id"))
382 .and_then(|v| v.as_str())
383 .unwrap_or("");
384 let name = params.and_then(|p| p.get("name")).and_then(|v| v.as_str());
385 commands::playlist_duplicate(id, name).await
386 }
387 "playlist.cover" => {
388 let id = params
389 .and_then(|p| p.get("id"))
390 .and_then(|v| v.as_str())
391 .unwrap_or("");
392 commands::playlist_cover(id).await
393 }
394 "playlist.user" => {
395 let user_id = params
396 .and_then(|p| p.get("user_id"))
397 .and_then(|v| v.as_str())
398 .unwrap_or("");
399 commands::playlist_user(user_id).await
400 }
401
402 "library.list" => {
406 let limit = params
407 .and_then(|p| p.get("limit"))
408 .and_then(|v| v.as_u64())
409 .unwrap_or(20) as u8;
410 let offset = params
411 .and_then(|p| p.get("offset"))
412 .and_then(|v| v.as_u64())
413 .unwrap_or(0) as u32;
414 commands::library_list(limit, offset).await
415 }
416 "library.save" => {
417 let ids: Vec<String> = params
418 .and_then(|p| p.get("ids"))
419 .and_then(|v| v.as_array())
420 .map(|arr| {
421 arr.iter()
422 .filter_map(|v| v.as_str().map(String::from))
423 .collect()
424 })
425 .unwrap_or_default();
426 let now_playing = params
427 .and_then(|p| p.get("now_playing"))
428 .and_then(|v| v.as_bool())
429 .unwrap_or(false);
430 let dry_run = params
431 .and_then(|p| p.get("dry_run"))
432 .and_then(|v| v.as_bool())
433 .unwrap_or(false);
434 commands::library_save(&ids, now_playing, dry_run).await
435 }
436 "library.remove" => {
437 let ids: Vec<String> = params
438 .and_then(|p| p.get("ids"))
439 .and_then(|v| v.as_array())
440 .map(|arr| {
441 arr.iter()
442 .filter_map(|v| v.as_str().map(String::from))
443 .collect()
444 })
445 .unwrap_or_default();
446 let dry_run = params
447 .and_then(|p| p.get("dry_run"))
448 .and_then(|v| v.as_bool())
449 .unwrap_or(false);
450 commands::library_remove(&ids, dry_run).await
451 }
452 "library.check" => {
453 let ids: Vec<String> = params
454 .and_then(|p| p.get("ids"))
455 .and_then(|v| v.as_array())
456 .map(|arr| {
457 arr.iter()
458 .filter_map(|v| v.as_str().map(String::from))
459 .collect()
460 })
461 .unwrap_or_default();
462 commands::library_check(&ids).await
463 }
464
465 "info.track" => {
469 let id = params.and_then(|p| p.get("id")).and_then(|v| v.as_str());
470 let id_only = params
471 .and_then(|p| p.get("id_only"))
472 .and_then(|v| v.as_bool())
473 .unwrap_or(false);
474 commands::info_track(id, id_only).await
475 }
476 "info.album" => {
477 let id = params.and_then(|p| p.get("id")).and_then(|v| v.as_str());
478 let id_only = params
479 .and_then(|p| p.get("id_only"))
480 .and_then(|v| v.as_bool())
481 .unwrap_or(false);
482 commands::info_album(id, id_only).await
483 }
484 "info.artist" => {
485 let id = params.and_then(|p| p.get("id")).and_then(|v| v.as_str());
486 let id_only = params
487 .and_then(|p| p.get("id_only"))
488 .and_then(|v| v.as_bool())
489 .unwrap_or(false);
490 let view = params
491 .and_then(|p| p.get("view"))
492 .and_then(|v| v.as_str())
493 .unwrap_or("details");
494 let market = params
495 .and_then(|p| p.get("market"))
496 .and_then(|v| v.as_str())
497 .map(String::from)
498 .unwrap_or_default();
499 let limit = params
500 .and_then(|p| p.get("limit"))
501 .and_then(|v| v.as_u64())
502 .unwrap_or(20) as u8;
503 let offset = params
504 .and_then(|p| p.get("offset"))
505 .and_then(|v| v.as_u64())
506 .unwrap_or(0) as u32;
507
508 let artist_view = match view {
509 "top_tracks" => ArtistView::TopTracks,
510 "albums" => ArtistView::Albums,
511 "related" => ArtistView::Related,
512 _ => ArtistView::Details,
513 };
514
515 let query = ArtistQuery::new()
516 .with_id(id.map(String::from))
517 .id_only(id_only)
518 .view(artist_view)
519 .market(market)
520 .paginate(limit, offset);
521 commands::info_artist(query).await
522 }
523
524 "user.profile" => commands::user_profile().await,
528 "user.top" => {
529 let item_type = params
530 .and_then(|p| p.get("type"))
531 .and_then(|v| v.as_str())
532 .unwrap_or("tracks");
533 let range = params
534 .and_then(|p| p.get("range"))
535 .and_then(|v| v.as_str())
536 .unwrap_or("medium");
537 let limit = params
538 .and_then(|p| p.get("limit"))
539 .and_then(|v| v.as_u64())
540 .unwrap_or(20) as u8;
541 commands::user_top(item_type, range, limit).await
542 }
543 "user.get" => {
544 let user_id = params
545 .and_then(|p| p.get("id"))
546 .and_then(|v| v.as_str())
547 .unwrap_or("");
548 commands::user_get(user_id).await
549 }
550
551 "show.get" => {
555 let id = params
556 .and_then(|p| p.get("id"))
557 .and_then(|v| v.as_str())
558 .unwrap_or("");
559 commands::show_get(id).await
560 }
561 "show.episodes" => {
562 let id = params
563 .and_then(|p| p.get("id"))
564 .and_then(|v| v.as_str())
565 .unwrap_or("");
566 let limit = params
567 .and_then(|p| p.get("limit"))
568 .and_then(|v| v.as_u64())
569 .unwrap_or(20) as u8;
570 let offset = params
571 .and_then(|p| p.get("offset"))
572 .and_then(|v| v.as_u64())
573 .unwrap_or(0) as u32;
574 commands::show_episodes(id, limit, offset).await
575 }
576 "show.list" => {
577 let limit = params
578 .and_then(|p| p.get("limit"))
579 .and_then(|v| v.as_u64())
580 .unwrap_or(20) as u8;
581 let offset = params
582 .and_then(|p| p.get("offset"))
583 .and_then(|v| v.as_u64())
584 .unwrap_or(0) as u32;
585 commands::show_list(limit, offset).await
586 }
587 "show.save" => {
588 let ids: Vec<String> = params
589 .and_then(|p| p.get("ids"))
590 .and_then(|v| v.as_array())
591 .map(|arr| {
592 arr.iter()
593 .filter_map(|v| v.as_str().map(String::from))
594 .collect()
595 })
596 .unwrap_or_default();
597 commands::show_save(&ids).await
598 }
599 "show.remove" => {
600 let ids: Vec<String> = params
601 .and_then(|p| p.get("ids"))
602 .and_then(|v| v.as_array())
603 .map(|arr| {
604 arr.iter()
605 .filter_map(|v| v.as_str().map(String::from))
606 .collect()
607 })
608 .unwrap_or_default();
609 commands::show_remove(&ids).await
610 }
611 "show.check" => {
612 let ids: Vec<String> = params
613 .and_then(|p| p.get("ids"))
614 .and_then(|v| v.as_array())
615 .map(|arr| {
616 arr.iter()
617 .filter_map(|v| v.as_str().map(String::from))
618 .collect()
619 })
620 .unwrap_or_default();
621 commands::show_check(&ids).await
622 }
623
624 "episode.get" => {
628 let id = params
629 .and_then(|p| p.get("id"))
630 .and_then(|v| v.as_str())
631 .unwrap_or("");
632 commands::episode_get(id).await
633 }
634 "episode.list" => {
635 let limit = params
636 .and_then(|p| p.get("limit"))
637 .and_then(|v| v.as_u64())
638 .unwrap_or(20) as u8;
639 let offset = params
640 .and_then(|p| p.get("offset"))
641 .and_then(|v| v.as_u64())
642 .unwrap_or(0) as u32;
643 commands::episode_list(limit, offset).await
644 }
645 "episode.save" => {
646 let ids: Vec<String> = params
647 .and_then(|p| p.get("ids"))
648 .and_then(|v| v.as_array())
649 .map(|arr| {
650 arr.iter()
651 .filter_map(|v| v.as_str().map(String::from))
652 .collect()
653 })
654 .unwrap_or_default();
655 commands::episode_save(&ids).await
656 }
657 "episode.remove" => {
658 let ids: Vec<String> = params
659 .and_then(|p| p.get("ids"))
660 .and_then(|v| v.as_array())
661 .map(|arr| {
662 arr.iter()
663 .filter_map(|v| v.as_str().map(String::from))
664 .collect()
665 })
666 .unwrap_or_default();
667 commands::episode_remove(&ids).await
668 }
669 "episode.check" => {
670 let ids: Vec<String> = params
671 .and_then(|p| p.get("ids"))
672 .and_then(|v| v.as_array())
673 .map(|arr| {
674 arr.iter()
675 .filter_map(|v| v.as_str().map(String::from))
676 .collect()
677 })
678 .unwrap_or_default();
679 commands::episode_check(&ids).await
680 }
681
682 "audiobook.get" => {
686 let id = params
687 .and_then(|p| p.get("id"))
688 .and_then(|v| v.as_str())
689 .unwrap_or("");
690 commands::audiobook_get(id).await
691 }
692 "audiobook.chapters" => {
693 let id = params
694 .and_then(|p| p.get("id"))
695 .and_then(|v| v.as_str())
696 .unwrap_or("");
697 let limit = params
698 .and_then(|p| p.get("limit"))
699 .and_then(|v| v.as_u64())
700 .unwrap_or(20) as u8;
701 let offset = params
702 .and_then(|p| p.get("offset"))
703 .and_then(|v| v.as_u64())
704 .unwrap_or(0) as u32;
705 commands::audiobook_chapters(id, limit, offset).await
706 }
707 "audiobook.list" => {
708 let limit = params
709 .and_then(|p| p.get("limit"))
710 .and_then(|v| v.as_u64())
711 .unwrap_or(20) as u8;
712 let offset = params
713 .and_then(|p| p.get("offset"))
714 .and_then(|v| v.as_u64())
715 .unwrap_or(0) as u32;
716 commands::audiobook_list(limit, offset).await
717 }
718 "audiobook.save" => {
719 let ids: Vec<String> = params
720 .and_then(|p| p.get("ids"))
721 .and_then(|v| v.as_array())
722 .map(|arr| {
723 arr.iter()
724 .filter_map(|v| v.as_str().map(String::from))
725 .collect()
726 })
727 .unwrap_or_default();
728 commands::audiobook_save(&ids).await
729 }
730 "audiobook.remove" => {
731 let ids: Vec<String> = params
732 .and_then(|p| p.get("ids"))
733 .and_then(|v| v.as_array())
734 .map(|arr| {
735 arr.iter()
736 .filter_map(|v| v.as_str().map(String::from))
737 .collect()
738 })
739 .unwrap_or_default();
740 commands::audiobook_remove(&ids).await
741 }
742 "audiobook.check" => {
743 let ids: Vec<String> = params
744 .and_then(|p| p.get("ids"))
745 .and_then(|v| v.as_array())
746 .map(|arr| {
747 arr.iter()
748 .filter_map(|v| v.as_str().map(String::from))
749 .collect()
750 })
751 .unwrap_or_default();
752 commands::audiobook_check(&ids).await
753 }
754
755 "album.list" => {
759 let limit = params
760 .and_then(|p| p.get("limit"))
761 .and_then(|v| v.as_u64())
762 .unwrap_or(20) as u8;
763 let offset = params
764 .and_then(|p| p.get("offset"))
765 .and_then(|v| v.as_u64())
766 .unwrap_or(0) as u32;
767 commands::album_list(limit, offset).await
768 }
769 "album.tracks" => {
770 let id = params
771 .and_then(|p| p.get("id"))
772 .and_then(|v| v.as_str())
773 .unwrap_or("");
774 let limit = params
775 .and_then(|p| p.get("limit"))
776 .and_then(|v| v.as_u64())
777 .unwrap_or(20) as u8;
778 let offset = params
779 .and_then(|p| p.get("offset"))
780 .and_then(|v| v.as_u64())
781 .unwrap_or(0) as u32;
782 commands::album_tracks(id, limit, offset).await
783 }
784 "album.save" => {
785 let ids: Vec<String> = params
786 .and_then(|p| p.get("ids"))
787 .and_then(|v| v.as_array())
788 .map(|arr| {
789 arr.iter()
790 .filter_map(|v| v.as_str().map(String::from))
791 .collect()
792 })
793 .unwrap_or_default();
794 commands::album_save(&ids).await
795 }
796 "album.remove" => {
797 let ids: Vec<String> = params
798 .and_then(|p| p.get("ids"))
799 .and_then(|v| v.as_array())
800 .map(|arr| {
801 arr.iter()
802 .filter_map(|v| v.as_str().map(String::from))
803 .collect()
804 })
805 .unwrap_or_default();
806 commands::album_remove(&ids).await
807 }
808 "album.check" => {
809 let ids: Vec<String> = params
810 .and_then(|p| p.get("ids"))
811 .and_then(|v| v.as_array())
812 .map(|arr| {
813 arr.iter()
814 .filter_map(|v| v.as_str().map(String::from))
815 .collect()
816 })
817 .unwrap_or_default();
818 commands::album_check(&ids).await
819 }
820 "album.newReleases" => {
821 let limit = params
822 .and_then(|p| p.get("limit"))
823 .and_then(|v| v.as_u64())
824 .unwrap_or(20) as u8;
825 let offset = params
826 .and_then(|p| p.get("offset"))
827 .and_then(|v| v.as_u64())
828 .unwrap_or(0) as u32;
829 commands::album_new_releases(limit, offset).await
830 }
831
832 "chapter.get" => {
836 let id = params
837 .and_then(|p| p.get("id"))
838 .and_then(|v| v.as_str())
839 .unwrap_or("");
840 commands::chapter_get(id).await
841 }
842
843 "category.list" => {
847 let limit = params
848 .and_then(|p| p.get("limit"))
849 .and_then(|v| v.as_u64())
850 .unwrap_or(20) as u8;
851 let offset = params
852 .and_then(|p| p.get("offset"))
853 .and_then(|v| v.as_u64())
854 .unwrap_or(0) as u32;
855 commands::category_list(limit, offset).await
856 }
857 "category.get" => {
858 let id = params
859 .and_then(|p| p.get("id"))
860 .and_then(|v| v.as_str())
861 .unwrap_or("");
862 commands::category_get(id).await
863 }
864 "category.playlists" => {
865 let id = params
866 .and_then(|p| p.get("id"))
867 .and_then(|v| v.as_str())
868 .unwrap_or("");
869 let limit = params
870 .and_then(|p| p.get("limit"))
871 .and_then(|v| v.as_u64())
872 .unwrap_or(20) as u8;
873 let offset = params
874 .and_then(|p| p.get("offset"))
875 .and_then(|v| v.as_u64())
876 .unwrap_or(0) as u32;
877 commands::category_playlists(id, limit, offset).await
878 }
879
880 "follow.artist" => {
884 let ids: Vec<String> = params
885 .and_then(|p| p.get("ids"))
886 .and_then(|v| v.as_array())
887 .map(|arr| {
888 arr.iter()
889 .filter_map(|v| v.as_str().map(String::from))
890 .collect()
891 })
892 .unwrap_or_default();
893 let dry_run = params
894 .and_then(|p| p.get("dry_run"))
895 .and_then(|v| v.as_bool())
896 .unwrap_or(false);
897 commands::follow_artist(&ids, dry_run).await
898 }
899 "follow.user" => {
900 let ids: Vec<String> = params
901 .and_then(|p| p.get("ids"))
902 .and_then(|v| v.as_array())
903 .map(|arr| {
904 arr.iter()
905 .filter_map(|v| v.as_str().map(String::from))
906 .collect()
907 })
908 .unwrap_or_default();
909 let dry_run = params
910 .and_then(|p| p.get("dry_run"))
911 .and_then(|v| v.as_bool())
912 .unwrap_or(false);
913 commands::follow_user(&ids, dry_run).await
914 }
915 "follow.unfollowArtist" => {
916 let ids: Vec<String> = params
917 .and_then(|p| p.get("ids"))
918 .and_then(|v| v.as_array())
919 .map(|arr| {
920 arr.iter()
921 .filter_map(|v| v.as_str().map(String::from))
922 .collect()
923 })
924 .unwrap_or_default();
925 let dry_run = params
926 .and_then(|p| p.get("dry_run"))
927 .and_then(|v| v.as_bool())
928 .unwrap_or(false);
929 commands::unfollow_artist(&ids, dry_run).await
930 }
931 "follow.unfollowUser" => {
932 let ids: Vec<String> = params
933 .and_then(|p| p.get("ids"))
934 .and_then(|v| v.as_array())
935 .map(|arr| {
936 arr.iter()
937 .filter_map(|v| v.as_str().map(String::from))
938 .collect()
939 })
940 .unwrap_or_default();
941 let dry_run = params
942 .and_then(|p| p.get("dry_run"))
943 .and_then(|v| v.as_bool())
944 .unwrap_or(false);
945 commands::unfollow_user(&ids, dry_run).await
946 }
947 "follow.list" => {
948 let limit = params
949 .and_then(|p| p.get("limit"))
950 .and_then(|v| v.as_u64())
951 .unwrap_or(20) as u8;
952 commands::follow_list(limit).await
953 }
954 "follow.checkArtist" => {
955 let ids: Vec<String> = params
956 .and_then(|p| p.get("ids"))
957 .and_then(|v| v.as_array())
958 .map(|arr| {
959 arr.iter()
960 .filter_map(|v| v.as_str().map(String::from))
961 .collect()
962 })
963 .unwrap_or_default();
964 commands::follow_check_artist(&ids).await
965 }
966 "follow.checkUser" => {
967 let ids: Vec<String> = params
968 .and_then(|p| p.get("ids"))
969 .and_then(|v| v.as_array())
970 .map(|arr| {
971 arr.iter()
972 .filter_map(|v| v.as_str().map(String::from))
973 .collect()
974 })
975 .unwrap_or_default();
976 commands::follow_check_user(&ids).await
977 }
978
979 "markets.list" => commands::markets_list().await,
983
984 _ => Response::err(
988 404, format!("Method not found: {}", request.method),
990 ErrorKind::Validation,
991 ),
992 }
993 }
994}
995
996impl Default for Dispatcher {
997 fn default() -> Self {
998 Self::new()
999 }
1000}
1001
1002#[cfg(test)]
1003mod tests {
1004 use super::*;
1005 use crate::rpc::protocol::RpcRequest;
1006
1007 fn make_request(method: &str, params: Option<serde_json::Value>) -> RpcRequest {
1008 RpcRequest {
1009 jsonrpc: "2.0".to_string(),
1010 method: method.to_string(),
1011 params,
1012 id: Some(serde_json::json!(1)),
1013 }
1014 }
1015
1016 #[tokio::test]
1017 async fn test_ping() {
1018 let dispatcher = Dispatcher::new();
1019 let req = make_request("ping", None);
1020 let resp = dispatcher.dispatch(&req).await;
1021 assert_eq!(resp.message, "pong");
1022 }
1023
1024 #[tokio::test]
1025 async fn test_version() {
1026 let dispatcher = Dispatcher::new();
1027 let req = make_request("version", None);
1028 let resp = dispatcher.dispatch(&req).await;
1029 assert!(resp.payload.is_some());
1030 let payload = resp.payload.unwrap();
1031 assert!(payload.get("version").is_some());
1032 assert!(payload.get("name").is_some());
1033 }
1034
1035 #[tokio::test]
1036 async fn test_unknown_method() {
1037 let dispatcher = Dispatcher::new();
1038 let req = make_request("unknown.method", None);
1039 let resp = dispatcher.dispatch(&req).await;
1040 assert_eq!(resp.code, 404);
1041 assert!(resp.message.contains("Method not found"));
1042 }
1043
1044 #[tokio::test]
1045 async fn test_player_transfer_missing_device() {
1046 let dispatcher = Dispatcher::new();
1047 let req = make_request("player.transfer", None);
1048 let resp = dispatcher.dispatch(&req).await;
1049 assert_eq!(resp.code, 400);
1050 assert!(resp.message.contains("device"));
1051 }
1052
1053 #[test]
1054 fn test_dispatcher_default() {
1055 let _dispatcher = Dispatcher;
1056 }
1057}