Skip to main content

server_less_macros/
lib.rs

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