Skip to main content

server_less/
lib.rs

1//! Server-less - Composable derive macros for Rust
2//!
3//! Server-less takes an **impl-first** approach: write your Rust methods,
4//! and derive macros project them into various protocols (HTTP, CLI, MCP, WebSocket).
5//!
6//! # Quick Start
7//!
8//! ```no_run
9//! use server_less::prelude::*;
10//! use serde::{Deserialize, Serialize};
11//!
12//! #[derive(Serialize, Deserialize)]
13//! struct User { name: String, email: String }
14//!
15//! #[derive(Debug, ServerlessError)]
16//! enum UserError { NotFound }
17//!
18//! struct UserService;
19//!
20//! #[mcp]
21//! impl UserService {
22//!     /// Create a new user
23//!     async fn create_user(&self, name: String, email: String) -> Result<User, UserError> {
24//!         Ok(User { name, email })
25//!     }
26//!
27//!     /// List all users
28//!     async fn list_users(&self, limit: Option<u32>) -> Vec<User> {
29//!         let _ = limit;
30//!         vec![]
31//!     }
32//! }
33//! ```
34//!
35//! This generates:
36//! - **MCP**: Tools `create_user`, `list_users` (Model Context Protocol)
37//!
38//! # Available Macros
39//!
40//! **Blessed presets** (batteries-included, use these to get started):
41//!
42//! | Macro | Combines | Feature |
43//! |-------|----------|---------|
44//! | `#[server]` | `#[http]` (with OpenAPI) + `#[serve(http)]` | `http` |
45//! | `#[program]` | `#[cli]` + `#[markdown]` | `cli` |
46//! | `#[tool]` | `#[mcp]` + `#[jsonschema]` | `mcp` |
47//! | `#[rpc]` | `#[jsonrpc]` + `#[openrpc]` + `#[serve(jsonrpc)]` | `jsonrpc` |
48//!
49//! **À la carte macros** (explicit composition):
50//!
51//! | Macro | Protocol | Generated Methods |
52//! |-------|----------|-------------------|
53//! | `#[http]` | HTTP/REST | `http_router()`, `http_openapi_spec()`, `http_openapi_paths()` |
54//! | `#[cli]` | Command Line | `cli_command()`, `cli_run()`, `cli_run_with()`, `cli_run_async()`, `cli_run_with_async()` |
55//! | `#[mcp]` | MCP | `mcp_tools()`, `mcp_call()`, `mcp_call_async()`, `mcp_method_names()` |
56//! | `#[ws]` | WebSocket | `ws_router()`, `ws_handle_message()`, `ws_handle_message_async()`, `ws_methods()` |
57//! | `#[jsonrpc]` | JSON-RPC 2.0 | `jsonrpc_router()`, `jsonrpc_methods()`, `jsonrpc_handle_async()` |
58//! | `#[graphql]` | GraphQL | async-graphql integration |
59//! | `#[grpc]` | gRPC | `.proto` schema generation |
60//!
61//! **Cross-cutting attributes:**
62//!
63//! | Macro | Purpose |
64//! |-------|---------|
65//! | `#[app(...)]` | Attach protocol-neutral metadata (name, description, version, homepage) |
66//! | `#[derive(Config)]` | Generate config loading from env vars, TOML files, and defaults |
67//! | `#[derive(ServerlessError)]` | Derive `IntoErrorCode` + `Display` + `Error` for error enums |
68//! | `#[route(...)]` | Per-method HTTP overrides (method, path, skip, hidden) |
69//! | `#[response(...)]` | Per-method response customization |
70//! | `#[param(...)]` | Per-parameter metadata (name, default, location, env, help) |
71//!
72//! # Naming Conventions
73//!
74//! Method names infer HTTP methods and CLI subcommand structure:
75//!
76//! | Prefix | HTTP | CLI |
77//! |--------|------|-----|
78//! | `create_*`, `add_*` | POST | `<cmd> create-*` |
79//! | `get_*`, `fetch_*` | GET (single) | `<cmd> get-*` |
80//! | `list_*`, `find_*` | GET (collection) | `<cmd> list-*` |
81//! | `update_*`, `set_*` | PUT | `<cmd> update-*` |
82//! | `delete_*`, `remove_*` | DELETE | `<cmd> delete-*` |
83//!
84//! # Return Types
85//!
86//! | Type | HTTP | CLI | MCP/WS |
87//! |------|------|-----|--------|
88//! | `T` | 200 + JSON | stdout JSON | JSON result |
89//! | `Option<T>` | 200 or 404 | stdout or exit 1 | result or null |
90//! | `Result<T, E>` | 200 or error | stdout or stderr | result or error |
91//! | `()` | 204 | silent | `{"success": true}` |
92//! | `impl Stream<Item=T>` | SSE | N/A | N/A |
93//!
94//! # Async Methods
95//!
96//! All macros support async methods:
97//!
98//! ```no_run
99//! use server_less::prelude::*;
100//!
101//! struct MyService;
102//!
103//! #[mcp]
104//! impl MyService {
105//!     /// Sync method - works with mcp_call() and mcp_call_async()
106//!     pub fn sync_method(&self) -> String {
107//!         String::from("hello")
108//!     }
109//!
110//!     /// Async method - use mcp_call_async() for proper await
111//!     pub async fn async_method(&self) -> String {
112//!         String::from("hello async")
113//!     }
114//! }
115//!
116//! #[tokio::main]
117//! async fn main() {
118//!     let service = MyService;
119//!     // Sync call (errors on async methods)
120//!     service.mcp_call("sync_method", serde_json::json!({}));
121//!     // Async call (awaits async methods properly)
122//!     service.mcp_call_async("async_method", serde_json::json!({})).await;
123//! }
124//! ```
125//!
126//! # SSE Streaming (HTTP)
127//!
128//! Return `impl Stream<Item=T>` for Server-Sent Events:
129//!
130//! ```no_run
131//! use server_less::prelude::*;
132//! use serde::{Deserialize, Serialize};
133//!
134//! #[derive(Clone, Serialize, Deserialize)]
135//! struct Event { message: String }
136//!
137//! #[derive(Clone)]
138//! struct StreamService;
139//!
140//! #[http]
141//! impl StreamService {
142//!     // Note: Rust 2024 requires `+ use<>` to avoid lifetime capture
143//!     pub fn stream_events(&self) -> impl futures::Stream<Item = Event> + use<> {
144//!         futures::stream::iter(vec![Event { message: String::from("hello") }])
145//!     }
146//! }
147//! ```
148//!
149//! # Application Metadata
150//!
151//! `#[app]` attaches protocol-neutral metadata consumed by all protocol macros on the same impl:
152//!
153//! ```no_run
154//! use server_less::prelude::*;
155//! use server_less::{app, __app_meta};
156//!
157//! #[derive(Clone)]
158//! struct MyApi;
159//!
160//! #[app(name = "myapi", description = "My API", version = "1.0.0")]
161//! #[server]
162//! impl MyApi {
163//!     pub fn hello(&self) -> String { String::from("hi") }
164//! }
165//! ```
166//!
167//! Preset macros also accept metadata inline:
168//!
169//! ```no_run
170//! use server_less::prelude::*;
171//!
172//! #[derive(Clone)]
173//! struct MyApi;
174//!
175//! #[server(name = "myapi", description = "My API")]
176//! impl MyApi {
177//!     pub fn hello(&self) -> String { String::from("hi") }
178//! }
179//! ```
180//!
181//! # Config Management
182//!
183//! `#[derive(Config)]` generates config loading from env vars, TOML files, and defaults:
184//!
185//! ```no_run
186//! use server_less::prelude::*;
187//! # #[cfg(feature = "config")]
188//! use server_less::{ConfigLoad, ConfigSource};
189//!
190//! # #[cfg(feature = "config")]
191//! #[derive(server_less::Config)]
192//! struct AppConfig {
193//!     #[param(default = "localhost")]
194//!     host: String,
195//!     #[param(default = 8080)]
196//!     port: u16,
197//!     #[param(env = "DATABASE_URL")]
198//!     database_url: String,
199//! }
200//! ```
201//!
202//! Pass the config type to `#[server]` to add a `config` subcommand and wire it into serve:
203//!
204//! ```no_run
205//! use server_less::prelude::*;
206//!
207//! #[derive(Clone)]
208//! struct MyService;
209//!
210//! // #[server(config = AppConfig)]
211//! #[server]
212//! impl MyService {
213//!     pub fn hello(&self) -> String { String::from("hi") }
214//! }
215//! ```
216//!
217//! # Feature Flags
218//!
219//! Enable only what you need:
220//!
221//! ```toml
222//! [dependencies]
223//! server-less = { version = "0.4", default-features = false, features = ["http", "cli"] }
224//! ```
225//!
226//! Available features:
227//! - `mcp` - MCP macro (no extra deps)
228//! - `http` - HTTP macro (requires axum)
229//! - `cli` - CLI macro (requires clap)
230//! - `ws` - WebSocket macro (requires axum, futures)
231//! - `jsonrpc` - JSON-RPC 2.0 macro
232//! - `graphql` - GraphQL macro (requires async-graphql)
233//! - `grpc` - gRPC `.proto` schema generation (no runtime deps)
234//! - `capnp` - Cap'n Proto `.capnp` schema generation (no runtime deps)
235//! - `thrift` - Apache Thrift `.thrift` IDL generation (no runtime deps)
236//! - `connect` - Connect RPC schema generation (no runtime deps)
237//! - `smithy` - AWS Smithy `.smithy` model generation (no runtime deps)
238//! - `openapi` - Standalone OpenAPI spec generation (no axum required)
239//! - `openrpc` - OpenRPC spec generation (no runtime deps)
240//! - `asyncapi` - AsyncAPI spec generation (no runtime deps)
241//! - `jsonschema` - JSON Schema generation (no runtime deps)
242//! - `markdown` - Markdown API docs generation (no runtime deps)
243//! - `config` - `#[derive(Config)]` for config loading (requires toml)
244//! - `full` - All features (default)
245
246// Re-export macros (feature-gated)
247#[cfg(feature = "mcp")]
248pub use server_less_macros::mcp;
249
250#[cfg(feature = "http")]
251pub use server_less_macros::http;
252
253#[cfg(any(feature = "http", feature = "openapi"))]
254pub use server_less_macros::openapi;
255
256#[cfg(feature = "http")]
257pub use server_less_macros::route;
258
259#[cfg(feature = "http")]
260pub use server_less_macros::response;
261
262#[cfg(any(feature = "http", feature = "cli", feature = "mcp"))]
263pub use server_less_macros::param;
264
265#[cfg(feature = "http")]
266pub use server_less_macros::serve;
267
268#[cfg(feature = "cli")]
269pub use server_less_macros::cli;
270
271#[cfg(feature = "cli")]
272pub use server_less_core::CliSubcommand;
273
274#[cfg(feature = "cli")]
275pub use server_less_core::CliGlobals;
276
277#[cfg(feature = "cli")]
278pub use server_less_core::cli_format_output;
279
280#[cfg(feature = "cli")]
281pub use server_less_core::{CliManualNode, cli_manual_to_json, cli_manual_to_text};
282
283#[cfg(feature = "mcp")]
284pub use server_less_core::McpNamespace;
285
286#[cfg(feature = "jsonrpc")]
287pub use server_less_core::JsonRpcMount;
288
289#[cfg(feature = "ws")]
290pub use server_less_core::WsMount;
291
292#[cfg(feature = "http")]
293pub use server_less_core::HttpMount;
294
295#[cfg(feature = "ws")]
296pub use server_less_macros::ws;
297
298#[cfg(feature = "jsonrpc")]
299pub use server_less_macros::jsonrpc;
300
301#[cfg(feature = "openrpc")]
302pub use server_less_macros::openrpc;
303
304#[cfg(feature = "graphql")]
305pub use server_less_macros::graphql;
306#[cfg(feature = "graphql")]
307pub use server_less_macros::graphql_enum;
308#[cfg(feature = "graphql")]
309pub use server_less_macros::graphql_input;
310
311#[cfg(feature = "grpc")]
312pub use server_less_macros::grpc;
313
314#[cfg(feature = "capnp")]
315pub use server_less_macros::capnp;
316
317#[cfg(feature = "thrift")]
318pub use server_less_macros::thrift;
319
320#[cfg(feature = "connect")]
321pub use server_less_macros::connect;
322
323#[cfg(feature = "smithy")]
324pub use server_less_macros::smithy;
325
326#[cfg(feature = "markdown")]
327pub use server_less_macros::markdown;
328
329#[cfg(feature = "jsonschema")]
330pub use server_less_macros::jsonschema;
331
332#[cfg(feature = "asyncapi")]
333pub use server_less_macros::asyncapi;
334
335// Blessed presets
336#[cfg(feature = "http")]
337pub use server_less_macros::server;
338
339#[cfg(feature = "jsonrpc")]
340pub use server_less_macros::rpc;
341
342#[cfg(feature = "mcp")]
343pub use server_less_macros::tool;
344
345#[cfg(feature = "cli")]
346pub use server_less_macros::program;
347
348// Error derive macro (always available - no deps, commonly needed)
349pub use server_less_macros::ServerlessError;
350
351// Application metadata attribute (always available)
352pub use server_less_macros::app;
353#[doc(hidden)]
354pub use server_less_macros::__app_meta;
355
356// Standalone health-check derive
357#[cfg(feature = "health")]
358pub use server_less_macros::HealthCheck;
359
360// Config derive macro
361#[cfg(feature = "config")]
362pub use server_less_macros::Config;
363#[cfg(feature = "config")]
364pub use server_less_core::config::{ConfigLoad, ConfigError, ConfigFieldMeta, ConfigSource};
365
366// Re-export deps for generated code — users shouldn't need to add these directly
367#[cfg(feature = "cli")]
368#[doc(hidden)]
369pub use clap;
370
371// Shell completions / man page generators for the #[cli] projection
372#[cfg(feature = "completions")]
373#[doc(hidden)]
374pub use clap_complete;
375#[cfg(feature = "completions")]
376#[doc(hidden)]
377pub use clap_mangen;
378
379#[cfg(any(feature = "cli", feature = "http"))]
380#[doc(hidden)]
381pub use tokio;
382
383#[cfg(any(feature = "http", feature = "ws", feature = "jsonrpc", feature = "graphql"))]
384#[doc(hidden)]
385pub use axum;
386
387// Re-export futures for generated WebSocket code
388#[cfg(feature = "ws")]
389#[doc(hidden)]
390pub use futures;
391
392// Re-export async-graphql for generated GraphQL code
393#[cfg(feature = "graphql")]
394#[doc(hidden)]
395pub use async_graphql;
396#[cfg(feature = "graphql")]
397#[doc(hidden)]
398pub use async_graphql_axum;
399
400// Re-export core types
401pub use server_less_core::*;
402
403// Re-export OpenAPI composition utilities (available when any protocol that generates OpenAPI is enabled)
404#[cfg(feature = "server-less-openapi")]
405pub use server_less_openapi::{
406    OpenApiBuilder, OpenApiError, OpenApiOperation, OpenApiParameter, OpenApiPath, OpenApiSchema,
407};
408
409// Re-export serde for generated code
410pub use serde;
411pub use serde_json;
412
413/// Prelude for convenient imports
414pub mod prelude {
415    // Runtime protocols
416    #[cfg(feature = "cli")]
417    pub use super::CliSubcommand;
418    #[cfg(feature = "cli")]
419    pub use super::CliGlobals;
420    #[cfg(feature = "http")]
421    pub use super::HttpMount;
422    #[cfg(feature = "jsonrpc")]
423    pub use super::JsonRpcMount;
424    #[cfg(feature = "mcp")]
425    pub use super::McpNamespace;
426    #[cfg(feature = "ws")]
427    pub use super::WsMount;
428    #[cfg(feature = "cli")]
429    pub use super::cli;
430    #[cfg(feature = "graphql")]
431    pub use super::graphql;
432    #[cfg(feature = "graphql")]
433    pub use super::graphql_enum;
434    #[cfg(feature = "graphql")]
435    pub use super::graphql_input;
436    #[cfg(feature = "http")]
437    pub use super::http;
438    #[cfg(feature = "jsonrpc")]
439    pub use super::jsonrpc;
440    #[cfg(feature = "mcp")]
441    pub use super::mcp;
442    #[cfg(any(feature = "http", feature = "openapi"))]
443    pub use super::openapi;
444    #[cfg(feature = "http")]
445    pub use super::response;
446    #[cfg(feature = "http")]
447    pub use super::route;
448    #[cfg(feature = "http")]
449    pub use super::serve;
450    #[cfg(feature = "ws")]
451    pub use super::ws;
452
453    // Schema generators
454    #[cfg(feature = "capnp")]
455    pub use super::capnp;
456    #[cfg(feature = "connect")]
457    pub use super::connect;
458    #[cfg(feature = "grpc")]
459    pub use super::grpc;
460    #[cfg(feature = "smithy")]
461    pub use super::smithy;
462    #[cfg(feature = "thrift")]
463    pub use super::thrift;
464
465    // Specification generators
466    #[cfg(feature = "asyncapi")]
467    pub use super::asyncapi;
468    #[cfg(feature = "jsonschema")]
469    pub use super::jsonschema;
470    #[cfg(feature = "openrpc")]
471    pub use super::openrpc;
472
473    // Documentation generators
474    #[cfg(feature = "markdown")]
475    pub use super::markdown;
476
477    // Blessed presets
478    #[cfg(feature = "cli")]
479    pub use super::program;
480    #[cfg(feature = "jsonrpc")]
481    pub use super::rpc;
482    #[cfg(feature = "http")]
483    pub use super::server;
484    #[cfg(feature = "mcp")]
485    pub use super::tool;
486
487    // Always available
488    pub use super::{Context, ErrorCode, ErrorResponse, IntoErrorCode, ServerlessError};
489
490    // OpenAPI composition (available when any protocol that generates OpenAPI is enabled)
491    #[cfg(feature = "server-less-openapi")]
492    pub use super::OpenApiBuilder;
493    pub use serde::{Deserialize, Serialize};
494
495    // WebSocket sender (when ws feature enabled)
496    #[cfg(feature = "ws")]
497    pub use super::WsSender;
498}