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::cli_format_output;
276
277#[cfg(feature = "cli")]
278pub use server_less_core::{CliManualNode, cli_manual_to_json, cli_manual_to_text};
279
280#[cfg(feature = "mcp")]
281pub use server_less_core::McpNamespace;
282
283#[cfg(feature = "jsonrpc")]
284pub use server_less_core::JsonRpcMount;
285
286#[cfg(feature = "ws")]
287pub use server_less_core::WsMount;
288
289#[cfg(feature = "http")]
290pub use server_less_core::HttpMount;
291
292#[cfg(feature = "ws")]
293pub use server_less_macros::ws;
294
295#[cfg(feature = "jsonrpc")]
296pub use server_less_macros::jsonrpc;
297
298#[cfg(feature = "openrpc")]
299pub use server_less_macros::openrpc;
300
301#[cfg(feature = "graphql")]
302pub use server_less_macros::graphql;
303#[cfg(feature = "graphql")]
304pub use server_less_macros::graphql_enum;
305#[cfg(feature = "graphql")]
306pub use server_less_macros::graphql_input;
307
308#[cfg(feature = "grpc")]
309pub use server_less_macros::grpc;
310
311#[cfg(feature = "capnp")]
312pub use server_less_macros::capnp;
313
314#[cfg(feature = "thrift")]
315pub use server_less_macros::thrift;
316
317#[cfg(feature = "connect")]
318pub use server_less_macros::connect;
319
320#[cfg(feature = "smithy")]
321pub use server_less_macros::smithy;
322
323#[cfg(feature = "markdown")]
324pub use server_less_macros::markdown;
325
326#[cfg(feature = "jsonschema")]
327pub use server_less_macros::jsonschema;
328
329#[cfg(feature = "asyncapi")]
330pub use server_less_macros::asyncapi;
331
332// Blessed presets
333#[cfg(feature = "http")]
334pub use server_less_macros::server;
335
336#[cfg(feature = "jsonrpc")]
337pub use server_less_macros::rpc;
338
339#[cfg(feature = "mcp")]
340pub use server_less_macros::tool;
341
342#[cfg(feature = "cli")]
343pub use server_less_macros::program;
344
345// Error derive macro (always available - no deps, commonly needed)
346pub use server_less_macros::ServerlessError;
347
348// Application metadata attribute (always available)
349pub use server_less_macros::app;
350#[doc(hidden)]
351pub use server_less_macros::__app_meta;
352
353// Standalone health-check derive
354#[cfg(feature = "health")]
355pub use server_less_macros::HealthCheck;
356
357// Config derive macro
358#[cfg(feature = "config")]
359pub use server_less_macros::Config;
360#[cfg(feature = "config")]
361pub use server_less_core::config::{ConfigLoad, ConfigError, ConfigFieldMeta, ConfigSource};
362
363// Re-export deps for generated code — users shouldn't need to add these directly
364#[cfg(feature = "cli")]
365#[doc(hidden)]
366pub use clap;
367
368// Shell completions / man page generators for the #[cli] projection
369#[cfg(feature = "completions")]
370#[doc(hidden)]
371pub use clap_complete;
372#[cfg(feature = "completions")]
373#[doc(hidden)]
374pub use clap_mangen;
375
376#[cfg(any(feature = "cli", feature = "http"))]
377#[doc(hidden)]
378pub use tokio;
379
380#[cfg(any(feature = "http", feature = "ws", feature = "jsonrpc", feature = "graphql"))]
381#[doc(hidden)]
382pub use axum;
383
384// Re-export futures for generated WebSocket code
385#[cfg(feature = "ws")]
386#[doc(hidden)]
387pub use futures;
388
389// Re-export async-graphql for generated GraphQL code
390#[cfg(feature = "graphql")]
391#[doc(hidden)]
392pub use async_graphql;
393#[cfg(feature = "graphql")]
394#[doc(hidden)]
395pub use async_graphql_axum;
396
397// Re-export core types
398pub use server_less_core::*;
399
400// Re-export OpenAPI composition utilities (available when any protocol that generates OpenAPI is enabled)
401#[cfg(feature = "server-less-openapi")]
402pub use server_less_openapi::{
403    OpenApiBuilder, OpenApiError, OpenApiOperation, OpenApiParameter, OpenApiPath, OpenApiSchema,
404};
405
406// Re-export serde for generated code
407pub use serde;
408pub use serde_json;
409
410/// Prelude for convenient imports
411pub mod prelude {
412    // Runtime protocols
413    #[cfg(feature = "cli")]
414    pub use super::CliSubcommand;
415    #[cfg(feature = "http")]
416    pub use super::HttpMount;
417    #[cfg(feature = "jsonrpc")]
418    pub use super::JsonRpcMount;
419    #[cfg(feature = "mcp")]
420    pub use super::McpNamespace;
421    #[cfg(feature = "ws")]
422    pub use super::WsMount;
423    #[cfg(feature = "cli")]
424    pub use super::cli;
425    #[cfg(feature = "graphql")]
426    pub use super::graphql;
427    #[cfg(feature = "graphql")]
428    pub use super::graphql_enum;
429    #[cfg(feature = "graphql")]
430    pub use super::graphql_input;
431    #[cfg(feature = "http")]
432    pub use super::http;
433    #[cfg(feature = "jsonrpc")]
434    pub use super::jsonrpc;
435    #[cfg(feature = "mcp")]
436    pub use super::mcp;
437    #[cfg(any(feature = "http", feature = "openapi"))]
438    pub use super::openapi;
439    #[cfg(feature = "http")]
440    pub use super::response;
441    #[cfg(feature = "http")]
442    pub use super::route;
443    #[cfg(feature = "http")]
444    pub use super::serve;
445    #[cfg(feature = "ws")]
446    pub use super::ws;
447
448    // Schema generators
449    #[cfg(feature = "capnp")]
450    pub use super::capnp;
451    #[cfg(feature = "connect")]
452    pub use super::connect;
453    #[cfg(feature = "grpc")]
454    pub use super::grpc;
455    #[cfg(feature = "smithy")]
456    pub use super::smithy;
457    #[cfg(feature = "thrift")]
458    pub use super::thrift;
459
460    // Specification generators
461    #[cfg(feature = "asyncapi")]
462    pub use super::asyncapi;
463    #[cfg(feature = "jsonschema")]
464    pub use super::jsonschema;
465    #[cfg(feature = "openrpc")]
466    pub use super::openrpc;
467
468    // Documentation generators
469    #[cfg(feature = "markdown")]
470    pub use super::markdown;
471
472    // Blessed presets
473    #[cfg(feature = "cli")]
474    pub use super::program;
475    #[cfg(feature = "jsonrpc")]
476    pub use super::rpc;
477    #[cfg(feature = "http")]
478    pub use super::server;
479    #[cfg(feature = "mcp")]
480    pub use super::tool;
481
482    // Always available
483    pub use super::{Context, ErrorCode, ErrorResponse, IntoErrorCode, ServerlessError};
484
485    // OpenAPI composition (available when any protocol that generates OpenAPI is enabled)
486    #[cfg(feature = "server-less-openapi")]
487    pub use super::OpenApiBuilder;
488    pub use serde::{Deserialize, Serialize};
489
490    // WebSocket sender (when ws feature enabled)
491    #[cfg(feature = "ws")]
492    pub use super::WsSender;
493}