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    register_layout, resolve_actions, resolve_actions_strict, resolve_errors, resolve_errors_all,
87    Action, ActionCardProps, ActionOutcome, AlertProps, AvatarProps, BadgeProps, BreadcrumbItem,
88    BreadcrumbProps, ButtonProps, ButtonType, CardAppearance, CardProps, CheckboxProps,
89    ChecklistItem, ChecklistProps, Column, ColumnFormat, ConfirmDialog, DashboardLayout,
90    DashboardLayoutConfig, DescriptionItem, DescriptionListProps, Element, ElementBuilder,
91    FormProps, HeaderProps, HttpMethod, IconPosition, ImageProps, InputProps, InputType,
92    JsonUiConfig, Layout, LayoutContext, LayoutRegistry, ModalProps, NavItem,
93    NotificationDropdownProps, NotificationItem, Orientation, PaginationProps, ProgressProps,
94    SelectOption, SelectProps, SeparatorProps, SidebarGroup, SidebarNavItem, SidebarProps,
95    SidebarSection, Size, SkeletonProps, SortDirection, Spec, SpecBuilder, SpecError,
96    StatCardProps, SwitchProps, Tab, TableProps, TabsProps, TextElement, TextProps, ToastProps,
97    Tone, Variant, Visibility as JsonUiVisibility, VisibilityCondition, VisibilityOperator,
98    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::{
270    default_template, register_template, JsonUiRenderer, RenderMode, VisualContext,
271};
272// Re-export text renderer from ferro-text
273#[cfg(feature = "projections")]
274pub use ferro_text::TextRenderer;
275// Re-export the shared transition-execution kernel surface
276#[cfg(feature = "projections")]
277pub use write::{
278    dispatch_write, ExecutorFn, GuardEvaluatorFn, OverrideFn, WriteDispatcher, WriteError,
279    WriteResult,
280};
281
282// Re-export async_trait for middleware implementations
283pub use async_trait::async_trait;
284
285// Re-export inventory for #[service(ConcreteType)] macro
286#[doc(hidden)]
287pub use inventory;
288
289// Re-export for macro usage
290#[doc(hidden)]
291pub use serde_json;
292
293// Re-export serde for InertiaProps derive macro
294pub use serde;
295
296// Re-export validator crate for derive-based validation
297pub use validator;
298pub use validator::Validate;
299
300// Re-export our Laravel-style validation module
301pub use validation::{
302    // Rules
303    accepted,
304    alpha,
305    alpha_dash,
306    alpha_num,
307    array,
308    between,
309    boolean,
310    confirmed,
311    date,
312    different,
313    email,
314    in_array,
315    integer,
316    max,
317    min,
318    not_in,
319    nullable,
320    numeric,
321    regex,
322    // Bridge
323    register_validation_translator,
324    required,
325    required_if,
326    same,
327    string,
328    unique,
329    url,
330    validate,
331    AsyncRule,
332    AsyncValidationError,
333    AsyncValidator,
334    ConstraintMap,
335    MapConstraintExt,
336    Rule,
337    TranslatorFn,
338    Validatable,
339    ValidationError,
340    Validator,
341};
342
343// Re-export the proc-macros for compile-time component validation and type safety
344pub use ferro_macros::action;
345pub use ferro_macros::domain_error;
346pub use ferro_macros::ferro_test;
347pub use ferro_macros::handler;
348pub use ferro_macros::inertia_response;
349pub use ferro_macros::injectable;
350pub use ferro_macros::redirect;
351pub use ferro_macros::request;
352pub use ferro_macros::resource_get;
353pub use ferro_macros::resource_post;
354pub use ferro_macros::service;
355pub use ferro_macros::ApiResource;
356pub use ferro_macros::FerroModel;
357pub use ferro_macros::FormRequest as FormRequestDerive;
358pub use ferro_macros::InertiaProps;
359pub use ferro_macros::ValidateRules;
360
361// Re-export Jest-like testing macros
362pub use ferro_macros::describe;
363pub use ferro_macros::test;
364
365// Re-export testing utilities
366pub use testing::{
367    Factory, FactoryBuilder, Fake, Sequence, TestClient, TestContainer, TestContainerGuard,
368    TestDatabase, TestRequestBuilder, TestResponse,
369};
370
371/// Return a JSON response from a handler using `serde_json::json!` syntax.
372///
373/// # Example
374///
375/// ```rust,ignore
376/// use ferro_rs::json_response;
377///
378/// pub async fn index() -> Response {
379///     json_response!({ "status": "ok" })
380/// }
381/// ```
382#[macro_export]
383macro_rules! json_response {
384    ($($json:tt)+) => {
385        Ok($crate::HttpResponse::json($crate::serde_json::json!($($json)+)))
386    };
387}
388
389/// Return a plain-text response from a handler.
390///
391/// # Example
392///
393/// ```rust,ignore
394/// use ferro_rs::text_response;
395///
396/// pub async fn ping() -> Response {
397///     text_response!("pong")
398/// }
399/// ```
400#[macro_export]
401macro_rules! text_response {
402    ($text:expr) => {
403        Ok($crate::HttpResponse::text($text))
404    };
405}
406
407/// Return an HTTP error response for use in handler error arms.
408///
409/// Produces a bare `HttpResponse` value (not `Result`) suitable for use in
410/// `.map_err(|e| ferro::error_response!(500, e.to_string()))` and
411/// `.ok_or_else(|| ferro::error_response!(404, "not found"))`.
412///
413/// # Example
414///
415/// ```rust,ignore
416/// Entity::find_by_id(id).one(db).await
417///     .map_err(|e| ferro::error_response!(500, e.to_string()))?
418///     .ok_or_else(|| ferro::error_response!(404, "Not found"))?;
419/// ```
420#[macro_export]
421macro_rules! error_response {
422    ($status:expr, $msg:expr) => {
423        $crate::HttpResponse::json($crate::serde_json::json!({ "message": ($msg).to_string() }))
424            .status($status as u16)
425    };
426}
427
428/// Register global middleware that runs on every request
429///
430/// Global middleware is registered in `bootstrap.rs` and runs in registration order,
431/// before any route-specific middleware.
432///
433/// # Example
434///
435/// ```rust,ignore
436/// // In bootstrap.rs
437/// use ferro_rs::global_middleware;
438/// use ferro_rs::middleware;
439///
440/// pub fn register() {
441///     global_middleware!(middleware::LoggingMiddleware);
442///     global_middleware!(middleware::CorsMiddleware);
443/// }
444/// ```
445#[macro_export]
446macro_rules! global_middleware {
447    ($middleware:expr) => {
448        $crate::register_global_middleware($middleware)
449    };
450}
451
452/// Register a pre-route middleware that runs before path extraction and route matching.
453///
454/// Pre-route middleware operates on the raw hyper request and can rewrite the path
455/// (via `rewrite_request_path`) before the router selects a handler. Use this for
456/// host-based routing, path aliasing, or any rewrite that must influence which
457/// route is matched. Runs in registration order, before standard global middleware.
458///
459/// # Example
460///
461/// ```rust,ignore
462/// // In bootstrap.rs
463/// pre_route_middleware!(middleware::host::HostMiddleware::new());
464/// ```
465#[macro_export]
466macro_rules! pre_route_middleware {
467    ($middleware:expr) => {
468        $crate::register_pre_route_middleware($middleware)
469    };
470}
471
472/// Create an expectation for fluent assertions
473///
474/// # Example
475///
476/// ```rust,ignore
477/// use ferro_rs::expect;
478///
479/// expect!(actual).to_equal(expected);
480/// expect!(result).to_be_ok();
481/// expect!(vec).to_have_length(3);
482/// ```
483///
484/// On failure, shows clear output:
485/// ```text
486/// Test: "returns all todos"
487///   at src/actions/todo_action.rs:25
488///
489///   expect!(actual).to_equal(expected)
490///
491///   Expected: 0
492///   Received: 3
493/// ```
494#[macro_export]
495macro_rules! expect {
496    ($value:expr) => {
497        $crate::testing::Expect::new($value, concat!(file!(), ":", line!()))
498    };
499}