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, ColumnTrait, EntityTrait, IntoActiveModel, ModelTrait, PaginatorTrait,
124    QueryFilter, QueryOrder, QuerySelect,
125};
126pub use session::{
127    invalidate_all_for_user, session, session_mut, DatabaseSessionDriver, SessionConfig,
128    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,
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, Cardinality, DataType, Error as ProjectionsError,
259    FieldDef, FieldMeaning, GuardDef, InputDef, Intent, IntentHint, IntentScore, NavigationHint,
260    RelationshipDef, Renderer, ServiceDef, StateDef, StateMachine, Transition,
261    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
267// Re-export async_trait for middleware implementations
268pub use async_trait::async_trait;
269
270// Re-export inventory for #[service(ConcreteType)] macro
271#[doc(hidden)]
272pub use inventory;
273
274// Re-export for macro usage
275#[doc(hidden)]
276pub use serde_json;
277
278// Re-export serde for InertiaProps derive macro
279pub use serde;
280
281// Re-export validator crate for derive-based validation
282pub use validator;
283pub use validator::Validate;
284
285// Re-export our Laravel-style validation module
286pub use validation::{
287    // Rules
288    accepted,
289    alpha,
290    alpha_dash,
291    alpha_num,
292    array,
293    between,
294    boolean,
295    confirmed,
296    date,
297    different,
298    email,
299    in_array,
300    integer,
301    max,
302    min,
303    not_in,
304    nullable,
305    numeric,
306    regex,
307    // Bridge
308    register_validation_translator,
309    required,
310    required_if,
311    same,
312    string,
313    unique,
314    url,
315    validate,
316    AsyncRule,
317    AsyncValidationError,
318    AsyncValidator,
319    ConstraintMap,
320    MapConstraintExt,
321    Rule,
322    TranslatorFn,
323    Validatable,
324    ValidationError,
325    Validator,
326};
327
328// Re-export the proc-macros for compile-time component validation and type safety
329pub use ferro_macros::action;
330pub use ferro_macros::domain_error;
331pub use ferro_macros::ferro_test;
332pub use ferro_macros::handler;
333pub use ferro_macros::inertia_response;
334pub use ferro_macros::injectable;
335pub use ferro_macros::redirect;
336pub use ferro_macros::request;
337pub use ferro_macros::service;
338pub use ferro_macros::ApiResource;
339pub use ferro_macros::FerroModel;
340pub use ferro_macros::FormRequest as FormRequestDerive;
341pub use ferro_macros::InertiaProps;
342pub use ferro_macros::ValidateRules;
343
344// Re-export Jest-like testing macros
345pub use ferro_macros::describe;
346pub use ferro_macros::test;
347
348// Re-export testing utilities
349pub use testing::{
350    Factory, FactoryBuilder, Fake, Sequence, TestClient, TestContainer, TestContainerGuard,
351    TestDatabase, TestRequestBuilder, TestResponse,
352};
353
354/// Return a JSON response from a handler using `serde_json::json!` syntax.
355///
356/// # Example
357///
358/// ```rust,ignore
359/// use ferro_rs::json_response;
360///
361/// pub async fn index() -> Response {
362///     json_response!({ "status": "ok" })
363/// }
364/// ```
365#[macro_export]
366macro_rules! json_response {
367    ($($json:tt)+) => {
368        Ok($crate::HttpResponse::json($crate::serde_json::json!($($json)+)))
369    };
370}
371
372/// Return a plain-text response from a handler.
373///
374/// # Example
375///
376/// ```rust,ignore
377/// use ferro_rs::text_response;
378///
379/// pub async fn ping() -> Response {
380///     text_response!("pong")
381/// }
382/// ```
383#[macro_export]
384macro_rules! text_response {
385    ($text:expr) => {
386        Ok($crate::HttpResponse::text($text))
387    };
388}
389
390/// Register global middleware that runs on every request
391///
392/// Global middleware is registered in `bootstrap.rs` and runs in registration order,
393/// before any route-specific middleware.
394///
395/// # Example
396///
397/// ```rust,ignore
398/// // In bootstrap.rs
399/// use ferro_rs::global_middleware;
400/// use ferro_rs::middleware;
401///
402/// pub fn register() {
403///     global_middleware!(middleware::LoggingMiddleware);
404///     global_middleware!(middleware::CorsMiddleware);
405/// }
406/// ```
407#[macro_export]
408macro_rules! global_middleware {
409    ($middleware:expr) => {
410        $crate::register_global_middleware($middleware)
411    };
412}
413
414/// Register a pre-route middleware that runs before path extraction and route matching.
415///
416/// Pre-route middleware operates on the raw hyper request and can rewrite the path
417/// (via `rewrite_request_path`) before the router selects a handler. Use this for
418/// host-based routing, path aliasing, or any rewrite that must influence which
419/// route is matched. Runs in registration order, before standard global middleware.
420///
421/// # Example
422///
423/// ```rust,ignore
424/// // In bootstrap.rs
425/// pre_route_middleware!(middleware::host::HostMiddleware::new());
426/// ```
427#[macro_export]
428macro_rules! pre_route_middleware {
429    ($middleware:expr) => {
430        $crate::register_pre_route_middleware($middleware)
431    };
432}
433
434/// Create an expectation for fluent assertions
435///
436/// # Example
437///
438/// ```rust,ignore
439/// use ferro_rs::expect;
440///
441/// expect!(actual).to_equal(expected);
442/// expect!(result).to_be_ok();
443/// expect!(vec).to_have_length(3);
444/// ```
445///
446/// On failure, shows clear output:
447/// ```text
448/// Test: "returns all todos"
449///   at src/actions/todo_action.rs:25
450///
451///   expect!(actual).to_equal(expected)
452///
453///   Expected: 0
454///   Received: 3
455/// ```
456#[macro_export]
457macro_rules! expect {
458    ($value:expr) => {
459        $crate::testing::Expect::new($value, concat!(file!(), ":", line!()))
460    };
461}