Skip to main content

server_less_macros/
lib.rs

1//! Proc macros for server-less.
2//!
3//! This crate provides attribute macros that transform impl blocks into protocol handlers,
4//! and derive macros for common patterns.
5
6use proc_macro::TokenStream;
7use proc_macro2::TokenStream as TokenStream2;
8#[cfg(feature = "graphql")]
9use syn::ItemEnum;
10#[cfg(feature = "graphql")]
11use syn::ItemStruct;
12use syn::{DeriveInput, ItemImpl, parse_macro_input};
13
14/// Compute the Levenshtein edit distance between two strings.
15fn levenshtein(a: &str, b: &str) -> usize {
16    let a: Vec<char> = a.chars().collect();
17    let b: Vec<char> = b.chars().collect();
18    let m = a.len();
19    let n = b.len();
20    let mut dp = vec![vec![0usize; n + 1]; m + 1];
21    for i in 0..=m {
22        dp[i][0] = i;
23    }
24    for j in 0..=n {
25        dp[0][j] = j;
26    }
27    for i in 1..=m {
28        for j in 1..=n {
29            dp[i][j] = if a[i - 1] == b[j - 1] {
30                dp[i - 1][j - 1]
31            } else {
32                1 + dp[i - 1][j - 1].min(dp[i - 1][j]).min(dp[i][j - 1])
33            };
34        }
35    }
36    dp[m][n]
37}
38
39/// Return the closest candidate to `input` within edit distance ≤ 2, or `None`.
40pub(crate) fn did_you_mean<'a>(input: &str, candidates: &[&'a str]) -> Option<&'a str> {
41    candidates
42        .iter()
43        .filter_map(|&c| {
44            let d = levenshtein(input, c);
45            if d <= 2 { Some((d, c)) } else { None }
46        })
47        .min_by_key(|&(d, _)| d)
48        .map(|(_, c)| c)
49}
50
51/// When `SERVER_LESS_DEBUG=1` is set at build time, print the generated token
52/// stream to stderr so implementors can inspect macro output without `cargo expand`.
53fn debug_emit(macro_name: &str, type_name: &str, tokens: &TokenStream2) {
54    if std::env::var("SERVER_LESS_DEBUG").as_deref() == Ok("1") {
55        eprintln!("--- server-less: #[{macro_name}] on {type_name} ---");
56        eprintln!("{tokens}");
57        eprintln!("--- end #[{macro_name}] on {type_name} ---");
58    }
59}
60
61fn type_name(ty: &syn::Type) -> String {
62    quote::quote!(#ty).to_string()
63}
64
65/// Strip the first `impl` block from a token stream.
66///
67/// Preset macros call multiple expand functions, each of which emits the
68/// original impl block followed by generated code. To avoid duplicate method
69/// definitions, the preset emits the impl block from the first expand call
70/// and strips it from subsequent calls.
71fn strip_first_impl(tokens: TokenStream2) -> TokenStream2 {
72    let file: syn::File = match syn::parse2(tokens.clone()) {
73        Ok(file) => file,
74        Err(err) => {
75            // Emit the original tokens (so the user's code is preserved) plus
76            // a compile_error! pointing at the parse failure.  This surfaces the
77            // real problem instead of silently dropping generated impls.
78            let msg = format!("server-less: preset macro failed to parse generated tokens: {err}");
79            return quote::quote! {
80                #tokens
81                ::core::compile_error!(#msg);
82            };
83        }
84    };
85
86    let mut found_first = false;
87    let remaining: Vec<_> = file
88        .items
89        .into_iter()
90        .filter(|item| {
91            if !found_first && matches!(item, syn::Item::Impl(_)) {
92                found_first = true;
93                return false;
94            }
95            true
96        })
97        .collect();
98
99    quote::quote! { #(#remaining)* }
100}
101
102/// Priority-ordered list of protocol macro attribute names.
103///
104/// When multiple protocol macros are stacked on the same impl block, Rust expands
105/// each independently (outputs are concatenated, not pipelined).  To avoid emitting
106/// the impl block multiple times, exactly ONE macro — the one with the highest
107/// priority that's present — takes responsibility for emitting it.
108const PROTOCOL_PRIORITY: &[&str] = &[
109    "cli", "http", "mcp", "jsonrpc", "ws", "graphql", "openapi", "openrpc",
110];
111
112/// Returns `true` if this protocol macro should emit the original impl block.
113///
114/// A macro emits the impl when no higher-priority protocol sibling is present on
115/// the same impl block.  This ensures exactly one copy is emitted when macros are
116/// stacked, preventing duplicate method definitions.
117pub(crate) fn is_protocol_impl_emitter(impl_block: &ItemImpl, current: &str) -> bool {
118    let current_pos = PROTOCOL_PRIORITY
119        .iter()
120        .position(|&p| p == current)
121        .unwrap_or(usize::MAX);
122    // Emit if no sibling with LOWER index (higher priority) is present.
123    !impl_block.attrs.iter().any(|attr| {
124        PROTOCOL_PRIORITY[..current_pos]
125            .iter()
126            .any(|name| attr.path().is_ident(name))
127    })
128}
129
130
131#[cfg(feature = "asyncapi")]
132mod asyncapi;
133#[cfg(feature = "capnp")]
134mod capnp;
135#[cfg(feature = "cli")]
136mod cli;
137#[cfg(feature = "connect")]
138mod connect;
139mod context;
140mod error;
141#[cfg(feature = "graphql")]
142mod graphql;
143#[cfg(feature = "graphql")]
144mod graphql_enum;
145#[cfg(feature = "graphql")]
146mod graphql_input;
147#[cfg(feature = "grpc")]
148mod grpc;
149#[cfg(feature = "http")]
150mod http;
151#[cfg(feature = "jsonrpc")]
152mod jsonrpc;
153#[cfg(feature = "jsonschema")]
154mod jsonschema;
155#[cfg(feature = "markdown")]
156mod markdown;
157#[cfg(feature = "mcp")]
158mod mcp;
159#[cfg(any(feature = "http", feature = "openapi"))]
160mod openapi;
161#[cfg(any(feature = "http", feature = "openapi"))]
162mod openapi_gen;
163#[cfg(feature = "openrpc")]
164mod openrpc;
165#[cfg(feature = "smithy")]
166mod smithy;
167#[cfg(feature = "thrift")]
168mod thrift;
169#[cfg(feature = "ws")]
170mod ws;
171
172mod server_attrs;
173
174// Blessed preset modules
175#[cfg(feature = "cli")]
176mod program;
177#[cfg(feature = "jsonrpc")]
178mod rpc_preset;
179#[cfg(feature = "http")]
180mod server;
181#[cfg(feature = "mcp")]
182mod tool;
183
184/// Generate HTTP handlers from an impl block.
185///
186/// # Basic Usage
187///
188/// ```ignore
189/// use server_less::http;
190///
191/// #[http]
192/// impl UserService {
193///     async fn create_user(&self, name: String) -> User { /* ... */ }
194/// }
195/// ```
196///
197/// # With URL Prefix
198///
199/// ```ignore
200/// #[http(prefix = "/api/v1")]
201/// impl UserService {
202///     // POST /api/v1/users
203///     async fn create_user(&self, name: String) -> User { /* ... */ }
204/// }
205/// ```
206///
207/// # Per-Method Route Overrides
208///
209/// ```ignore
210/// #[http]
211/// impl UserService {
212///     // Override HTTP method: GET /data becomes POST /data
213///     #[route(method = "POST")]
214///     async fn get_data(&self, payload: String) -> String { /* ... */ }
215///
216///     // Override path: POST /users becomes POST /custom-endpoint
217///     #[route(path = "/custom-endpoint")]
218///     async fn create_user(&self, name: String) -> User { /* ... */ }
219///
220///     // Override both
221///     #[route(method = "PUT", path = "/special/{id}")]
222///     async fn do_something(&self, id: String) -> String { /* ... */ }
223///
224///     // Skip route generation (internal methods)
225///     #[route(skip)]
226///     fn internal_helper(&self) -> String { /* ... */ }
227///
228///     // Hide from OpenAPI but still generate route
229///     #[route(hidden)]
230///     fn secret_endpoint(&self) -> String { /* ... */ }
231/// }
232/// ```
233///
234/// # Parameter Handling
235///
236/// ```ignore
237/// #[http]
238/// impl BlogService {
239///     // Path parameters (id, post_id, etc. go in URL)
240///     async fn get_post(&self, post_id: u32) -> Post { /* ... */ }
241///     // GET /posts/{post_id}
242///
243///     // Query parameters (GET methods use query string)
244///     async fn search_posts(&self, query: String, tag: Option<String>) -> Vec<Post> {
245///         /* ... */
246///     }
247///     // GET /posts?query=rust&tag=tutorial
248///
249///     // Body parameters (POST/PUT/PATCH use JSON body)
250///     async fn create_post(&self, title: String, content: String) -> Post {
251///         /* ... */
252///     }
253///     // POST /posts with body: {"title": "...", "content": "..."}
254/// }
255/// ```
256///
257/// # Error Handling
258///
259/// ```ignore
260/// #[http]
261/// impl UserService {
262///     // Return Result for error handling
263///     async fn get_user(&self, id: u32) -> Result<User, MyError> {
264///         if id == 0 {
265///             return Err(MyError::InvalidId);
266///         }
267///         Ok(User { id, name: "Alice".into() })
268///     }
269///
270///     // Return Option - None becomes 404
271///     async fn find_user(&self, email: String) -> Option<User> {
272///         // Returns 200 with user or 404 if None
273///         None
274///     }
275/// }
276/// ```
277///
278/// # Server-Sent Events (SSE) Streaming
279///
280/// Return `impl Stream<Item = T>` to enable Server-Sent Events streaming.
281///
282/// **Important for Rust 2024:** You must add `+ use<>` to impl Trait return types
283/// to explicitly capture all generic parameters in scope. This is required by the
284/// Rust 2024 edition's stricter lifetime capture rules.
285///
286/// ```ignore
287/// use futures::stream::{self, Stream};
288///
289/// #[http]
290/// impl DataService {
291///     // Simple stream - emits values immediately
292///     // Note the `+ use<>` syntax for Rust 2024
293///     fn stream_numbers(&self, count: u32) -> impl Stream<Item = u32> + use<> {
294///         stream::iter(0..count)
295///     }
296///
297///     // Async stream with delays
298///     async fn stream_events(&self, n: u32) -> impl Stream<Item = Event> + use<> {
299///         stream::unfold(0, move |count| async move {
300///             if count >= n {
301///                 return None;
302///             }
303///             tokio::time::sleep(Duration::from_secs(1)).await;
304///             Some((Event { id: count }, count + 1))
305///         })
306///     }
307/// }
308/// ```
309///
310/// Clients receive data as SSE:
311/// ```text
312/// data: {"id": 0}
313///
314/// data: {"id": 1}
315///
316/// data: {"id": 2}
317/// ```
318///
319/// **Why `+ use<>`?**
320/// - Rust 2024 requires explicit capture of generic parameters in return position impl Trait
321/// - `+ use<>` captures all type parameters and lifetimes from the function context
322/// - Without it, you'll get compilation errors about uncaptured parameters
323/// - See: examples/streaming_service.rs for a complete working example
324///
325/// # Real-World Example
326///
327/// ```ignore
328/// #[http(prefix = "/api/v1")]
329/// impl UserService {
330///     // GET /api/v1/users?page=0&limit=10
331///     async fn list_users(
332///         &self,
333///         #[param(default = 0)] page: u32,
334///         #[param(default = 20)] limit: u32,
335///     ) -> Vec<User> {
336///         /* ... */
337///     }
338///
339///     // GET /api/v1/users/{user_id}
340///     async fn get_user(&self, user_id: u32) -> Result<User, ApiError> {
341///         /* ... */
342///     }
343///
344///     // POST /api/v1/users with body: {"name": "...", "email": "..."}
345///     #[response(status = 201)]
346///     #[response(header = "Location", value = "/api/v1/users/{id}")]
347///     async fn create_user(&self, name: String, email: String) -> Result<User, ApiError> {
348///         /* ... */
349///     }
350///
351///     // PUT /api/v1/users/{user_id}
352///     async fn update_user(
353///         &self,
354///         user_id: u32,
355///         name: Option<String>,
356///         email: Option<String>,
357///     ) -> Result<User, ApiError> {
358///         /* ... */
359///     }
360///
361///     // DELETE /api/v1/users/{user_id}
362///     #[response(status = 204)]
363///     async fn delete_user(&self, user_id: u32) -> Result<(), ApiError> {
364///         /* ... */
365///     }
366/// }
367/// ```
368///
369/// # Generated Methods
370/// - `http_router() -> axum::Router` - Complete router with all endpoints
371/// - `http_routes() -> Vec<&'static str>` - List of route paths
372/// - `openapi_spec() -> serde_json::Value` - OpenAPI 3.0 specification (unless `openapi = false`)
373///
374/// # OpenAPI Control
375///
376/// By default, `#[http]` generates both HTTP routes and OpenAPI specs. You can disable
377/// OpenAPI generation:
378///
379/// ```ignore
380/// #[http(openapi = false)]  // No openapi_spec() method generated
381/// impl MyService { /* ... */ }
382/// ```
383///
384/// For standalone OpenAPI generation without HTTP routing, see `#[openapi]`.
385#[cfg(feature = "http")]
386#[proc_macro_attribute]
387pub fn http(attr: TokenStream, item: TokenStream) -> TokenStream {
388    let args = parse_macro_input!(attr as http::HttpArgs);
389    let impl_block = parse_macro_input!(item as ItemImpl);
390    let name = type_name(&impl_block.self_ty);
391
392    match http::expand_http(args, impl_block) {
393        Ok(tokens) => {
394            debug_emit("http", &name, &tokens);
395            tokens.into()
396        }
397        Err(err) => err.to_compile_error().into(),
398    }
399}
400
401/// Generate OpenAPI specification without HTTP routing.
402///
403/// Generates OpenAPI 3.0 specs using the same naming conventions as `#[http]`,
404/// but without creating route handlers. Useful for:
405/// - Schema-first development
406/// - Documentation-only use cases
407/// - Separate OpenAPI generation from HTTP routing
408///
409/// # Basic Usage
410///
411/// ```ignore
412/// use server_less::openapi;
413///
414/// #[openapi]
415/// impl UserService {
416///     /// Create a new user
417///     fn create_user(&self, name: String, email: String) -> User { /* ... */ }
418///
419///     /// Get user by ID
420///     fn get_user(&self, id: String) -> Option<User> { /* ... */ }
421/// }
422///
423/// // Generate spec:
424/// let spec = UserService::openapi_spec();
425/// ```
426///
427/// # With URL Prefix
428///
429/// ```ignore
430/// #[openapi(prefix = "/api/v1")]
431/// impl UserService { /* ... */ }
432/// ```
433///
434/// # Generated Methods
435///
436/// - `openapi_spec() -> serde_json::Value` - OpenAPI 3.0 specification
437///
438/// # Combining with #[http]
439///
440/// If you want separate control over OpenAPI generation:
441///
442/// ```ignore
443/// // Option 1: Disable OpenAPI in http, use standalone macro
444/// #[http(openapi = false)]
445/// #[openapi(prefix = "/api")]
446/// impl MyService { /* ... */ }
447///
448/// // Option 2: Just use http with default (openapi = true)
449/// #[http]
450/// impl MyService { /* ... */ }
451/// ```
452#[cfg(any(feature = "http", feature = "openapi"))]
453#[proc_macro_attribute]
454pub fn openapi(attr: TokenStream, item: TokenStream) -> TokenStream {
455    let args = parse_macro_input!(attr as openapi::OpenApiArgs);
456    let impl_block = parse_macro_input!(item as ItemImpl);
457    let name = type_name(&impl_block.self_ty);
458
459    match openapi::expand_openapi(args, impl_block) {
460        Ok(tokens) => {
461            debug_emit("openapi", &name, &tokens);
462            tokens.into()
463        }
464        Err(err) => err.to_compile_error().into(),
465    }
466}
467
468/// Generate a CLI application from an impl block.
469///
470/// # Basic Usage
471///
472/// ```ignore
473/// use server_less::cli;
474///
475/// #[cli]
476/// impl MyApp {
477///     fn create_user(&self, name: String) { /* ... */ }
478/// }
479/// ```
480///
481/// # With All Options
482///
483/// ```ignore
484/// #[cli(
485///     name = "myapp",
486///     version = "1.0.0",
487///     about = "My awesome application"
488/// )]
489/// impl MyApp {
490///     /// Create a new user (becomes: myapp create-user <NAME>)
491///     fn create_user(&self, name: String) { /* ... */ }
492///
493///     /// Optional flags use Option<T>
494///     fn list_users(&self, limit: Option<usize>) { /* ... */ }
495/// }
496/// ```
497///
498/// # Generated Methods
499/// - `cli_app() -> clap::Command` - Complete CLI application
500/// - `cli_run(&self, matches: &ArgMatches)` - Execute matched command
501#[cfg(feature = "cli")]
502#[proc_macro_attribute]
503pub fn cli(attr: TokenStream, item: TokenStream) -> TokenStream {
504    let args = parse_macro_input!(attr as cli::CliArgs);
505    let impl_block = parse_macro_input!(item as ItemImpl);
506    let name = type_name(&impl_block.self_ty);
507
508    match cli::expand_cli(args, impl_block) {
509        Ok(tokens) => {
510            debug_emit("cli", &name, &tokens);
511            tokens.into()
512        }
513        Err(err) => err.to_compile_error().into(),
514    }
515}
516
517/// Generate MCP (Model Context Protocol) tools from an impl block.
518///
519/// # Basic Usage
520///
521/// ```ignore
522/// use server_less::mcp;
523///
524/// #[mcp]
525/// impl FileTools {
526///     fn read_file(&self, path: String) -> String { /* ... */ }
527/// }
528/// ```
529///
530/// # With Namespace
531///
532/// ```ignore
533/// #[mcp(namespace = "file")]
534/// impl FileTools {
535///     // Exposed as "file_read_file" tool
536///     fn read_file(&self, path: String) -> String { /* ... */ }
537/// }
538/// ```
539///
540/// # Streaming Support
541///
542/// Methods returning `impl Stream<Item = T>` are automatically collected into arrays:
543///
544/// ```ignore
545/// use futures::stream::{self, Stream};
546///
547/// #[mcp]
548/// impl DataService {
549///     // Returns JSON array: [0, 1, 2, 3, 4]
550///     fn stream_numbers(&self, count: u32) -> impl Stream<Item = u32> + use<> {
551///         stream::iter(0..count)
552///     }
553/// }
554///
555/// // Call with:
556/// service.mcp_call_async("stream_numbers", json!({"count": 5})).await
557/// // Returns: [0, 1, 2, 3, 4]
558/// ```
559///
560/// **Note:** Streaming methods require `mcp_call_async`, not `mcp_call`.
561///
562/// # Generated Methods
563/// - `mcp_tools() -> Vec<serde_json::Value>` - Tool definitions
564/// - `mcp_call(&self, name, args) -> Result<Value, String>` - Execute tool (sync only)
565/// - `mcp_call_async(&self, name, args).await` - Execute tool (supports async & streams)
566#[cfg(feature = "mcp")]
567#[proc_macro_attribute]
568pub fn mcp(attr: TokenStream, item: TokenStream) -> TokenStream {
569    let args = parse_macro_input!(attr as mcp::McpArgs);
570    let impl_block = parse_macro_input!(item as ItemImpl);
571    let name = type_name(&impl_block.self_ty);
572
573    match mcp::expand_mcp(args, impl_block) {
574        Ok(tokens) => {
575            debug_emit("mcp", &name, &tokens);
576            tokens.into()
577        }
578        Err(err) => err.to_compile_error().into(),
579    }
580}
581
582/// Generate WebSocket JSON-RPC handlers from an impl block.
583///
584/// Methods are exposed as JSON-RPC methods over WebSocket connections.
585/// Supports both sync and async methods.
586///
587/// # Basic Usage
588///
589/// ```ignore
590/// use server_less::ws;
591///
592/// #[ws(path = "/ws")]
593/// impl ChatService {
594///     fn send_message(&self, room: String, content: String) -> Message {
595///         // ...
596///     }
597/// }
598/// ```
599///
600/// # With Async Methods
601///
602/// ```ignore
603/// #[ws(path = "/ws")]
604/// impl ChatService {
605///     // Async methods work seamlessly
606///     async fn send_message(&self, room: String, content: String) -> Message {
607///         // Can await database, network calls, etc.
608///     }
609///
610///     // Mix sync and async
611///     fn get_rooms(&self) -> Vec<String> {
612///         // Synchronous method
613///     }
614/// }
615/// ```
616///
617/// # Error Handling
618///
619/// ```ignore
620/// #[ws(path = "/ws")]
621/// impl ChatService {
622///     fn send_message(&self, room: String, content: String) -> Result<Message, ChatError> {
623///         if room.is_empty() {
624///             return Err(ChatError::InvalidRoom);
625///         }
626///         Ok(Message::new(room, content))
627///     }
628/// }
629/// ```
630///
631/// # Client Usage
632///
633/// Clients send JSON-RPC 2.0 messages over WebSocket:
634///
635/// ```json
636/// // Request
637/// {
638///   "jsonrpc": "2.0",
639///   "method": "send_message",
640///   "params": {"room": "general", "content": "Hello!"},
641///   "id": 1
642/// }
643///
644/// // Response
645/// {
646///   "jsonrpc": "2.0",
647///   "result": {"id": 123, "room": "general", "content": "Hello!"},
648///   "id": 1
649/// }
650/// ```
651///
652/// # Generated Methods
653/// - `ws_router() -> axum::Router` - Router with WebSocket endpoint
654/// - `ws_handle_message(msg) -> String` - Sync message handler
655/// - `ws_handle_message_async(msg) -> String` - Async message handler
656/// - `ws_methods() -> Vec<&'static str>` - List of available methods
657#[cfg(feature = "ws")]
658#[proc_macro_attribute]
659pub fn ws(attr: TokenStream, item: TokenStream) -> TokenStream {
660    let args = parse_macro_input!(attr as ws::WsArgs);
661    let impl_block = parse_macro_input!(item as ItemImpl);
662    let name = type_name(&impl_block.self_ty);
663
664    match ws::expand_ws(args, impl_block) {
665        Ok(tokens) => {
666            debug_emit("ws", &name, &tokens);
667            tokens.into()
668        }
669        Err(err) => err.to_compile_error().into(),
670    }
671}
672
673/// Generate JSON-RPC 2.0 handlers over HTTP.
674///
675/// # Example
676///
677/// ```ignore
678/// use server_less::jsonrpc;
679///
680/// struct Calculator;
681///
682/// #[jsonrpc]
683/// impl Calculator {
684///     /// Add two numbers
685///     fn add(&self, a: i32, b: i32) -> i32 {
686///         a + b
687///     }
688///
689///     /// Multiply two numbers
690///     fn multiply(&self, a: i32, b: i32) -> i32 {
691///         a * b
692///     }
693/// }
694///
695/// // POST /rpc with {"jsonrpc": "2.0", "method": "add", "params": {"a": 1, "b": 2}, "id": 1}
696/// // Returns: {"jsonrpc": "2.0", "result": 3, "id": 1}
697/// ```
698///
699/// This generates:
700/// - `Calculator::jsonrpc_router()` returning an axum Router
701/// - `Calculator::jsonrpc_handle(request)` to handle JSON-RPC requests
702/// - `Calculator::jsonrpc_methods()` listing available methods
703///
704/// Supports JSON-RPC 2.0 features:
705/// - Named and positional parameters
706/// - Batch requests (array of requests)
707/// - Notifications (requests without id)
708#[cfg(feature = "jsonrpc")]
709#[proc_macro_attribute]
710pub fn jsonrpc(attr: TokenStream, item: TokenStream) -> TokenStream {
711    let args = parse_macro_input!(attr as jsonrpc::JsonRpcArgs);
712    let impl_block = parse_macro_input!(item as ItemImpl);
713    let name = type_name(&impl_block.self_ty);
714
715    match jsonrpc::expand_jsonrpc(args, impl_block) {
716        Ok(tokens) => {
717            debug_emit("jsonrpc", &name, &tokens);
718            tokens.into()
719        }
720        Err(err) => err.to_compile_error().into(),
721    }
722}
723
724/// Generate OpenRPC specification for JSON-RPC services.
725///
726/// OpenRPC is to JSON-RPC what OpenAPI is to REST APIs.
727///
728/// # Example
729///
730/// ```ignore
731/// use server_less::openrpc;
732///
733/// struct Calculator;
734///
735/// #[openrpc(title = "Calculator API", version = "1.0.0")]
736/// impl Calculator {
737///     /// Add two numbers
738///     fn add(&self, a: i32, b: i32) -> i32 { a + b }
739/// }
740///
741/// // Get OpenRPC spec as JSON
742/// let spec = Calculator::openrpc_spec();
743/// let json = Calculator::openrpc_json();
744///
745/// // Write to file
746/// Calculator::write_openrpc("openrpc.json")?;
747/// ```
748#[cfg(feature = "openrpc")]
749#[proc_macro_attribute]
750pub fn openrpc(attr: TokenStream, item: TokenStream) -> TokenStream {
751    let args = parse_macro_input!(attr as openrpc::OpenRpcArgs);
752    let impl_block = parse_macro_input!(item as ItemImpl);
753    let name = type_name(&impl_block.self_ty);
754
755    match openrpc::expand_openrpc(args, impl_block) {
756        Ok(tokens) => {
757            debug_emit("openrpc", &name, &tokens);
758            tokens.into()
759        }
760        Err(err) => err.to_compile_error().into(),
761    }
762}
763
764/// Generate Markdown API documentation from an impl block.
765///
766/// Creates human-readable documentation that can be used with
767/// any static site generator (VitePress, Docusaurus, MkDocs, etc.).
768///
769/// # Example
770///
771/// ```ignore
772/// use server_less::markdown;
773///
774/// struct UserService;
775///
776/// #[markdown(title = "User API")]
777/// impl UserService {
778///     /// Create a new user
779///     fn create_user(&self, name: String, email: String) -> User { ... }
780///
781///     /// Get user by ID
782///     fn get_user(&self, id: String) -> Option<User> { ... }
783/// }
784///
785/// // Get markdown string
786/// let docs = UserService::markdown_docs();
787///
788/// // Write to file
789/// UserService::write_markdown("docs/api.md")?;
790/// ```
791#[cfg(feature = "markdown")]
792#[proc_macro_attribute]
793pub fn markdown(attr: TokenStream, item: TokenStream) -> TokenStream {
794    let args = parse_macro_input!(attr as markdown::MarkdownArgs);
795    let impl_block = parse_macro_input!(item as ItemImpl);
796    let name = type_name(&impl_block.self_ty);
797
798    match markdown::expand_markdown(args, impl_block) {
799        Ok(tokens) => {
800            debug_emit("markdown", &name, &tokens);
801            tokens.into()
802        }
803        Err(err) => err.to_compile_error().into(),
804    }
805}
806
807/// Generate AsyncAPI specification for event-driven services.
808///
809/// AsyncAPI is to WebSockets/messaging what OpenAPI is to REST.
810///
811/// # Example
812///
813/// ```ignore
814/// use server_less::asyncapi;
815///
816/// struct ChatService;
817///
818/// #[asyncapi(title = "Chat API", server = "ws://localhost:8080")]
819/// impl ChatService {
820///     /// Send a message to a room
821///     fn send_message(&self, room: String, content: String) -> bool { true }
822///
823///     /// Get message history
824///     fn get_history(&self, room: String, limit: Option<u32>) -> Vec<String> { vec![] }
825/// }
826///
827/// // Get AsyncAPI spec
828/// let spec = ChatService::asyncapi_spec();
829/// let json = ChatService::asyncapi_json();
830///
831/// // Write to file
832/// ChatService::write_asyncapi("asyncapi.json")?;
833/// ```
834#[cfg(feature = "asyncapi")]
835#[proc_macro_attribute]
836pub fn asyncapi(attr: TokenStream, item: TokenStream) -> TokenStream {
837    let args = parse_macro_input!(attr as asyncapi::AsyncApiArgs);
838    let impl_block = parse_macro_input!(item as ItemImpl);
839    let name = type_name(&impl_block.self_ty);
840
841    match asyncapi::expand_asyncapi(args, impl_block) {
842        Ok(tokens) => {
843            debug_emit("asyncapi", &name, &tokens);
844            tokens.into()
845        }
846        Err(err) => err.to_compile_error().into(),
847    }
848}
849
850/// Generate Connect protocol schema from an impl block.
851///
852/// Connect is a modern RPC protocol from Buf that works over HTTP/1.1, HTTP/2, and HTTP/3.
853/// The generated schema is compatible with connect-go, connect-es, connect-swift, etc.
854///
855/// # Example
856///
857/// ```ignore
858/// use server_less::connect;
859///
860/// struct UserService;
861///
862/// #[connect(package = "users.v1")]
863/// impl UserService {
864///     fn get_user(&self, id: String) -> User { ... }
865/// }
866///
867/// // Get schema and endpoint paths
868/// let schema = UserService::connect_schema();
869/// let paths = UserService::connect_paths(); // ["/users.v1.UserService/GetUser", ...]
870/// ```
871#[cfg(feature = "connect")]
872#[proc_macro_attribute]
873pub fn connect(attr: TokenStream, item: TokenStream) -> TokenStream {
874    let args = parse_macro_input!(attr as connect::ConnectArgs);
875    let impl_block = parse_macro_input!(item as ItemImpl);
876    let name = type_name(&impl_block.self_ty);
877
878    match connect::expand_connect(args, impl_block) {
879        Ok(tokens) => {
880            debug_emit("connect", &name, &tokens);
881            tokens.into()
882        }
883        Err(err) => err.to_compile_error().into(),
884    }
885}
886
887/// Generate Protocol Buffers schema from an impl block.
888///
889/// # Example
890///
891/// ```ignore
892/// use server_less::grpc;
893///
894/// struct UserService;
895///
896/// #[grpc(package = "users")]
897/// impl UserService {
898///     /// Get user by ID
899///     fn get_user(&self, id: String) -> User { ... }
900///
901///     /// Create a new user
902///     fn create_user(&self, name: String, email: String) -> User { ... }
903/// }
904///
905/// // Get the proto schema
906/// let proto = UserService::proto_schema();
907///
908/// // Write to file for use with tonic-build
909/// UserService::write_proto("proto/users.proto")?;
910/// ```
911///
912/// The generated schema can be used with tonic-build in your build.rs
913/// to generate the full gRPC client/server implementation.
914#[cfg(feature = "grpc")]
915#[proc_macro_attribute]
916pub fn grpc(attr: TokenStream, item: TokenStream) -> TokenStream {
917    let args = parse_macro_input!(attr as grpc::GrpcArgs);
918    let impl_block = parse_macro_input!(item as ItemImpl);
919    let name = type_name(&impl_block.self_ty);
920
921    match grpc::expand_grpc(args, impl_block) {
922        Ok(tokens) => {
923            debug_emit("grpc", &name, &tokens);
924            tokens.into()
925        }
926        Err(err) => err.to_compile_error().into(),
927    }
928}
929
930/// Generate Cap'n Proto schema from an impl block.
931///
932/// # Example
933///
934/// ```ignore
935/// use server_less::capnp;
936///
937/// struct UserService;
938///
939/// #[capnp(id = "0x85150b117366d14b")]
940/// impl UserService {
941///     /// Get user by ID
942///     fn get_user(&self, id: String) -> String { ... }
943///
944///     /// Create a new user
945///     fn create_user(&self, name: String, email: String) -> String { ... }
946/// }
947///
948/// // Get the Cap'n Proto schema
949/// let schema = UserService::capnp_schema();
950///
951/// // Write to file for use with capnpc
952/// UserService::write_capnp("schema/users.capnp")?;
953/// ```
954///
955/// The generated schema can be used with capnpc to generate
956/// the full Cap'n Proto serialization code.
957#[cfg(feature = "capnp")]
958#[proc_macro_attribute]
959pub fn capnp(attr: TokenStream, item: TokenStream) -> TokenStream {
960    let args = parse_macro_input!(attr as capnp::CapnpArgs);
961    let impl_block = parse_macro_input!(item as ItemImpl);
962    let name = type_name(&impl_block.self_ty);
963
964    match capnp::expand_capnp(args, impl_block) {
965        Ok(tokens) => {
966            debug_emit("capnp", &name, &tokens);
967            tokens.into()
968        }
969        Err(err) => err.to_compile_error().into(),
970    }
971}
972
973/// Generate Apache Thrift schema from an impl block.
974///
975/// # Example
976///
977/// ```ignore
978/// use server_less::thrift;
979///
980/// struct UserService;
981///
982/// #[thrift(namespace = "users")]
983/// impl UserService {
984///     /// Get user by ID
985///     fn get_user(&self, id: String) -> String { ... }
986///
987///     /// Create a new user
988///     fn create_user(&self, name: String, email: String) -> String { ... }
989/// }
990///
991/// // Get the Thrift schema
992/// let schema = UserService::thrift_schema();
993///
994/// // Write to file for use with thrift compiler
995/// UserService::write_thrift("idl/users.thrift")?;
996/// ```
997///
998/// The generated schema can be used with the Thrift compiler to generate
999/// client/server code in various languages.
1000#[cfg(feature = "thrift")]
1001#[proc_macro_attribute]
1002pub fn thrift(attr: TokenStream, item: TokenStream) -> TokenStream {
1003    let args = parse_macro_input!(attr as thrift::ThriftArgs);
1004    let impl_block = parse_macro_input!(item as ItemImpl);
1005    let name = type_name(&impl_block.self_ty);
1006
1007    match thrift::expand_thrift(args, impl_block) {
1008        Ok(tokens) => {
1009            debug_emit("thrift", &name, &tokens);
1010            tokens.into()
1011        }
1012        Err(err) => err.to_compile_error().into(),
1013    }
1014}
1015
1016/// Generate Smithy IDL schema from an impl block.
1017///
1018/// Smithy is AWS's open-source interface definition language for defining APIs.
1019/// The generated schema follows Smithy 2.0 specification.
1020///
1021/// # Example
1022///
1023/// ```ignore
1024/// use server_less::smithy;
1025///
1026/// struct UserService;
1027///
1028/// #[smithy(namespace = "com.example.users")]
1029/// impl UserService {
1030///     /// Get user by ID
1031///     fn get_user(&self, id: String) -> User { ... }
1032///
1033///     /// Create a new user
1034///     fn create_user(&self, name: String, email: String) -> User { ... }
1035/// }
1036///
1037/// // Get Smithy schema
1038/// let schema = UserService::smithy_schema();
1039/// // Write to file
1040/// UserService::write_smithy("service.smithy")?;
1041/// ```
1042///
1043/// The generated schema can be used with the Smithy toolchain for code generation.
1044#[cfg(feature = "smithy")]
1045#[proc_macro_attribute]
1046pub fn smithy(attr: TokenStream, item: TokenStream) -> TokenStream {
1047    let args = parse_macro_input!(attr as smithy::SmithyArgs);
1048    let impl_block = parse_macro_input!(item as ItemImpl);
1049    let name = type_name(&impl_block.self_ty);
1050
1051    match smithy::expand_smithy(args, impl_block) {
1052        Ok(tokens) => {
1053            debug_emit("smithy", &name, &tokens);
1054            tokens.into()
1055        }
1056        Err(err) => err.to_compile_error().into(),
1057    }
1058}
1059
1060/// Generate JSON Schema from an impl block.
1061///
1062/// Generates JSON Schema definitions for request/response types.
1063/// Useful for API validation, documentation, and tooling.
1064///
1065/// # Example
1066///
1067/// ```ignore
1068/// use server_less::jsonschema;
1069///
1070/// struct UserService;
1071///
1072/// #[jsonschema(title = "User API")]
1073/// impl UserService {
1074///     /// Get user by ID
1075///     fn get_user(&self, id: String) -> User { ... }
1076///
1077///     /// Create a new user
1078///     fn create_user(&self, name: String, email: String) -> User { ... }
1079/// }
1080///
1081/// // Get JSON Schema
1082/// let schema = UserService::json_schema();
1083/// // Write to file
1084/// UserService::write_json_schema("schema.json")?;
1085/// ```
1086#[cfg(feature = "jsonschema")]
1087#[proc_macro_attribute]
1088pub fn jsonschema(attr: TokenStream, item: TokenStream) -> TokenStream {
1089    let args = parse_macro_input!(attr as jsonschema::JsonSchemaArgs);
1090    let impl_block = parse_macro_input!(item as ItemImpl);
1091    let name = type_name(&impl_block.self_ty);
1092
1093    match jsonschema::expand_jsonschema(args, impl_block) {
1094        Ok(tokens) => {
1095            debug_emit("jsonschema", &name, &tokens);
1096            tokens.into()
1097        }
1098        Err(err) => err.to_compile_error().into(),
1099    }
1100}
1101
1102/// Generate GraphQL schema from an impl block using async-graphql.
1103///
1104/// Methods are automatically classified as Queries or Mutations based on naming:
1105/// - Queries: `get_*`, `list_*`, `find_*`, `search_*`, `fetch_*`, `query_*`
1106/// - Mutations: everything else (create, update, delete, etc.)
1107///
1108/// # Basic Usage
1109///
1110/// ```ignore
1111/// use server_less::graphql;
1112///
1113/// #[graphql]
1114/// impl UserService {
1115///     // Query: returns single user
1116///     async fn get_user(&self, id: String) -> Option<User> {
1117///         // ...
1118///     }
1119///
1120///     // Query: returns list of users
1121///     async fn list_users(&self) -> Vec<User> {
1122///         // ...
1123///     }
1124///
1125///     // Mutation: creates new user
1126///     async fn create_user(&self, name: String, email: String) -> User {
1127///         // ...
1128///     }
1129/// }
1130/// ```
1131///
1132/// # Type Mappings
1133///
1134/// - `String`, `i32`, `bool`, etc. → GraphQL scalars
1135/// - `Option<T>` → nullable GraphQL field
1136/// - `Vec<T>` → GraphQL list `[T]`
1137/// - Custom structs → GraphQL objects (must derive SimpleObject)
1138///
1139/// ```ignore
1140/// use async_graphql::SimpleObject;
1141///
1142/// #[derive(SimpleObject)]
1143/// struct User {
1144///     id: String,
1145///     name: String,
1146///     email: Option<String>,  // Nullable field
1147/// }
1148///
1149/// #[graphql]
1150/// impl UserService {
1151///     async fn get_user(&self, id: String) -> Option<User> {
1152///         // Returns User object with proper GraphQL schema
1153///     }
1154///
1155///     async fn list_users(&self) -> Vec<User> {
1156///         // Returns [User] in GraphQL
1157///     }
1158/// }
1159/// ```
1160///
1161/// # GraphQL Queries
1162///
1163/// ```graphql
1164/// # Query single user
1165/// query {
1166///   getUser(id: "123") {
1167///     id
1168///     name
1169///     email
1170///   }
1171/// }
1172///
1173/// # List all users
1174/// query {
1175///   listUsers {
1176///     id
1177///     name
1178///   }
1179/// }
1180///
1181/// # Mutation
1182/// mutation {
1183///   createUser(name: "Alice", email: "alice@example.com") {
1184///     id
1185///     name
1186///   }
1187/// }
1188/// ```
1189///
1190/// # Custom Scalars
1191///
1192/// Common custom scalar types are automatically supported:
1193///
1194/// ```ignore
1195/// use chrono::{DateTime, Utc};
1196/// use uuid::Uuid;
1197///
1198/// #[graphql]
1199/// impl EventService {
1200///     // UUID parameter
1201///     async fn get_event(&self, event_id: Uuid) -> Option<Event> { /* ... */ }
1202///
1203///     // DateTime parameter
1204///     async fn list_events(&self, since: DateTime<Utc>) -> Vec<Event> { /* ... */ }
1205///
1206///     // JSON parameter
1207///     async fn search_events(&self, filter: serde_json::Value) -> Vec<Event> { /* ... */ }
1208/// }
1209/// ```
1210///
1211/// Supported custom scalars:
1212/// - `chrono::DateTime<Utc>` → DateTime
1213/// - `uuid::Uuid` → UUID
1214/// - `url::Url` → Url
1215/// - `serde_json::Value` → JSON
1216///
1217/// # Generated Methods
1218/// - `graphql_schema() -> Schema` - async-graphql Schema
1219/// - `graphql_router() -> axum::Router` - Router with /graphql endpoint
1220/// - `graphql_sdl() -> String` - Schema Definition Language string
1221#[cfg(feature = "graphql")]
1222#[proc_macro_attribute]
1223pub fn graphql(attr: TokenStream, item: TokenStream) -> TokenStream {
1224    let args = parse_macro_input!(attr as graphql::GraphqlArgs);
1225    let impl_block = parse_macro_input!(item as ItemImpl);
1226    let name = type_name(&impl_block.self_ty);
1227
1228    match graphql::expand_graphql(args, impl_block) {
1229        Ok(tokens) => {
1230            debug_emit("graphql", &name, &tokens);
1231            tokens.into()
1232        }
1233        Err(err) => err.to_compile_error().into(),
1234    }
1235}
1236
1237/// Define a GraphQL enum type.
1238///
1239/// Generates a GraphQL Enum type definition from a Rust enum.
1240/// Only unit variants (no fields) are supported.
1241///
1242/// # Example
1243///
1244/// ```ignore
1245/// use server_less::graphql_enum;
1246///
1247/// #[graphql_enum]
1248/// #[derive(Clone, Debug)]
1249/// enum Status {
1250///     /// User is active
1251///     Active,
1252///     /// User is inactive
1253///     Inactive,
1254///     /// Awaiting approval
1255///     Pending,
1256/// }
1257///
1258/// // Then register with #[graphql]:
1259/// #[graphql(enums(Status))]
1260/// impl MyService {
1261///     pub fn get_status(&self) -> Status { Status::Active }
1262/// }
1263/// ```
1264///
1265/// # Generated Methods
1266///
1267/// - `__graphql_enum_type() -> async_graphql::dynamic::Enum` - Enum type definition
1268/// - `__to_graphql_value(&self) -> async_graphql::Value` - Convert to GraphQL value
1269///
1270/// # Variant Naming
1271///
1272/// Variant names are converted to SCREAMING_SNAKE_CASE for GraphQL:
1273/// - `Active` → `ACTIVE`
1274/// - `InProgress` → `IN_PROGRESS`
1275#[cfg(feature = "graphql")]
1276#[proc_macro_attribute]
1277pub fn graphql_enum(_attr: TokenStream, item: TokenStream) -> TokenStream {
1278    let item_enum = parse_macro_input!(item as ItemEnum);
1279    let name = item_enum.ident.to_string();
1280
1281    match graphql_enum::expand_graphql_enum(item_enum) {
1282        Ok(tokens) => {
1283            debug_emit("graphql_enum", &name, &tokens);
1284            tokens.into()
1285        }
1286        Err(err) => err.to_compile_error().into(),
1287    }
1288}
1289
1290/// Define a GraphQL input type.
1291///
1292/// Generates a GraphQL InputObject type definition from a Rust struct.
1293/// The struct must implement `serde::Deserialize` for input parsing.
1294///
1295/// # Example
1296///
1297/// ```ignore
1298/// use server_less::graphql_input;
1299/// use serde::Deserialize;
1300///
1301/// #[graphql_input]
1302/// #[derive(Clone, Debug, Deserialize)]
1303/// struct CreateUserInput {
1304///     /// User's name
1305///     name: String,
1306///     /// User's email address
1307///     email: String,
1308///     /// Optional age
1309///     age: Option<i32>,
1310/// }
1311///
1312/// // Then register with #[graphql]:
1313/// #[graphql(inputs(CreateUserInput))]
1314/// impl UserService {
1315///     pub fn create_user(&self, input: CreateUserInput) -> User { /* ... */ }
1316/// }
1317/// ```
1318///
1319/// # Generated Methods
1320///
1321/// - `__graphql_input_type() -> async_graphql::dynamic::InputObject` - Input type definition
1322/// - `__from_graphql_value(value) -> Result<Self, String>` - Parse from GraphQL value
1323///
1324/// # Field Naming
1325///
1326/// Field names are converted to camelCase for GraphQL:
1327/// - `user_name` → `userName`
1328/// - `email_address` → `emailAddress`
1329#[cfg(feature = "graphql")]
1330#[proc_macro_attribute]
1331pub fn graphql_input(_attr: TokenStream, item: TokenStream) -> TokenStream {
1332    let item_struct = parse_macro_input!(item as ItemStruct);
1333    let name = item_struct.ident.to_string();
1334
1335    match graphql_input::expand_graphql_input(item_struct) {
1336        Ok(tokens) => {
1337            debug_emit("graphql_input", &name, &tokens);
1338            tokens.into()
1339        }
1340        Err(err) => err.to_compile_error().into(),
1341    }
1342}
1343
1344/// Coordinate multiple protocol handlers into a single server.
1345///
1346/// # Example
1347///
1348/// ```ignore
1349/// use server_less::{http, ws, jsonrpc, serve};
1350///
1351/// struct MyService;
1352///
1353/// #[http]
1354/// #[ws]
1355/// #[jsonrpc]
1356/// #[serve(http, ws, jsonrpc)]
1357/// impl MyService {
1358///     fn list_items(&self) -> Vec<String> { vec![] }
1359/// }
1360///
1361/// // Now you can:
1362/// // - service.serve("0.0.0.0:3000").await  // start server
1363/// // - service.router()                     // get combined router
1364/// ```
1365///
1366/// # Arguments
1367///
1368/// - `http` - Include the HTTP router (REST API)
1369/// - `ws` - Include the WebSocket router (WS JSON-RPC)
1370/// - `jsonrpc` - Include the JSON-RPC HTTP router
1371/// - `graphql` - Include the GraphQL router
1372/// - `health = "/path"` - Custom health check path (default: `/health`)
1373#[cfg(feature = "http")]
1374#[proc_macro_attribute]
1375pub fn serve(attr: TokenStream, item: TokenStream) -> TokenStream {
1376    let args = parse_macro_input!(attr as http::ServeArgs);
1377    let impl_block = parse_macro_input!(item as ItemImpl);
1378    let name = type_name(&impl_block.self_ty);
1379
1380    match http::expand_serve(args, impl_block) {
1381        Ok(tokens) => {
1382            debug_emit("serve", &name, &tokens);
1383            tokens.into()
1384        }
1385        Err(err) => err.to_compile_error().into(),
1386    }
1387}
1388
1389/// Helper attribute for method-level HTTP route customization.
1390///
1391/// This attribute is used within `#[http]` impl blocks to customize
1392/// individual method routing. It is a no-op on its own.
1393///
1394/// # Example
1395///
1396/// ```ignore
1397/// #[http(prefix = "/api")]
1398/// impl MyService {
1399///     #[route(method = "POST", path = "/custom")]
1400///     fn my_method(&self) { }
1401///
1402///     #[route(skip)]
1403///     fn internal_method(&self) { }
1404///
1405///     #[route(hidden)]  // Hidden from OpenAPI but still routed
1406///     fn secret(&self) { }
1407/// }
1408/// ```
1409#[cfg(feature = "http")]
1410#[proc_macro_attribute]
1411pub fn route(_attr: TokenStream, item: TokenStream) -> TokenStream {
1412    // Pass through unchanged - the #[http] macro parses these attributes
1413    item
1414}
1415
1416/// Helper attribute for method-level HTTP response customization.
1417///
1418/// This attribute is used within `#[http]` impl blocks to customize
1419/// individual method responses. It is a no-op on its own.
1420///
1421/// # Supported Options
1422///
1423/// - `status = <code>` - Custom HTTP status code (e.g., 201, 204)
1424/// - `content_type = "<type>"` - Custom content type
1425/// - `header = "<name>", value = "<value>"` - Add custom response header
1426///
1427/// Multiple `#[response(...)]` attributes can be combined on a single method.
1428///
1429/// # Examples
1430///
1431/// ```ignore
1432/// #[http(prefix = "/api")]
1433/// impl MyService {
1434///     // Custom status code for creation
1435///     #[response(status = 201)]
1436///     fn create_item(&self, name: String) -> Item { /* ... */ }
1437///
1438///     // No content response
1439///     #[response(status = 204)]
1440///     fn delete_item(&self, id: String) { /* ... */ }
1441///
1442///     // Binary response with custom content type
1443///     #[response(content_type = "application/octet-stream")]
1444///     fn download(&self, id: String) -> Vec<u8> { /* ... */ }
1445///
1446///     // Add custom headers
1447///     #[response(header = "X-Custom", value = "foo")]
1448///     fn with_header(&self) -> String { /* ... */ }
1449///
1450///     // Combine multiple response attributes
1451///     #[response(status = 201)]
1452///     #[response(header = "Location", value = "/api/items/123")]
1453///     #[response(header = "X-Request-Id", value = "abc")]
1454///     fn create_with_headers(&self, name: String) -> Item { /* ... */ }
1455/// }
1456/// ```
1457#[cfg(feature = "http")]
1458#[proc_macro_attribute]
1459pub fn response(_attr: TokenStream, item: TokenStream) -> TokenStream {
1460    // Pass through unchanged - the #[http] macro parses these attributes
1461    item
1462}
1463
1464/// Helper attribute for parameter-level HTTP customization.
1465///
1466/// This attribute is used on function parameters within `#[http]` impl blocks
1467/// to customize parameter extraction and naming. It is a no-op on its own.
1468///
1469/// **Note:** Requires nightly Rust with `#![feature(register_tool)]` and
1470/// `#![register_tool(param)]` at the crate root.
1471///
1472/// # Supported Options
1473///
1474/// - `name = "<wire_name>"` - Use a different name on the wire (e.g., `q` instead of `query`)
1475/// - `default = <value>` - Provide a default value for optional parameters
1476/// - `query` - Force parameter to come from query string
1477/// - `path` - Force parameter to come from URL path
1478/// - `body` - Force parameter to come from request body
1479/// - `header` - Extract parameter from HTTP header
1480///
1481/// # Location Inference
1482///
1483/// When no location is specified, parameters are inferred based on conventions:
1484/// - Parameters named `id` or ending in `_id` → path parameters
1485/// - POST/PUT/PATCH methods → body parameters
1486/// - GET/DELETE methods → query parameters
1487///
1488/// # Examples
1489///
1490/// ```ignore
1491/// #![feature(register_tool)]
1492/// #![register_tool(param)]
1493///
1494/// #[http(prefix = "/api")]
1495/// impl SearchService {
1496///     // Rename parameter: code uses `query`, API accepts `q`
1497///     fn search(&self, #[param(name = "q")] query: String) -> Vec<Result> {
1498///         /* ... */
1499///     }
1500///
1501///     // Default value for pagination
1502///     fn list_items(
1503///         &self,
1504///         #[param(default = 0)] offset: u32,
1505///         #[param(default = 10)] limit: u32,
1506///     ) -> Vec<Item> {
1507///         /* ... */
1508///     }
1509///
1510///     // Extract API key from header
1511///     fn protected_endpoint(
1512///         &self,
1513///         #[param(header, name = "X-API-Key")] api_key: String,
1514///         data: String,
1515///     ) -> String {
1516///         /* ... */
1517///     }
1518///
1519///     // Override location inference: force to query even though method is POST
1520///     fn search_posts(
1521///         &self,
1522///         #[param(query)] filter: String,
1523///         #[param(body)] content: String,
1524///     ) -> Vec<Post> {
1525///         /* ... */
1526///     }
1527///
1528///     // Combine multiple options
1529///     fn advanced(
1530///         &self,
1531///         #[param(query, name = "page", default = 1)] page_num: u32,
1532///     ) -> Vec<Item> {
1533///         /* ... */
1534///     }
1535/// }
1536/// ```
1537///
1538/// # OpenAPI Integration
1539///
1540/// - Parameters with `name` are documented with their wire names
1541/// - Parameters with `default` are marked as not required
1542/// - Location overrides are reflected in OpenAPI specs
1543#[cfg(feature = "http")]
1544#[proc_macro_attribute]
1545pub fn param(_attr: TokenStream, item: TokenStream) -> TokenStream {
1546    // Pass through unchanged - the #[http] macro parses these attributes
1547    item
1548}
1549
1550// ============================================================================
1551// Blessed Presets
1552// ============================================================================
1553
1554/// Blessed preset: HTTP server with OpenAPI and serve.
1555///
1556/// Combines `#[http]` + `#[serve(http)]` into a single attribute.
1557///
1558/// # Example
1559///
1560/// ```ignore
1561/// use server_less::server;
1562///
1563/// #[derive(Clone)]
1564/// struct MyApi;
1565///
1566/// #[server]
1567/// impl MyApi {
1568///     pub fn list_items(&self) -> Vec<String> { vec![] }
1569///     pub fn create_item(&self, name: String) -> String { name }
1570/// }
1571///
1572/// // Equivalent to:
1573/// // #[http]
1574/// // #[serve(http)]
1575/// // impl MyApi { ... }
1576/// ```
1577///
1578/// # Options
1579///
1580/// - `prefix` - URL prefix (e.g., `#[server(prefix = "/api")]`)
1581/// - `openapi` - Toggle OpenAPI generation (default: true)
1582/// - `health` - Custom health check path (default: `/health`)
1583#[cfg(feature = "http")]
1584#[proc_macro_attribute]
1585pub fn server(attr: TokenStream, item: TokenStream) -> TokenStream {
1586    // When applied to a method inside an impl block (e.g. `#[server(skip)]`),
1587    // pass through unchanged.  The enclosing protocol macro reads these
1588    // attributes from the ItemImpl tokens; `#[server]` just needs to not error.
1589    let item2: proc_macro2::TokenStream = item.clone().into();
1590    if syn::parse2::<ItemImpl>(item2).is_err() {
1591        return item;
1592    }
1593    let args = parse_macro_input!(attr as server::ServerArgs);
1594    let impl_block = parse_macro_input!(item as ItemImpl);
1595    let name = type_name(&impl_block.self_ty);
1596
1597    match server::expand_server(args, impl_block) {
1598        Ok(tokens) => {
1599            debug_emit("server", &name, &tokens);
1600            tokens.into()
1601        }
1602        Err(err) => err.to_compile_error().into(),
1603    }
1604}
1605
1606/// Blessed preset: JSON-RPC server with OpenRPC spec and serve.
1607///
1608/// Combines `#[jsonrpc]` + `#[openrpc]` + `#[serve(jsonrpc)]` into a single attribute.
1609/// OpenRPC and serve are included when their features are enabled, and gracefully
1610/// omitted otherwise.
1611///
1612/// # Example
1613///
1614/// ```ignore
1615/// use server_less::rpc;
1616///
1617/// #[derive(Clone)]
1618/// struct Calculator;
1619///
1620/// #[rpc]
1621/// impl Calculator {
1622///     pub fn add(&self, a: i32, b: i32) -> i32 { a + b }
1623///     pub fn multiply(&self, a: i32, b: i32) -> i32 { a * b }
1624/// }
1625/// ```
1626///
1627/// # Options
1628///
1629/// - `path` - JSON-RPC endpoint path (e.g., `#[rpc(path = "/api")]`)
1630/// - `openrpc` - Toggle OpenRPC spec generation (default: true)
1631/// - `health` - Custom health check path (default: `/health`)
1632#[cfg(feature = "jsonrpc")]
1633#[proc_macro_attribute]
1634pub fn rpc(attr: TokenStream, item: TokenStream) -> TokenStream {
1635    let args = parse_macro_input!(attr as rpc_preset::RpcArgs);
1636    let impl_block = parse_macro_input!(item as ItemImpl);
1637    let name = type_name(&impl_block.self_ty);
1638
1639    match rpc_preset::expand_rpc(args, impl_block) {
1640        Ok(tokens) => {
1641            debug_emit("rpc", &name, &tokens);
1642            tokens.into()
1643        }
1644        Err(err) => err.to_compile_error().into(),
1645    }
1646}
1647
1648/// Blessed preset: MCP tools with JSON Schema.
1649///
1650/// Combines `#[mcp]` + `#[jsonschema]` into a single attribute.
1651/// JSON Schema is included when the feature is enabled, and gracefully
1652/// omitted otherwise.
1653///
1654/// # Example
1655///
1656/// ```ignore
1657/// use server_less::tool;
1658///
1659/// struct FileTools;
1660///
1661/// #[tool(namespace = "file")]
1662/// impl FileTools {
1663///     pub fn read_file(&self, path: String) -> String { String::new() }
1664///     pub fn write_file(&self, path: String, content: String) -> bool { true }
1665/// }
1666/// ```
1667///
1668/// # Options
1669///
1670/// - `namespace` - MCP tool namespace prefix
1671/// - `jsonschema` - Toggle JSON Schema generation (default: true)
1672#[cfg(feature = "mcp")]
1673#[proc_macro_attribute]
1674pub fn tool(attr: TokenStream, item: TokenStream) -> TokenStream {
1675    let args = parse_macro_input!(attr as tool::ToolArgs);
1676    let impl_block = parse_macro_input!(item as ItemImpl);
1677    let name = type_name(&impl_block.self_ty);
1678
1679    match tool::expand_tool(args, impl_block) {
1680        Ok(tokens) => {
1681            debug_emit("tool", &name, &tokens);
1682            tokens.into()
1683        }
1684        Err(err) => err.to_compile_error().into(),
1685    }
1686}
1687
1688/// Blessed preset: CLI application with Markdown docs.
1689///
1690/// Combines `#[cli]` + `#[markdown]` into a single attribute.
1691/// Markdown docs are included when the feature is enabled, and gracefully
1692/// omitted otherwise.
1693///
1694/// Named `program` instead of `cli` to avoid collision with the existing
1695/// `#[cli]` attribute macro.
1696///
1697/// # Example
1698///
1699/// ```ignore
1700/// use server_less::program;
1701///
1702/// struct MyApp;
1703///
1704/// #[program(name = "myctl", version = "1.0.0")]
1705/// impl MyApp {
1706///     pub fn create_user(&self, name: String) { println!("Created {}", name); }
1707///     pub fn list_users(&self) { println!("Listing users..."); }
1708/// }
1709/// ```
1710///
1711/// # Options
1712///
1713/// - `name` - CLI application name
1714/// - `version` - CLI version string
1715/// - `about` - CLI description
1716/// - `markdown` - Toggle Markdown docs generation (default: true)
1717#[cfg(feature = "cli")]
1718#[proc_macro_attribute]
1719pub fn program(attr: TokenStream, item: TokenStream) -> TokenStream {
1720    let args = parse_macro_input!(attr as program::ProgramArgs);
1721    let impl_block = parse_macro_input!(item as ItemImpl);
1722    let name = type_name(&impl_block.self_ty);
1723
1724    match program::expand_program(args, impl_block) {
1725        Ok(tokens) => {
1726            debug_emit("program", &name, &tokens);
1727            tokens.into()
1728        }
1729        Err(err) => err.to_compile_error().into(),
1730    }
1731}
1732
1733/// Derive macro for error types that implement `IntoErrorCode`.
1734///
1735/// # Example
1736///
1737/// ```ignore
1738/// use server_less::ServerlessError;
1739///
1740/// #[derive(ServerlessError)]
1741/// enum MyError {
1742///     #[error(code = NotFound, message = "User not found")]
1743///     UserNotFound,
1744///     #[error(code = 400)]  // HTTP status also works
1745///     InvalidInput(String),
1746///     // Code inferred from variant name
1747///     Unauthorized,
1748/// }
1749/// ```
1750///
1751/// This generates:
1752/// - `impl IntoErrorCode for MyError`
1753/// - `impl Display for MyError`
1754/// - `impl Error for MyError`
1755///
1756/// # Attributes
1757///
1758/// - `#[error(code = X)]` - Set error code (ErrorCode variant or HTTP status)
1759/// - `#[error(message = "...")]` - Set custom message
1760///
1761/// Without attributes, the error code is inferred from the variant name.
1762#[proc_macro_derive(ServerlessError, attributes(error))]
1763pub fn serverless_error(input: TokenStream) -> TokenStream {
1764    let input = parse_macro_input!(input as DeriveInput);
1765    let name = input.ident.to_string();
1766
1767    match error::expand_serverless_error(input) {
1768        Ok(tokens) => {
1769            debug_emit("ServerlessError", &name, &tokens);
1770            tokens.into()
1771        }
1772        Err(err) => err.to_compile_error().into(),
1773    }
1774}