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