agent_client_protocol_derive/
lib.rs1use proc_macro::TokenStream;
41use quote::quote;
42use syn::{DeriveInput, Expr, Lit, Path, Type, parse_macro_input};
43
44#[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 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#[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 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#[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}