Skip to main content

agent_client_protocol_derive/
lib.rs

1//! Derive macros for Agent Client Protocol JSON-RPC traits.
2//!
3//! This crate provides derive macros to reduce boilerplate when implementing
4//! custom JSON-RPC requests, notifications, and response types.
5//!
6//! # Example
7//!
8//! ```ignore
9//! use agent_client_protocol::{JsonRpcRequest, JsonRpcNotification, JsonRpcResponse};
10//!
11//! #[derive(Debug, Clone, Serialize, Deserialize, JsonRpcRequest)]
12//! #[request(method = "_hello", response = HelloResponse)]
13//! struct HelloRequest {
14//!     name: String,
15//! }
16//!
17//! #[derive(Debug, Serialize, Deserialize, JsonRpcResponse)]
18//! #[response(method = "_hello")]
19//! struct HelloResponse {
20//!     greeting: String,
21//! }
22//!
23//! #[derive(Debug, Clone, Serialize, Deserialize, JsonRpcNotification)]
24//! #[notification(method = "_ping")]
25//! struct PingNotification {
26//!     timestamp: u64,
27//! }
28//! ```
29//!
30//! # Using within the `agent_client_protocol` crate
31//!
32//! When using these derives within the `agent_client_protocol` crate itself, add `crate = crate`:
33//!
34//! ```ignore
35//! #[derive(JsonRpcRequest)]
36//! #[request(method = "_foo", response = FooResponse, crate = crate)]
37//! struct FooRequest { ... }
38//! ```
39
40use proc_macro::TokenStream;
41use quote::quote;
42use syn::{DeriveInput, Expr, Lit, Path, Type, parse_macro_input};
43
44/// Derive macro for implementing `JsonRpcRequest` and `JsonRpcMessage` traits.
45///
46/// # Attributes
47///
48/// - `#[request(method = "method_name", response = ResponseType)]`
49/// - `#[request(method = "method_name", response = ResponseType, crate = crate)]` - for use within the `agent_client_protocol` crate
50///
51/// # Example
52///
53/// ```ignore
54/// #[derive(Debug, Clone, Serialize, Deserialize, JsonRpcRequest)]
55/// #[request(method = "_hello", response = HelloResponse)]
56/// struct HelloRequest {
57///     name: String,
58/// }
59/// ```
60#[proc_macro_derive(JsonRpcRequest, attributes(request))]
61pub fn derive_json_rpc_request(input: TokenStream) -> TokenStream {
62    let input = parse_macro_input!(input as DeriveInput);
63    let name = &input.ident;
64
65    // Parse attributes
66    let (method, response_type, krate) = match parse_request_attrs(&input) {
67        Ok(attrs) => attrs,
68        Err(e) => return e.to_compile_error().into(),
69    };
70
71    let expanded = quote! {
72        impl #krate::JsonRpcMessage for #name {
73            fn matches_method(method: &str) -> bool {
74                method == #method
75            }
76
77            fn method(&self) -> &str {
78                #method
79            }
80
81            fn to_untyped_message(&self) -> Result<#krate::UntypedMessage, #krate::Error> {
82                #krate::UntypedMessage::new(#method, self)
83            }
84
85            fn parse_message(
86                method: &str,
87                params: &impl serde::Serialize,
88            ) -> Result<Self, #krate::Error> {
89                if method != #method {
90                    return Err(#krate::Error::method_not_found());
91                }
92                #krate::util::json_cast_params(params)
93            }
94        }
95
96        impl #krate::JsonRpcRequest for #name {
97            type Response = #response_type;
98        }
99    };
100
101    TokenStream::from(expanded)
102}
103
104/// Derive macro for implementing `JsonRpcNotification` and `JsonRpcMessage` traits.
105///
106/// # Attributes
107///
108/// - `#[notification(method = "method_name")]`
109/// - `#[notification(method = "method_name", crate = crate)]` - for use within the `agent_client_protocol` crate
110///
111/// # Example
112///
113/// ```ignore
114/// #[derive(Debug, Clone, Serialize, Deserialize, JsonRpcNotification)]
115/// #[notification(method = "_ping")]
116/// struct PingNotification {
117///     timestamp: u64,
118/// }
119/// ```
120#[proc_macro_derive(JsonRpcNotification, attributes(notification))]
121pub fn derive_json_rpc_notification(input: TokenStream) -> TokenStream {
122    let input = parse_macro_input!(input as DeriveInput);
123    let name = &input.ident;
124
125    // Parse attributes
126    let (method, krate) = match parse_notification_attrs(&input) {
127        Ok(attrs) => attrs,
128        Err(e) => return e.to_compile_error().into(),
129    };
130
131    let expanded = quote! {
132        impl #krate::JsonRpcMessage for #name {
133            fn matches_method(method: &str) -> bool {
134                method == #method
135            }
136
137            fn method(&self) -> &str {
138                #method
139            }
140
141            fn to_untyped_message(&self) -> Result<#krate::UntypedMessage, #krate::Error> {
142                #krate::UntypedMessage::new(#method, self)
143            }
144
145            fn parse_message(
146                method: &str,
147                params: &impl serde::Serialize,
148            ) -> Result<Self, #krate::Error> {
149                if method != #method {
150                    return Err(#krate::Error::method_not_found());
151                }
152                #krate::util::json_cast_params(params)
153            }
154        }
155
156        impl #krate::JsonRpcNotification for #name {}
157    };
158
159    TokenStream::from(expanded)
160}
161
162/// Derive macro for implementing `JsonRpcResponse` trait.
163///
164/// # Attributes
165///
166/// - `#[response(crate = crate)]` - for use within the `agent_client_protocol` crate
167///
168/// # Example
169///
170/// ```ignore
171/// #[derive(Debug, Serialize, Deserialize, JsonRpcResponse)]
172/// struct HelloResponse {
173///     greeting: String,
174/// }
175/// ```
176#[proc_macro_derive(JsonRpcResponse, attributes(response))]
177pub fn derive_json_rpc_response_payload(input: TokenStream) -> TokenStream {
178    let input = parse_macro_input!(input as DeriveInput);
179    let name = &input.ident;
180
181    let krate = match parse_response_attrs(&input) {
182        Ok(attrs) => attrs,
183        Err(e) => return e.to_compile_error().into(),
184    };
185
186    let expanded = quote! {
187        impl #krate::JsonRpcResponse for #name {
188            fn into_json(self, _method: &str) -> Result<serde_json::Value, #krate::Error> {
189                serde_json::to_value(self).map_err(#krate::Error::into_internal_error)
190            }
191
192            fn from_value(_method: &str, value: serde_json::Value) -> Result<Self, #krate::Error> {
193                #krate::util::json_cast(value)
194            }
195        }
196    };
197
198    TokenStream::from(expanded)
199}
200
201fn default_crate_path() -> Path {
202    syn::parse_quote!(agent_client_protocol)
203}
204
205fn parse_request_attrs(input: &DeriveInput) -> syn::Result<(String, Type, Path)> {
206    let mut method: Option<String> = None;
207    let mut response_type: Option<Type> = None;
208    let mut krate: Option<Path> = None;
209
210    for attr in &input.attrs {
211        if !attr.path().is_ident("request") {
212            continue;
213        }
214
215        attr.parse_nested_meta(|meta| {
216            if meta.path.is_ident("method") {
217                let value: Expr = meta.value()?.parse()?;
218                if let Expr::Lit(expr_lit) = value
219                    && let Lit::Str(lit_str) = expr_lit.lit
220                {
221                    method = Some(lit_str.value());
222                    return Ok(());
223                }
224                return Err(meta.error("expected string literal for method"));
225            }
226
227            if meta.path.is_ident("response") {
228                let value: Expr = meta.value()?.parse()?;
229                if let Expr::Path(expr_path) = value {
230                    response_type = Some(Type::Path(syn::TypePath {
231                        qself: None,
232                        path: expr_path.path,
233                    }));
234                    return Ok(());
235                }
236                return Err(meta.error("expected type for response"));
237            }
238
239            if meta.path.is_ident("crate") {
240                let value: Expr = meta.value()?.parse()?;
241                if let Expr::Path(expr_path) = value {
242                    krate = Some(expr_path.path);
243                    return Ok(());
244                }
245                return Err(meta.error("expected path for crate"));
246            }
247
248            Err(meta.error("unknown attribute"))
249        })?;
250    }
251
252    let method = method.ok_or_else(|| {
253        syn::Error::new_spanned(
254            &input.ident,
255            "missing required attribute: #[request(method = \"...\")]",
256        )
257    })?;
258
259    let response_type = response_type.ok_or_else(|| {
260        syn::Error::new_spanned(
261            &input.ident,
262            "missing required attribute: #[request(response = ...)]",
263        )
264    })?;
265
266    Ok((
267        method,
268        response_type,
269        krate.unwrap_or_else(default_crate_path),
270    ))
271}
272
273fn parse_notification_attrs(input: &DeriveInput) -> syn::Result<(String, Path)> {
274    let mut method: Option<String> = None;
275    let mut krate: Option<Path> = None;
276
277    for attr in &input.attrs {
278        if !attr.path().is_ident("notification") {
279            continue;
280        }
281
282        attr.parse_nested_meta(|meta| {
283            if meta.path.is_ident("method") {
284                let value: Expr = meta.value()?.parse()?;
285                if let Expr::Lit(expr_lit) = value
286                    && let Lit::Str(lit_str) = expr_lit.lit
287                {
288                    method = Some(lit_str.value());
289                    return Ok(());
290                }
291                return Err(meta.error("expected string literal for method"));
292            }
293
294            if meta.path.is_ident("crate") {
295                let value: Expr = meta.value()?.parse()?;
296                if let Expr::Path(expr_path) = value {
297                    krate = Some(expr_path.path);
298                    return Ok(());
299                }
300                return Err(meta.error("expected path for crate"));
301            }
302
303            Err(meta.error("unknown attribute"))
304        })?;
305    }
306
307    let method = method.ok_or_else(|| {
308        syn::Error::new_spanned(
309            &input.ident,
310            "missing required attribute: #[notification(method = \"...\")]",
311        )
312    })?;
313
314    Ok((method, krate.unwrap_or_else(default_crate_path)))
315}
316
317fn parse_response_attrs(input: &DeriveInput) -> syn::Result<Path> {
318    let mut krate: Option<Path> = None;
319
320    for attr in &input.attrs {
321        if !attr.path().is_ident("response") {
322            continue;
323        }
324
325        attr.parse_nested_meta(|meta| {
326            if meta.path.is_ident("crate") {
327                let value: Expr = meta.value()?.parse()?;
328                if let Expr::Path(expr_path) = value {
329                    krate = Some(expr_path.path);
330                    return Ok(());
331                }
332                return Err(meta.error("expected path for crate"));
333            }
334
335            Err(meta.error("unknown attribute"))
336        })?;
337    }
338
339    Ok(krate.unwrap_or_else(default_crate_path))
340}