// Server Action Types
//
// Server Actions are secure server-side functions that can be called from the client.
// They automatically include CSRF protection and type-safe serialization.
// ============================================================================
// Action Handler Types
// ============================================================================
///|
/// Action handler context - provides request info and utilities
pub struct ActionContext {
/// Mars Context
ctx : @mars.Context
/// Raw request body (JSON string)
body : String
}
///|
/// Create ActionContext from Mars context
pub fn ActionContext::from_ctx(
ctx : @mars.Context,
body : String,
) -> ActionContext {
{ ctx, body }
}
///|
/// Get header value (case-insensitive)
pub fn ActionContext::get_header(
self : ActionContext,
name : String,
) -> String? {
self.ctx.header(name)
}
///|
/// Get query parameter
pub fn ActionContext::get_query(self : ActionContext, name : String) -> String? {
self.ctx.query(name)
}
///|
/// Get state value
pub fn ActionContext::get_state(self : ActionContext, key : String) -> String? {
self.ctx.get(key)
}
///|
/// Set state value
pub fn ActionContext::set_state(
self : ActionContext,
key : String,
value : String,
) -> Unit {
self.ctx.set(key, value)
}
// ============================================================================
// Action Result Types
// ============================================================================
///|
/// Action result - either success with data or error
pub enum ActionResult {
/// Success with JSON data
Success(@js.Any)
/// Success with redirect (returns JSON with redirect instruction for JavaScript clients)
Redirect(String)
/// HTTP redirect (returns 302 status with Location header, for non-JS form submissions)
HttpRedirect(String)
/// Client error (4xx)
ClientError(Int, String)
/// Server error (5xx)
ServerError(String)
}
///|
/// Create success result
pub fn ActionResult::ok(data : @js.Any) -> ActionResult {
Success(data)
}
///|
/// Create redirect result (returns JSON for JavaScript clients)
pub fn ActionResult::redirect(url : String) -> ActionResult {
Redirect(url)
}
///|
/// Create HTTP redirect result (returns 302 with Location header)
/// Use this for non-JavaScript form submissions (progressive enhancement)
pub fn ActionResult::http_redirect(url : String) -> ActionResult {
HttpRedirect(url)
}
///|
/// Create bad request error
pub fn ActionResult::bad_request(message : String) -> ActionResult {
ClientError(400, message)
}
///|
/// Create validation error
pub fn ActionResult::validation_error(message : String) -> ActionResult {
ClientError(422, message)
}
///|
/// Create unauthorized error
pub fn ActionResult::unauthorized(message? : String) -> ActionResult {
ClientError(401, message.unwrap_or("Unauthorized"))
}
///|
/// Create forbidden error
pub fn ActionResult::forbidden(message? : String) -> ActionResult {
ClientError(403, message.unwrap_or("Forbidden"))
}
///|
/// Create not found error
pub fn ActionResult::not_found(message? : String) -> ActionResult {
ClientError(404, message.unwrap_or("Not Found"))
}
///|
/// Create server error
pub fn ActionResult::server_error(message? : String) -> ActionResult {
ServerError(message.unwrap_or("Internal Server Error"))
}
// ============================================================================
// Action Handler
// ============================================================================
///|
/// Action handler type - async function that receives ActionContext and returns ActionResult
pub(all) struct ActionHandler(async (ActionContext) -> ActionResult raise Error)
// ============================================================================
// ActionRef (type-safe reference, recommended)
// ============================================================================
///|
/// Type-safe action reference.
/// Factory functions can be auto-generated by `sol generate` from action definitions.
///
/// Example:
/// ```moonbit nocheck
/// // Define action ref (can be auto-generated)
/// pub fn create_user() -> ActionRef {
/// { id: "create-user", base_path: "/_action" }
/// }
///
/// // Register with handler
/// registry.register_ref(@actions.create_user(), handler=handler)
///
/// // Client-side invocation
/// @action.invoke_action(@actions.create_user().url(), payload, callback)
/// ```
pub(all) struct ActionRef {
/// Action identifier
id : String
/// Base path for action endpoints (default: "/_action")
base_path : String
}
///|
/// Get the full URL for this action
pub fn ActionRef::url(self : ActionRef) -> String {
self.base_path + "/" + self.id
}
///|
/// Create an ActionDef from this ref and a handler
pub fn ActionRef::to_def(
self : ActionRef,
handler : ActionHandler,
) -> ActionDef {
{ id: self.id, handler, middlewares: [], require_json: true }
}
// ============================================================================
// Action Definition
// ============================================================================
///|
/// Server Action definition
pub struct ActionDef {
/// Action identifier (should be cryptographically secure in production)
id : String
/// Action handler
handler : ActionHandler
/// Additional middlewares for this action
middlewares : Array[@middleware.Middleware]
/// Whether to require JSON Content-Type
require_json : Bool
}
///|
/// Create action definition with default settings
pub fn ActionDef::new(id : String, handler : ActionHandler) -> ActionDef {
{ id, handler, middlewares: [], require_json: true }
}
///|
/// Add middleware to action
pub fn ActionDef::with_middleware(
self : ActionDef,
mw : @middleware.Middleware,
) -> ActionDef {
let middlewares = self.middlewares.copy()
middlewares.push(mw)
{ ..self, middlewares, }
}
///|
/// Add multiple middlewares to action
pub fn ActionDef::with_middlewares(
self : ActionDef,
mws : Array[@middleware.Middleware],
) -> ActionDef {
let middlewares = self.middlewares.copy()
for mw in mws {
middlewares.push(mw)
}
{ ..self, middlewares, }
}
///|
/// Set JSON requirement
pub fn ActionDef::with_require_json(
self : ActionDef,
require : Bool,
) -> ActionDef {
{ ..self, require_json: require }
}
// ============================================================================
// Action Registry
// ============================================================================
///|
/// Registry for server actions
pub struct ActionRegistry {
/// Action definitions by ID
actions : Map[String, ActionDef]
/// Base path for action endpoints (default: "/_action")
base_path : String
/// Allowed origins for CSRF protection
allowed_origins : Array[String]
}
///|
/// Create new action registry
pub fn ActionRegistry::new(allowed_origins? : Array[String]) -> ActionRegistry {
{
actions: {},
base_path: "/_action",
allowed_origins: allowed_origins.unwrap_or([]),
}
}
///|
/// Set base path for action endpoints
pub fn ActionRegistry::with_base_path(
self : ActionRegistry,
base_path : String,
) -> ActionRegistry {
{ ..self, base_path, }
}
///|
/// Register an action
pub fn ActionRegistry::register(
self : ActionRegistry,
action : ActionDef,
) -> ActionRegistry {
let actions = self.actions
actions[action.id] = action
{ ..self, actions, }
}
///|
/// Register an action from a type-safe ActionRef and handler
pub fn ActionRegistry::register_ref(
self : ActionRegistry,
aref : ActionRef,
handler : ActionHandler,
) -> ActionRegistry {
self.register(aref.to_def(handler))
}
///|
/// Register multiple actions
pub fn ActionRegistry::register_all(
self : ActionRegistry,
defs : Array[ActionDef],
) -> ActionRegistry {
let actions = self.actions
for def in defs {
actions[def.id] = def
}
{ ..self, actions, }
}
///|
/// Get action by ID
pub fn ActionRegistry::get(self : ActionRegistry, id : String) -> ActionDef? {
self.actions.get(id)
}
///|
/// Get all action IDs
pub fn ActionRegistry::action_ids(self : ActionRegistry) -> Array[String] {
self.actions.keys().collect()
}
///|
/// Get action endpoint URL
pub fn ActionRegistry::action_url(self : ActionRegistry, id : String) -> String {
self.base_path + "/" + id
}