forge_core/function/
traits.rs

1use std::future::Future;
2use std::pin::Pin;
3
4use serde::{Serialize, de::DeserializeOwned};
5
6use super::context::{ActionContext, MutationContext, QueryContext};
7use crate::error::Result;
8
9/// Information about a registered function.
10#[derive(Debug, Clone)]
11pub struct FunctionInfo {
12    /// Function name (used for routing).
13    pub name: &'static str,
14    /// Human-readable description.
15    pub description: Option<&'static str>,
16    /// Kind of function.
17    pub kind: FunctionKind,
18    /// Whether authentication is required.
19    pub requires_auth: bool,
20    /// Required role (if any).
21    pub required_role: Option<&'static str>,
22    /// Whether this function is public (no auth).
23    pub is_public: bool,
24    /// Cache TTL in seconds (for queries).
25    pub cache_ttl: Option<u64>,
26    /// Timeout in seconds.
27    pub timeout: Option<u64>,
28    /// Rate limit: requests per time window.
29    pub rate_limit_requests: Option<u32>,
30    /// Rate limit: time window in seconds.
31    pub rate_limit_per_secs: Option<u64>,
32    /// Rate limit: bucket key type (user, ip, tenant, global).
33    pub rate_limit_key: Option<&'static str>,
34    /// Log level for access logging: "trace", "debug", "info", "warn", "error", "off".
35    /// Defaults to "trace" if not specified.
36    pub log_level: Option<&'static str>,
37}
38
39/// The kind of function.
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum FunctionKind {
42    Query,
43    Mutation,
44    Action,
45}
46
47impl std::fmt::Display for FunctionKind {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        match self {
50            FunctionKind::Query => write!(f, "query"),
51            FunctionKind::Mutation => write!(f, "mutation"),
52            FunctionKind::Action => write!(f, "action"),
53        }
54    }
55}
56
57/// A query function (read-only, cacheable, subscribable).
58///
59/// Queries:
60/// - Can only read from the database
61/// - Are automatically cached based on arguments
62/// - Can be subscribed to for real-time updates
63/// - Should be deterministic (same inputs → same outputs)
64/// - Should not have side effects
65pub trait ForgeQuery: Send + Sync + 'static {
66    /// The input arguments type.
67    type Args: DeserializeOwned + Serialize + Send + Sync;
68    /// The output type.
69    type Output: Serialize + Send;
70
71    /// Function metadata.
72    fn info() -> FunctionInfo;
73
74    /// Execute the query.
75    fn execute(
76        ctx: &QueryContext,
77        args: Self::Args,
78    ) -> Pin<Box<dyn Future<Output = Result<Self::Output>> + Send + '_>>;
79}
80
81/// A mutation function (transactional write).
82///
83/// Mutations:
84/// - Run within a database transaction
85/// - Can read and write to the database
86/// - Should NOT call external APIs (use Actions)
87/// - Are atomic: all changes commit or none do
88pub trait ForgeMutation: Send + Sync + 'static {
89    /// The input arguments type.
90    type Args: DeserializeOwned + Serialize + Send + Sync;
91    /// The output type.
92    type Output: Serialize + Send;
93
94    /// Function metadata.
95    fn info() -> FunctionInfo;
96
97    /// Execute the mutation within a transaction.
98    fn execute(
99        ctx: &MutationContext,
100        args: Self::Args,
101    ) -> Pin<Box<dyn Future<Output = Result<Self::Output>> + Send + '_>>;
102}
103
104/// An action function (side effects, external APIs).
105///
106/// Actions:
107/// - Can call external APIs
108/// - Are NOT transactional by default
109/// - Can call queries and mutations
110/// - May be slow (external network calls)
111/// - Can have timeouts and retries
112pub trait ForgeAction: Send + Sync + 'static {
113    /// The input arguments type.
114    type Args: DeserializeOwned + Serialize + Send + Sync;
115    /// The output type.
116    type Output: Serialize + Send;
117
118    /// Function metadata.
119    fn info() -> FunctionInfo;
120
121    /// Execute the action.
122    fn execute(
123        ctx: &ActionContext,
124        args: Self::Args,
125    ) -> Pin<Box<dyn Future<Output = Result<Self::Output>> + Send + '_>>;
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131
132    #[test]
133    fn test_function_kind_display() {
134        assert_eq!(format!("{}", FunctionKind::Query), "query");
135        assert_eq!(format!("{}", FunctionKind::Mutation), "mutation");
136        assert_eq!(format!("{}", FunctionKind::Action), "action");
137    }
138
139    #[test]
140    fn test_function_info() {
141        let info = FunctionInfo {
142            name: "get_user",
143            description: Some("Get a user by ID"),
144            kind: FunctionKind::Query,
145            requires_auth: true,
146            required_role: None,
147            is_public: false,
148            cache_ttl: Some(300),
149            timeout: Some(30),
150            rate_limit_requests: Some(100),
151            rate_limit_per_secs: Some(60),
152            rate_limit_key: Some("user"),
153            log_level: Some("debug"),
154        };
155
156        assert_eq!(info.name, "get_user");
157        assert_eq!(info.kind, FunctionKind::Query);
158        assert!(info.requires_auth);
159        assert_eq!(info.cache_ttl, Some(300));
160        assert_eq!(info.rate_limit_requests, Some(100));
161        assert_eq!(info.log_level, Some("debug"));
162    }
163}