spotify_cli/cli/commands/
macros.rs

1//! Resource command macros to reduce boilerplate
2//!
3//! These macros generate common patterns for CRUD operations on Spotify resources.
4
5/// Generate a "get single resource by ID" command
6///
7/// # Example
8/// ```ignore
9/// resource_get!(show_get, get_show::get_show, "Show");
10/// ```
11#[macro_export]
12macro_rules! resource_get {
13    ($fn_name:ident, $endpoint_mod:ident :: $endpoint_fn:ident, $resource_name:literal) => {
14        pub async fn $fn_name(id: &str) -> $crate::io::output::Response {
15            let id = $crate::cli::commands::extract_id(id);
16
17            $crate::cli::commands::with_client(|client| async move {
18                match $endpoint_mod::$endpoint_fn(&client, &id).await {
19                    Ok(Some(payload)) => $crate::io::output::Response::success_with_payload(
20                        200,
21                        concat!($resource_name, " details"),
22                        payload,
23                    ),
24                    Ok(None) => $crate::io::output::Response::err(
25                        404,
26                        concat!($resource_name, " not found"),
27                        $crate::io::output::ErrorKind::NotFound,
28                    ),
29                    Err(e) => $crate::io::output::Response::from_http_error(
30                        &e,
31                        concat!("Failed to get ", $resource_name),
32                    ),
33                }
34            })
35            .await
36        }
37    };
38}
39
40/// Generate a "list resources with pagination" command
41///
42/// # Example
43/// ```ignore
44/// resource_list!(show_list, get_users_saved_shows::get_users_saved_shows, "Saved shows");
45/// ```
46#[macro_export]
47macro_rules! resource_list {
48    ($fn_name:ident, $endpoint_mod:ident :: $endpoint_fn:ident, $success_msg:literal) => {
49        pub async fn $fn_name(limit: u8, offset: u32) -> $crate::io::output::Response {
50            $crate::cli::commands::with_client(|client| async move {
51                match $endpoint_mod::$endpoint_fn(&client, Some(limit), Some(offset)).await {
52                    Ok(Some(payload)) => {
53                        $crate::io::output::Response::success_with_payload(200, $success_msg, payload)
54                    }
55                    Ok(None) => $crate::io::output::Response::success_with_payload(
56                        200,
57                        $success_msg,
58                        serde_json::json!({ "items": [] }),
59                    ),
60                    Err(e) => $crate::io::output::Response::from_http_error(
61                        &e,
62                        concat!("Failed to get ", $success_msg),
63                    ),
64                }
65            })
66            .await
67        }
68    };
69}
70
71/// Generate a "list sub-resources with ID and pagination" command (e.g., show episodes, audiobook chapters)
72///
73/// # Example
74/// ```ignore
75/// resource_list_with_id!(show_episodes, get_show_episodes::get_show_episodes, "Show episodes");
76/// ```
77#[macro_export]
78macro_rules! resource_list_with_id {
79    ($fn_name:ident, $endpoint_mod:ident :: $endpoint_fn:ident, $success_msg:literal, $empty_msg:literal) => {
80        pub async fn $fn_name(id: &str, limit: u8, offset: u32) -> $crate::io::output::Response {
81            let id = $crate::cli::commands::extract_id(id);
82
83            $crate::cli::commands::with_client(|client| async move {
84                match $endpoint_mod::$endpoint_fn(&client, &id, Some(limit), Some(offset)).await {
85                    Ok(Some(payload)) => {
86                        $crate::io::output::Response::success_with_payload(200, $success_msg, payload)
87                    }
88                    Ok(None) => $crate::io::output::Response::success_with_payload(
89                        200,
90                        $empty_msg,
91                        serde_json::json!({ "items": [] }),
92                    ),
93                    Err(e) => $crate::io::output::Response::from_http_error(
94                        &e,
95                        concat!("Failed to get ", $success_msg),
96                    ),
97                }
98            })
99            .await
100        }
101    };
102}
103
104/// Generate a "save resources" command
105///
106/// # Example
107/// ```ignore
108/// resource_save!(show_save, save_shows_for_current_user::save_shows, "show");
109/// ```
110#[macro_export]
111macro_rules! resource_save {
112    ($fn_name:ident, $endpoint_mod:ident :: $endpoint_fn:ident, $resource_name:literal) => {
113        pub async fn $fn_name(ids: &[String]) -> $crate::io::output::Response {
114            let ids = ids.to_vec();
115            let count = ids.len();
116
117            $crate::cli::commands::with_client(|client| async move {
118                match $endpoint_mod::$endpoint_fn(&client, &ids).await {
119                    Ok(_) => $crate::io::output::Response::success(
120                        200,
121                        &format!(concat!("Saved {} ", $resource_name, "(s)"), count),
122                    ),
123                    Err(e) => $crate::io::output::Response::from_http_error(
124                        &e,
125                        concat!("Failed to save ", $resource_name, "s"),
126                    ),
127                }
128            })
129            .await
130        }
131    };
132}
133
134/// Generate a "remove resources" command
135///
136/// # Example
137/// ```ignore
138/// resource_remove!(show_remove, remove_users_saved_shows::remove_shows, "show");
139/// ```
140#[macro_export]
141macro_rules! resource_remove {
142    ($fn_name:ident, $endpoint_mod:ident :: $endpoint_fn:ident, $resource_name:literal) => {
143        pub async fn $fn_name(ids: &[String]) -> $crate::io::output::Response {
144            let ids = ids.to_vec();
145            let count = ids.len();
146
147            $crate::cli::commands::with_client(|client| async move {
148                match $endpoint_mod::$endpoint_fn(&client, &ids).await {
149                    Ok(_) => $crate::io::output::Response::success(
150                        200,
151                        &format!(concat!("Removed {} ", $resource_name, "(s)"), count),
152                    ),
153                    Err(e) => $crate::io::output::Response::from_http_error(
154                        &e,
155                        concat!("Failed to remove ", $resource_name, "s"),
156                    ),
157                }
158            })
159            .await
160        }
161    };
162}
163
164/// Generate a "check if resources are saved" command
165///
166/// # Example
167/// ```ignore
168/// resource_check!(show_check, check_users_saved_shows::check_saved_shows);
169/// ```
170#[macro_export]
171macro_rules! resource_check {
172    ($fn_name:ident, $endpoint_mod:ident :: $endpoint_fn:ident) => {
173        pub async fn $fn_name(ids: &[String]) -> $crate::io::output::Response {
174            let ids = ids.to_vec();
175
176            $crate::cli::commands::with_client(|client| async move {
177                match $endpoint_mod::$endpoint_fn(&client, &ids).await {
178                    Ok(Some(payload)) => $crate::io::output::Response::success_with_payload(
179                        200,
180                        "Check results",
181                        payload,
182                    ),
183                    Ok(None) => $crate::io::output::Response::success_with_payload(
184                        200,
185                        "Check results",
186                        serde_json::json!([]),
187                    ),
188                    Err(e) => $crate::io::output::Response::from_http_error(&e, "Failed to check"),
189                }
190            })
191            .await
192        }
193    };
194}