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