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}
35
36/// The kind of function.
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum FunctionKind {
39    Query,
40    Mutation,
41    Action,
42}
43
44impl std::fmt::Display for FunctionKind {
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        match self {
47            FunctionKind::Query => write!(f, "query"),
48            FunctionKind::Mutation => write!(f, "mutation"),
49            FunctionKind::Action => write!(f, "action"),
50        }
51    }
52}
53
54/// A query function (read-only, cacheable, subscribable).
55///
56/// Queries:
57/// - Can only read from the database
58/// - Are automatically cached based on arguments
59/// - Can be subscribed to for real-time updates
60/// - Should be deterministic (same inputs → same outputs)
61/// - Should not have side effects
62pub trait ForgeQuery: Send + Sync + 'static {
63    /// The input arguments type.
64    type Args: DeserializeOwned + Serialize + Send + Sync;
65    /// The output type.
66    type Output: Serialize + Send;
67
68    /// Function metadata.
69    fn info() -> FunctionInfo;
70
71    /// Execute the query.
72    fn execute(
73        ctx: &QueryContext,
74        args: Self::Args,
75    ) -> Pin<Box<dyn Future<Output = Result<Self::Output>> + Send + '_>>;
76}
77
78/// A mutation function (transactional write).
79///
80/// Mutations:
81/// - Run within a database transaction
82/// - Can read and write to the database
83/// - Should NOT call external APIs (use Actions)
84/// - Are atomic: all changes commit or none do
85pub trait ForgeMutation: Send + Sync + 'static {
86    /// The input arguments type.
87    type Args: DeserializeOwned + Serialize + Send + Sync;
88    /// The output type.
89    type Output: Serialize + Send;
90
91    /// Function metadata.
92    fn info() -> FunctionInfo;
93
94    /// Execute the mutation within a transaction.
95    fn execute(
96        ctx: &MutationContext,
97        args: Self::Args,
98    ) -> Pin<Box<dyn Future<Output = Result<Self::Output>> + Send + '_>>;
99}
100
101/// An action function (side effects, external APIs).
102///
103/// Actions:
104/// - Can call external APIs
105/// - Are NOT transactional by default
106/// - Can call queries and mutations
107/// - May be slow (external network calls)
108/// - Can have timeouts and retries
109pub trait ForgeAction: Send + Sync + 'static {
110    /// The input arguments type.
111    type Args: DeserializeOwned + Serialize + Send + Sync;
112    /// The output type.
113    type Output: Serialize + Send;
114
115    /// Function metadata.
116    fn info() -> FunctionInfo;
117
118    /// Execute the action.
119    fn execute(
120        ctx: &ActionContext,
121        args: Self::Args,
122    ) -> Pin<Box<dyn Future<Output = Result<Self::Output>> + Send + '_>>;
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    #[test]
130    fn test_function_kind_display() {
131        assert_eq!(format!("{}", FunctionKind::Query), "query");
132        assert_eq!(format!("{}", FunctionKind::Mutation), "mutation");
133        assert_eq!(format!("{}", FunctionKind::Action), "action");
134    }
135
136    #[test]
137    fn test_function_info() {
138        let info = FunctionInfo {
139            name: "get_user",
140            description: Some("Get a user by ID"),
141            kind: FunctionKind::Query,
142            requires_auth: true,
143            required_role: None,
144            is_public: false,
145            cache_ttl: Some(300),
146            timeout: Some(30),
147            rate_limit_requests: Some(100),
148            rate_limit_per_secs: Some(60),
149            rate_limit_key: Some("user"),
150        };
151
152        assert_eq!(info.name, "get_user");
153        assert_eq!(info.kind, FunctionKind::Query);
154        assert!(info.requires_auth);
155        assert_eq!(info.cache_ttl, Some(300));
156        assert_eq!(info.rate_limit_requests, Some(100));
157    }
158}