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