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]` + `#[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()`, `openapi_spec()` |
54//! | `#[cli]` | Command Line | `cli_command()`, `cli_run()`, `cli_run_async()` |
55//! | `#[mcp]` | MCP | `mcp_tools()`, `mcp_call()`, `mcp_call_async()` |
56//! | `#[ws]` | WebSocket | `ws_router()`, `ws_handle_message()`, `ws_handle_message_async()` |
57//! | `#[jsonrpc]` | JSON-RPC 2.0 | `jsonrpc_router()`, `jsonrpc_methods()` |
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::{Config as ConfigTrait, 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 schema generation
234//! - `config` - `#[derive(Config)]` for config loading (requires toml)
235//! - `full` - All features (default)
236
237// Re-export macros (feature-gated)
238#[cfg(feature = "mcp")]
239pub use server_less_macros::mcp;
240
241#[cfg(feature = "http")]
242pub use server_less_macros::http;
243
244#[cfg(any(feature = "http", feature = "openapi"))]
245pub use server_less_macros::openapi;
246
247#[cfg(feature = "http")]
248pub use server_less_macros::route;
249
250#[cfg(feature = "http")]
251pub use server_less_macros::response;
252
253#[cfg(feature = "http")]
254pub use server_less_macros::serve;
255
256#[cfg(feature = "cli")]
257pub use server_less_macros::cli;
258
259#[cfg(feature = "cli")]
260pub use server_less_core::CliSubcommand;
261
262#[cfg(feature = "cli")]
263pub use server_less_core::cli_format_output;
264
265#[cfg(feature = "mcp")]
266pub use server_less_core::McpNamespace;
267
268#[cfg(feature = "jsonrpc")]
269pub use server_less_core::JsonRpcMount;
270
271#[cfg(feature = "ws")]
272pub use server_less_core::WsMount;
273
274#[cfg(feature = "http")]
275pub use server_less_core::HttpMount;
276
277#[cfg(feature = "ws")]
278pub use server_less_macros::ws;
279
280#[cfg(feature = "jsonrpc")]
281pub use server_less_macros::jsonrpc;
282
283#[cfg(feature = "openrpc")]
284pub use server_less_macros::openrpc;
285
286#[cfg(feature = "graphql")]
287pub use server_less_macros::graphql;
288#[cfg(feature = "graphql")]
289pub use server_less_macros::graphql_enum;
290#[cfg(feature = "graphql")]
291pub use server_less_macros::graphql_input;
292
293#[cfg(feature = "grpc")]
294pub use server_less_macros::grpc;
295
296#[cfg(feature = "capnp")]
297pub use server_less_macros::capnp;
298
299#[cfg(feature = "thrift")]
300pub use server_less_macros::thrift;
301
302#[cfg(feature = "connect")]
303pub use server_less_macros::connect;
304
305#[cfg(feature = "smithy")]
306pub use server_less_macros::smithy;
307
308#[cfg(feature = "markdown")]
309pub use server_less_macros::markdown;
310
311#[cfg(feature = "jsonschema")]
312pub use server_less_macros::jsonschema;
313
314#[cfg(feature = "asyncapi")]
315pub use server_less_macros::asyncapi;
316
317// Blessed presets
318#[cfg(feature = "http")]
319pub use server_less_macros::server;
320
321#[cfg(feature = "jsonrpc")]
322pub use server_less_macros::rpc;
323
324#[cfg(feature = "mcp")]
325pub use server_less_macros::tool;
326
327#[cfg(feature = "cli")]
328pub use server_less_macros::program;
329
330// Error derive macro (always available - no deps, commonly needed)
331pub use server_less_macros::ServerlessError;
332
333// Application metadata attribute (always available)
334pub use server_less_macros::app;
335#[doc(hidden)]
336pub use server_less_macros::__app_meta;
337
338// Config derive macro
339#[cfg(feature = "config")]
340pub use server_less_macros::Config;
341#[cfg(feature = "config")]
342pub use server_less_core::config::{Config as ConfigTrait, ConfigError, ConfigFieldMeta, ConfigSource};
343
344// Re-export deps for generated code — users shouldn't need to add these directly
345#[cfg(feature = "clap")]
346#[doc(hidden)]
347pub use clap;
348
349#[cfg(any(feature = "cli", feature = "http"))]
350#[doc(hidden)]
351pub use tokio;
352
353#[cfg(feature = "axum")]
354#[doc(hidden)]
355pub use axum;
356
357// Re-export futures for generated WebSocket code
358#[cfg(feature = "ws")]
359pub use futures;
360
361// Re-export async-graphql for generated GraphQL code
362#[cfg(feature = "graphql")]
363pub use async_graphql;
364#[cfg(feature = "graphql")]
365pub use async_graphql_axum;
366
367// Re-export core types
368pub use server_less_core::*;
369
370// Re-export OpenAPI composition utilities (available when any protocol that generates OpenAPI is enabled)
371#[cfg(feature = "server-less-openapi")]
372pub use server_less_openapi::{
373 OpenApiBuilder, OpenApiError, OpenApiOperation, OpenApiParameter, OpenApiPath, OpenApiSchema,
374};
375
376// Re-export serde for generated code
377pub use serde;
378pub use serde_json;
379
380/// Prelude for convenient imports
381pub mod prelude {
382 // Runtime protocols
383 #[cfg(feature = "cli")]
384 pub use super::CliSubcommand;
385 #[cfg(feature = "http")]
386 pub use super::HttpMount;
387 #[cfg(feature = "jsonrpc")]
388 pub use super::JsonRpcMount;
389 #[cfg(feature = "mcp")]
390 pub use super::McpNamespace;
391 #[cfg(feature = "ws")]
392 pub use super::WsMount;
393 #[cfg(feature = "cli")]
394 pub use super::cli;
395 #[cfg(feature = "graphql")]
396 pub use super::graphql;
397 #[cfg(feature = "graphql")]
398 pub use super::graphql_enum;
399 #[cfg(feature = "graphql")]
400 pub use super::graphql_input;
401 #[cfg(feature = "http")]
402 pub use super::http;
403 #[cfg(feature = "jsonrpc")]
404 pub use super::jsonrpc;
405 #[cfg(feature = "mcp")]
406 pub use super::mcp;
407 #[cfg(any(feature = "http", feature = "openapi"))]
408 pub use super::openapi;
409 #[cfg(feature = "http")]
410 pub use super::response;
411 #[cfg(feature = "http")]
412 pub use super::route;
413 #[cfg(feature = "http")]
414 pub use super::serve;
415 #[cfg(feature = "ws")]
416 pub use super::ws;
417
418 // Schema generators
419 #[cfg(feature = "capnp")]
420 pub use super::capnp;
421 #[cfg(feature = "connect")]
422 pub use super::connect;
423 #[cfg(feature = "grpc")]
424 pub use super::grpc;
425 #[cfg(feature = "smithy")]
426 pub use super::smithy;
427 #[cfg(feature = "thrift")]
428 pub use super::thrift;
429
430 // Specification generators
431 #[cfg(feature = "asyncapi")]
432 pub use super::asyncapi;
433 #[cfg(feature = "jsonschema")]
434 pub use super::jsonschema;
435 #[cfg(feature = "openrpc")]
436 pub use super::openrpc;
437
438 // Documentation generators
439 #[cfg(feature = "markdown")]
440 pub use super::markdown;
441
442 // Blessed presets
443 #[cfg(feature = "cli")]
444 pub use super::program;
445 #[cfg(feature = "jsonrpc")]
446 pub use super::rpc;
447 #[cfg(feature = "http")]
448 pub use super::server;
449 #[cfg(feature = "mcp")]
450 pub use super::tool;
451
452 // Always available
453 pub use super::{Context, ErrorCode, ErrorResponse, IntoErrorCode, ServerlessError};
454
455 // OpenAPI composition (available when any protocol that generates OpenAPI is enabled)
456 #[cfg(feature = "server-less-openapi")]
457 pub use super::OpenApiBuilder;
458 pub use serde::{Deserialize, Serialize};
459
460 // WebSocket sender (when ws feature enabled)
461 #[cfg(feature = "ws")]
462 pub use super::WsSender;
463}