Skip to main content

ferro_rs/
lib.rs

1//! Ferro — a full-stack web framework for Rust.
2//!
3//! Provides routing, database access, validation, authentication, queues,
4//! events, notifications, broadcasting, storage, caching, and Inertia.js
5//! integration in a single cohesive package.
6#![warn(missing_docs)]
7
8/// API key management and OpenAPI specification generation.
9pub mod api;
10pub mod app;
11pub mod auth;
12pub mod authorization;
13pub mod broadcast;
14pub mod cache;
15pub mod config;
16pub mod container;
17pub mod csrf;
18pub mod database;
19pub mod debug;
20pub mod error;
21pub mod hashing;
22/// HTTP request, response, cookie, and resource types.
23pub mod http;
24#[cfg(feature = "inertia")]
25pub mod inertia;
26#[cfg(feature = "json-ui")]
27pub mod json_ui;
28pub mod lang;
29pub mod metrics;
30pub mod middleware;
31/// Route definition and registration.
32pub mod routing;
33pub mod schedule;
34pub mod seeder;
35/// HTTP server builder and runner.
36pub mod server;
37pub mod session;
38pub(crate) mod static_files;
39/// Request-scoped telemetry primitives — inline-vs-preload decisioning and a
40/// process-global ring-buffer for sampled time-series telemetry.
41pub mod telemetry;
42pub mod tenant;
43pub mod testing;
44#[cfg(feature = "theme")]
45pub mod theme;
46pub mod validation;
47mod websocket;
48
49pub use api::api_key::{
50    generate_api_key, hash_api_key, verify_api_key_hash, ApiKeyInfo, ApiKeyMiddleware,
51    ApiKeyProvider, GeneratedApiKey,
52};
53pub use api::openapi::{
54    build_openapi_spec, openapi_docs_response, openapi_json_response, OpenApiConfig,
55};
56
57pub use app::Application;
58pub use auth::{
59    Auth, AuthMiddleware, AuthUser, Authenticatable, GuestMiddleware, OptionalUser, UserProvider,
60};
61pub use authorization::{AuthResponse, Authorizable, AuthorizationError, Authorize, Gate, Policy};
62pub use cache::{Cache, CacheConfig, CacheStore, InMemoryCache, RedisCache};
63pub use config::{
64    env, env_optional, env_required, AppConfig, Config, Environment, LangConfig, LangConfigBuilder,
65    ServerConfig,
66};
67pub use container::{App, Container};
68pub use csrf::{csrf_field, csrf_meta_tag, csrf_token, CsrfMiddleware};
69pub use database::{
70    AutoRouteBinding, Database, DatabaseConfig, DatabaseType, DbConnection, Model, ModelMut,
71    RouteBinding, DB,
72};
73// Re-export utoipa and utoipa-redoc for advanced OpenAPI customization
74pub use utoipa;
75pub use utoipa_redoc;
76
77// Re-export commonly used SeaORM traits for convenience
78// This saves users from having to add `use sea_orm::*` imports
79pub use error::{AppError, FrameworkError, HttpError, ValidationErrors};
80#[cfg(feature = "json-ui")]
81pub use ferro_json_ui::{
82    resolve_actions, resolve_actions_strict, resolve_errors, resolve_errors_all, Action,
83    ActionCardProps, ActionCardVariant, ActionOutcome, AlertProps, AlertVariant, AvatarProps,
84    BadgeProps, BadgeVariant, BreadcrumbItem, BreadcrumbProps, ButtonProps, ButtonType,
85    ButtonVariant, CardProps, CheckboxProps, ChecklistItem, ChecklistProps, Column, ColumnFormat,
86    ConfirmDialog, DashboardLayout, DashboardLayoutConfig, DescriptionItem, DescriptionListProps,
87    DialogVariant, Element, ElementBuilder, FormProps, HeaderProps, HttpMethod, IconPosition,
88    ImageProps, InputProps, InputType, JsonUiConfig, Layout, LayoutContext, LayoutRegistry,
89    ModalProps, NavItem, NotificationDropdownProps, NotificationItem, NotifyVariant, Orientation,
90    PaginationProps, ProgressProps, SelectOption, SelectProps, SeparatorProps, SidebarGroup,
91    SidebarNavItem, SidebarProps, SidebarSection, Size, SkeletonProps, SortDirection, Spec,
92    SpecBuilder, SpecError, StatCardProps, SwitchProps, Tab, TableProps, TabsProps, TextElement,
93    TextProps, ToastProps, ToastVariant, Visibility as JsonUiVisibility, VisibilityCondition,
94    VisibilityOperator, MAX_NESTING_DEPTH, SCHEMA_VERSION,
95};
96#[cfg(feature = "stripe")]
97pub use ferro_stripe::{
98    account, checkout, refund, verify_webhook, CheckoutBuilder, CheckoutIntent,
99    Error as StripeError, LineItem, MemoryProcessedLog, Mode, ProcessStripeWebhook,
100    ProcessedEventLog, Stripe, StripeChargeDisputeCreated, StripeChargeRefunded,
101    StripeCheckoutCompleted, StripeCheckoutExpired, StripeConfig, StripeConnectAccountUpdated,
102    StripeConnectPaymentSucceeded, StripeEvent, StripeInvoicePaid, StripePaymentIntentFailed,
103    StripeSubscriptionDeleted, StripeSubscriptionUpdated, SyncDispatcher,
104};
105#[cfg(feature = "theme")]
106pub use ferro_theme::{IntentModeTemplates, IntentSlotTemplate, Theme, ThemeError, ThemeTemplates};
107pub use hashing::{hash, needs_rehash, verify, DEFAULT_COST as HASH_DEFAULT_COST};
108pub use http::action::{
109    ActionError, ActionKind, ActionResult, ActionResultExt, FlashVariant, IntoActionError,
110};
111pub use http::{
112    bytes, json, request_host, text, validate_mime, validate_size, Cookie, CookieOptions,
113    FerroBody, FormRequest, FromParam, FromRequest, HttpResponse, InertiaRedirect, MultipartForm,
114    PaginationLinks, PaginationMeta, Redirect, Request, Resource, ResourceCollection, ResourceMap,
115    Response, ResponseExt, SameSite, SseEvent, SseStream, UploadedFile,
116};
117#[cfg(feature = "inertia")]
118pub use inertia::{Inertia, InertiaConfig, InertiaResponse, InertiaShared, SavedInertiaContext};
119#[cfg(feature = "json-ui")]
120pub use json_ui::JsonUi;
121pub use lang::{lang_choice, lang_init, locale, set_locale, t, trans, LangMiddleware};
122pub use sea_orm::{
123    ActiveModelTrait, ActiveValue, ColumnTrait, EntityTrait, IntoActiveModel, ModelTrait,
124    PaginatorTrait, QueryFilter, QueryOrder, QuerySelect,
125};
126pub use session::{
127    invalidate_all_for_user, session, session_mut, with_test_session, DatabaseSessionDriver,
128    SessionConfig, SessionData, SessionMiddleware, SessionStore,
129};
130#[cfg(feature = "stripe")]
131pub use tenant::RequiresPlan;
132pub use tenant::{
133    current_tenant, DbTenantLookup, FrameworkTenantScopeProvider, HeaderResolver, JwtClaimResolver,
134    PathResolver, SubdomainResolver, TenantContext, TenantFailureMode, TenantLookup,
135    TenantMiddleware, TenantResolver, TenantScope, TenantScoped,
136};
137#[cfg(feature = "theme")]
138pub use theme::{
139    current_theme, DefaultResolver, HeaderThemeResolver, TenantThemeResolver, ThemeMiddleware,
140    ThemeResolver,
141};
142// Deprecated - kept for backward compatibility
143#[cfg(feature = "inertia")]
144#[allow(deprecated)]
145pub use inertia::InertiaContext;
146pub use metrics::{get_metrics, MetricsSnapshot, RouteMetrics, RouteMetricsView};
147pub use middleware::{
148    get_pre_route_middleware, register_global_middleware, register_pre_route_middleware,
149    rewrite_request_path, Cors, Limit, LimiterResponse, MetricsMiddleware, Middleware,
150    MiddlewareFuture, MiddlewareRegistry, Next, PreRouteMiddleware, PreRouteResult, RateLimiter,
151    SecurityHeaders, Throttle,
152};
153pub use routing::{
154    // Internal functions used by macros (hidden from docs)
155    __box_handler,
156    __delete_impl,
157    __fallback_impl,
158    __get_impl,
159    __patch_impl,
160    __post_impl,
161    __put_impl,
162    get_registered_routes,
163    route,
164    validate_route_path,
165    FallbackDefBuilder,
166    GroupBuilder,
167    GroupDef,
168    GroupItem,
169    GroupRoute,
170    GroupRouter,
171    IntoGroupItem,
172    ResourceAction,
173    ResourceDef,
174    ResourceRoute,
175    RouteBuilder,
176    RouteDefBuilder,
177    RouteInfo,
178    Router,
179};
180pub use schedule::{CronExpression, DayOfWeek, Schedule, Task, TaskBuilder, TaskEntry, TaskResult};
181pub use seeder::{DatabaseSeeder, Seeder, SeederRegistry};
182pub use server::Server;
183pub use telemetry::{
184    inline_budget::DEFAULT_INLINE_BUDGET_THRESHOLD_BYTES, request_telemetry::RING_BUFFER_CAPACITY,
185    Decision, RequestTelemetry, Sample,
186};
187
188// Re-export ferro-events for event-driven architecture
189pub use ferro_events::{
190    dispatch as dispatch_event, dispatch_sync, Error as EventError, Event, EventDispatcher,
191    Listener, ShouldQueue,
192};
193
194/// Background job queue. Use `ferro::queue::Job`, `ferro::queue::dispatch`, etc.
195pub mod queue {
196    pub use ferro_queue::{
197        dispatch, dispatch_later, dispatch_to, register_tenant_capture_hook, CreateJobsTable,
198        Error, FailedJobInfo, Job, JobInfo, JobPayload, JobState, PendingDispatch, Queue,
199        QueueConfig, QueueStats, Queueable, SingleQueueStats, TenantScopeProvider, Worker,
200        WorkerConfig, WorkerLoop,
201    };
202}
203
204// Re-export ferro-notifications for multi-channel notifications
205pub use ferro_notifications::{
206    Channel as NotificationChannel, ChannelResult, DatabaseMessage, DatabaseNotificationStore,
207    Error as NotificationError, InAppConfig, InAppMessage, InAppSeverity, MailAttachment,
208    MailConfig, MailDriver, MailMessage, Notifiable, Notification, NotificationConfig,
209    NotificationDispatcher, PushMessage, ResendConfig, SlackAttachment, SlackField, SlackMessage,
210    SmsMessage, SmtpConfig, StoredNotification, WhatsAppMessage,
211};
212
213// Re-export ferro-broadcast for real-time WebSocket channels
214pub use ferro_broadcast::{
215    AuthData, Broadcast, BroadcastBuilder, BroadcastConfig, BroadcastMessage, Broadcaster,
216    ChannelAuthorizer, ChannelInfo, ChannelType, Client as BroadcastClient, ClientMessage,
217    Error as BroadcastError, PresenceMember, ServerMessage,
218};
219
220// Re-export broadcasting auth handler
221pub use broadcast::broadcasting_auth;
222
223// Re-export ferro-storage for file storage abstraction
224pub use ferro_storage::{
225    Disk, DiskConfig, DiskDriver, Error as StorageError, FileMetadata, LocalDriver,
226    MemoryDriver as StorageMemoryDriver, PutOptions, Storage, StorageDriver, Visibility,
227};
228
229// Re-export ferro-cache for caching with tags
230pub use ferro_cache::{
231    Cache as TaggableCache, CacheConfig as TaggableCacheConfig, CacheStore as TaggableCacheStore,
232    Error as TaggableCacheError, MemoryStore as TaggableCacheMemoryStore, TaggedCache,
233};
234
235// Re-export ferro-lang for localization
236pub use ferro_lang::{LangError, Translator};
237
238// Re-export ferro-ai for AI classification and confirmation primitives
239#[cfg(feature = "ai")]
240pub use ferro_ai::{
241    AnthropicProvider, ClassificationProvider, ClassificationResult, Classifier, ClassifierConfig,
242    ConfirmationExpired, ConfirmationStore, Error as AiError, InMemoryConfirmationStore,
243    PendingActionInfo,
244};
245
246// Re-export ferro-whatsapp for WhatsApp Business Cloud API integration
247#[cfg(feature = "whatsapp")]
248pub use ferro_whatsapp::{
249    verify_whatsapp_webhook, DeduplicationStore, DeliveryStatus, Error as WhatsAppError,
250    InMemoryDeduplicationStore, Message as WhatsAppRawMessage, ProcessWhatsAppWebhook,
251    SendResult as WhatsAppSendResult, SenderIdentity, WhatsApp, WhatsAppConfig,
252    WhatsAppStatusUpdate, WhatsAppTextReceived,
253};
254
255// Re-export ferro-projections for service projection definitions
256#[cfg(feature = "projections")]
257pub use ferro_projections::{
258    derive_intents, infer_meaning, ActionDef, BaseContext, Cardinality, DataType,
259    Error as ProjectionsError, FieldDef, FieldMeaning, GuardDef, InputDef, Intent, IntentHint,
260    IntentScore, NavigationHint, RelationshipDef, RenderHint, Renderer, ServiceDef, StateDef,
261    StateMachine, Transition, Verbosity, Warning as ProjectionsWarning,
262};
263// Re-export visual renderer types from ferro-json-ui
264#[cfg(feature = "projections")]
265pub use ferro_json_ui::{JsonUiRenderer, RenderMode, VisualContext};
266// Re-export text renderer from ferro-text
267#[cfg(feature = "projections")]
268pub use ferro_text::TextRenderer;
269
270// Re-export async_trait for middleware implementations
271pub use async_trait::async_trait;
272
273// Re-export inventory for #[service(ConcreteType)] macro
274#[doc(hidden)]
275pub use inventory;
276
277// Re-export for macro usage
278#[doc(hidden)]
279pub use serde_json;
280
281// Re-export serde for InertiaProps derive macro
282pub use serde;
283
284// Re-export validator crate for derive-based validation
285pub use validator;
286pub use validator::Validate;
287
288// Re-export our Laravel-style validation module
289pub use validation::{
290    // Rules
291    accepted,
292    alpha,
293    alpha_dash,
294    alpha_num,
295    array,
296    between,
297    boolean,
298    confirmed,
299    date,
300    different,
301    email,
302    in_array,
303    integer,
304    max,
305    min,
306    not_in,
307    nullable,
308    numeric,
309    regex,
310    // Bridge
311    register_validation_translator,
312    required,
313    required_if,
314    same,
315    string,
316    unique,
317    url,
318    validate,
319    AsyncRule,
320    AsyncValidationError,
321    AsyncValidator,
322    ConstraintMap,
323    MapConstraintExt,
324    Rule,
325    TranslatorFn,
326    Validatable,
327    ValidationError,
328    Validator,
329};
330
331// Re-export the proc-macros for compile-time component validation and type safety
332pub use ferro_macros::action;
333pub use ferro_macros::domain_error;
334pub use ferro_macros::ferro_test;
335pub use ferro_macros::handler;
336pub use ferro_macros::inertia_response;
337pub use ferro_macros::injectable;
338pub use ferro_macros::redirect;
339pub use ferro_macros::request;
340pub use ferro_macros::resource_get;
341pub use ferro_macros::resource_post;
342pub use ferro_macros::service;
343pub use ferro_macros::ApiResource;
344pub use ferro_macros::FerroModel;
345pub use ferro_macros::FormRequest as FormRequestDerive;
346pub use ferro_macros::InertiaProps;
347pub use ferro_macros::ValidateRules;
348
349// Re-export Jest-like testing macros
350pub use ferro_macros::describe;
351pub use ferro_macros::test;
352
353// Re-export testing utilities
354pub use testing::{
355    Factory, FactoryBuilder, Fake, Sequence, TestClient, TestContainer, TestContainerGuard,
356    TestDatabase, TestRequestBuilder, TestResponse,
357};
358
359/// Return a JSON response from a handler using `serde_json::json!` syntax.
360///
361/// # Example
362///
363/// ```rust,ignore
364/// use ferro_rs::json_response;
365///
366/// pub async fn index() -> Response {
367///     json_response!({ "status": "ok" })
368/// }
369/// ```
370#[macro_export]
371macro_rules! json_response {
372    ($($json:tt)+) => {
373        Ok($crate::HttpResponse::json($crate::serde_json::json!($($json)+)))
374    };
375}
376
377/// Return a plain-text response from a handler.
378///
379/// # Example
380///
381/// ```rust,ignore
382/// use ferro_rs::text_response;
383///
384/// pub async fn ping() -> Response {
385///     text_response!("pong")
386/// }
387/// ```
388#[macro_export]
389macro_rules! text_response {
390    ($text:expr) => {
391        Ok($crate::HttpResponse::text($text))
392    };
393}
394
395/// Return an HTTP error response for use in handler error arms.
396///
397/// Produces a bare `HttpResponse` value (not `Result`) suitable for use in
398/// `.map_err(|e| ferro::error_response!(500, e.to_string()))` and
399/// `.ok_or_else(|| ferro::error_response!(404, "not found"))`.
400///
401/// # Example
402///
403/// ```rust,ignore
404/// Entity::find_by_id(id).one(db).await
405///     .map_err(|e| ferro::error_response!(500, e.to_string()))?
406///     .ok_or_else(|| ferro::error_response!(404, "Not found"))?;
407/// ```
408#[macro_export]
409macro_rules! error_response {
410    ($status:expr, $msg:expr) => {
411        $crate::HttpResponse::json($crate::serde_json::json!({ "message": ($msg).to_string() }))
412            .status($status as u16)
413    };
414}
415
416/// Register global middleware that runs on every request
417///
418/// Global middleware is registered in `bootstrap.rs` and runs in registration order,
419/// before any route-specific middleware.
420///
421/// # Example
422///
423/// ```rust,ignore
424/// // In bootstrap.rs
425/// use ferro_rs::global_middleware;
426/// use ferro_rs::middleware;
427///
428/// pub fn register() {
429///     global_middleware!(middleware::LoggingMiddleware);
430///     global_middleware!(middleware::CorsMiddleware);
431/// }
432/// ```
433#[macro_export]
434macro_rules! global_middleware {
435    ($middleware:expr) => {
436        $crate::register_global_middleware($middleware)
437    };
438}
439
440/// Register a pre-route middleware that runs before path extraction and route matching.
441///
442/// Pre-route middleware operates on the raw hyper request and can rewrite the path
443/// (via `rewrite_request_path`) before the router selects a handler. Use this for
444/// host-based routing, path aliasing, or any rewrite that must influence which
445/// route is matched. Runs in registration order, before standard global middleware.
446///
447/// # Example
448///
449/// ```rust,ignore
450/// // In bootstrap.rs
451/// pre_route_middleware!(middleware::host::HostMiddleware::new());
452/// ```
453#[macro_export]
454macro_rules! pre_route_middleware {
455    ($middleware:expr) => {
456        $crate::register_pre_route_middleware($middleware)
457    };
458}
459
460/// Create an expectation for fluent assertions
461///
462/// # Example
463///
464/// ```rust,ignore
465/// use ferro_rs::expect;
466///
467/// expect!(actual).to_equal(expected);
468/// expect!(result).to_be_ok();
469/// expect!(vec).to_have_length(3);
470/// ```
471///
472/// On failure, shows clear output:
473/// ```text
474/// Test: "returns all todos"
475///   at src/actions/todo_action.rs:25
476///
477///   expect!(actual).to_equal(expected)
478///
479///   Expected: 0
480///   Received: 3
481/// ```
482#[macro_export]
483macro_rules! expect {
484    ($value:expr) => {
485        $crate::testing::Expect::new($value, concat!(file!(), ":", line!()))
486    };
487}