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