1use std::future::Future;
2use std::pin::Pin;
3use std::time::Duration;
4
5use serde::{Serialize, de::DeserializeOwned};
6
7use super::context::{MutationContext, QueryContext};
8use crate::error::Result;
9
10#[derive(Debug, Clone)]
12pub struct FunctionInfo {
13 pub name: &'static str,
14 pub description: Option<&'static str>,
15 pub kind: FunctionKind,
16 pub required_role: Option<&'static str>,
17 pub is_public: bool,
18 pub cache_ttl: Option<u64>,
19 pub timeout: Option<Duration>,
21 pub http_timeout: Option<Duration>,
23 pub rate_limit_requests: Option<u32>,
24 pub rate_limit_per_secs: Option<u64>,
25 pub rate_limit_key: Option<crate::rate_limit::RateLimitKey>,
26 pub log_level: Option<LogLevel>,
27 pub table_dependencies: &'static [&'static str],
29 pub selected_columns: &'static [&'static str],
31 pub changed_columns: &'static [&'static str],
33 pub transactional: bool,
35 pub consistent: bool,
37 pub max_upload_size_bytes: Option<usize>,
38 pub requires_tenant_scope: bool,
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43#[non_exhaustive]
44pub enum FunctionKind {
45 Query,
46 Mutation,
47 Webhook,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51#[non_exhaustive]
52pub enum LogLevel {
53 Trace,
54 Debug,
55 Info,
56 Warn,
57 Error,
58 Off,
59}
60
61impl LogLevel {
62 pub fn as_str(&self) -> &'static str {
63 match self {
64 Self::Trace => "trace",
65 Self::Debug => "debug",
66 Self::Info => "info",
67 Self::Warn => "warn",
68 Self::Error => "error",
69 Self::Off => "off",
70 }
71 }
72}
73
74impl FunctionKind {
75 pub fn as_str(&self) -> &'static str {
76 match self {
77 Self::Query => "query",
78 Self::Mutation => "mutation",
79 Self::Webhook => "webhook",
80 }
81 }
82}
83
84impl std::fmt::Display for FunctionKind {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 f.write_str(self.as_str())
87 }
88}
89
90pub trait ForgeQuery: crate::__sealed::Sealed + Send + Sync + 'static {
92 type Args: DeserializeOwned + Serialize + Send + Sync;
93 type Output: Serialize + Send;
94
95 fn info() -> FunctionInfo;
96
97 fn execute(
98 ctx: &QueryContext,
99 args: Self::Args,
100 ) -> Pin<Box<dyn Future<Output = Result<Self::Output>> + Send + '_>>;
101}
102
103pub trait ForgeMutation: crate::__sealed::Sealed + Send + Sync + 'static {
105 type Args: DeserializeOwned + Serialize + Send + Sync;
106 type Output: Serialize + Send;
107
108 fn info() -> FunctionInfo;
109
110 fn execute(
111 ctx: &MutationContext,
112 args: Self::Args,
113 ) -> Pin<Box<dyn Future<Output = Result<Self::Output>> + Send + '_>>;
114}
115
116#[cfg(test)]
117#[allow(clippy::unwrap_used, clippy::indexing_slicing)]
118mod tests {
119 use super::*;
120
121 #[test]
122 fn test_function_kind_display() {
123 assert_eq!(format!("{}", FunctionKind::Query), "query");
124 assert_eq!(format!("{}", FunctionKind::Mutation), "mutation");
125 assert_eq!(format!("{}", FunctionKind::Webhook), "webhook");
126 }
127
128 #[test]
129 fn test_function_info() {
130 let info = FunctionInfo {
131 name: "get_user",
132 description: Some("Get a user by ID"),
133 kind: FunctionKind::Query,
134 required_role: None,
135 is_public: false,
136 cache_ttl: Some(300),
137 timeout: Some(Duration::from_secs(30)),
138 http_timeout: Some(Duration::from_secs(5)),
139 rate_limit_requests: Some(100),
140 rate_limit_per_secs: Some(60),
141 rate_limit_key: Some(crate::rate_limit::RateLimitKey::User),
142 log_level: Some(LogLevel::Debug),
143 table_dependencies: &["users"],
144 selected_columns: &["id", "name", "email"],
145 changed_columns: &[],
146 transactional: false,
147 consistent: false,
148 max_upload_size_bytes: None,
149 requires_tenant_scope: false,
150 };
151
152 assert_eq!(info.name, "get_user");
153 assert_eq!(info.kind, FunctionKind::Query);
154 assert_eq!(info.cache_ttl, Some(300));
155 assert_eq!(info.http_timeout, Some(Duration::from_secs(5)));
156 assert_eq!(info.rate_limit_requests, Some(100));
157 assert_eq!(info.log_level, Some(LogLevel::Debug));
158 assert_eq!(info.table_dependencies, &["users"]);
159 }
160}