1pub mod config;
41pub mod engine;
42pub mod introspector;
43pub mod sql_generator;
44pub mod dataloader;
45pub mod resolver;
46pub mod validation;
47pub mod metrics;
48
49pub use config::{GraphQLConfig, GraphQLConfigBuilder, TableConfig, RelationshipConfig};
50pub use engine::{GraphQLEngine, GraphQLRequest, GraphQLResponse, GraphQLError};
51pub use introspector::{SchemaIntrospector, GraphQLSchema, GraphQLType, GraphQLField};
52pub use sql_generator::{SqlGenerator, SqlQuery, QueryPlan, Selection, Filter};
53pub use dataloader::{DataLoader, DataLoaderConfig, BatchResult};
54pub use resolver::{FieldResolver, ResolverContext, ResolverResult};
55pub use validation::{QueryValidator, ValidationError, ComplexityResult};
56pub use metrics::{GraphQLMetrics, QueryStats, OperationMetrics};
57
58use std::collections::HashMap;
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
62pub enum OperationType {
63 Query,
65 Mutation,
67 Subscription,
69}
70
71impl std::fmt::Display for OperationType {
72 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 match self {
74 OperationType::Query => write!(f, "query"),
75 OperationType::Mutation => write!(f, "mutation"),
76 OperationType::Subscription => write!(f, "subscription"),
77 }
78 }
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
83pub enum RelationType {
84 OneToOne,
86 OneToMany,
88 ManyToOne,
90 ManyToMany,
92}
93
94impl RelationType {
95 pub fn from_str(s: &str) -> Option<Self> {
97 match s.to_lowercase().as_str() {
98 "one_to_one" | "onetoone" | "1:1" => Some(RelationType::OneToOne),
99 "one_to_many" | "onetomany" | "1:n" => Some(RelationType::OneToMany),
100 "many_to_one" | "manytoone" | "n:1" => Some(RelationType::ManyToOne),
101 "many_to_many" | "manytomany" | "n:n" => Some(RelationType::ManyToMany),
102 _ => None,
103 }
104 }
105
106 pub fn is_list(&self) -> bool {
108 matches!(self, RelationType::OneToMany | RelationType::ManyToMany)
109 }
110}
111
112#[derive(Debug, Clone, PartialEq, Eq, Hash)]
114pub enum GraphQLScalar {
115 ID,
117 String,
119 Int,
121 Float,
123 Boolean,
125 DateTime,
127 Date,
129 Time,
131 JSON,
133 Decimal,
135 BigInt,
137 Custom(String),
139}
140
141impl GraphQLScalar {
142 pub fn from_sql_type(sql_type: &str) -> Self {
144 let lower = sql_type.to_lowercase();
145
146 if lower.contains("serial") || lower == "uuid" {
147 GraphQLScalar::ID
148 } else if lower.contains("int") || lower == "smallint" {
149 if lower.contains("big") {
150 GraphQLScalar::BigInt
151 } else {
152 GraphQLScalar::Int
153 }
154 } else if lower.contains("float") || lower.contains("double") || lower == "real" {
155 GraphQLScalar::Float
156 } else if lower.contains("numeric") || lower.contains("decimal") {
157 GraphQLScalar::Decimal
158 } else if lower == "boolean" || lower == "bool" {
159 GraphQLScalar::Boolean
160 } else if lower.contains("timestamp") || lower == "datetime" {
161 GraphQLScalar::DateTime
162 } else if lower == "date" {
163 GraphQLScalar::Date
164 } else if lower == "time" {
165 GraphQLScalar::Time
166 } else if lower == "json" || lower == "jsonb" {
167 GraphQLScalar::JSON
168 } else {
169 GraphQLScalar::String
170 }
171 }
172
173 pub fn to_sdl(&self) -> &str {
175 match self {
176 GraphQLScalar::ID => "ID",
177 GraphQLScalar::String => "String",
178 GraphQLScalar::Int => "Int",
179 GraphQLScalar::Float => "Float",
180 GraphQLScalar::Boolean => "Boolean",
181 GraphQLScalar::DateTime => "DateTime",
182 GraphQLScalar::Date => "Date",
183 GraphQLScalar::Time => "Time",
184 GraphQLScalar::JSON => "JSON",
185 GraphQLScalar::Decimal => "Decimal",
186 GraphQLScalar::BigInt => "BigInt",
187 GraphQLScalar::Custom(name) => name,
188 }
189 }
190}
191
192#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
194pub enum ConsistencyLevel {
195 Strong,
197 #[default]
199 Eventual,
200 Bounded,
202}
203
204#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
206pub enum DistanceMetric {
207 #[default]
209 Cosine,
210 Euclidean,
212 DotProduct,
214}
215
216#[derive(Debug, Clone)]
218pub struct BranchContext {
219 pub name: String,
221 pub as_of: Option<std::time::SystemTime>,
223}
224
225impl Default for BranchContext {
226 fn default() -> Self {
227 Self {
228 name: "main".to_string(),
229 as_of: None,
230 }
231 }
232}
233
234#[derive(Debug, Clone)]
236pub struct ExecutionContext {
237 pub user_id: Option<String>,
239 pub roles: Vec<String>,
241 pub branch: BranchContext,
243 pub consistency: ConsistencyLevel,
245 pub headers: HashMap<String, String>,
247 pub metadata: HashMap<String, String>,
249}
250
251impl Default for ExecutionContext {
252 fn default() -> Self {
253 Self {
254 user_id: None,
255 roles: Vec::new(),
256 branch: BranchContext::default(),
257 consistency: ConsistencyLevel::default(),
258 headers: HashMap::new(),
259 metadata: HashMap::new(),
260 }
261 }
262}
263
264impl ExecutionContext {
265 pub fn new() -> Self {
267 Self::default()
268 }
269
270 pub fn with_user(mut self, user_id: impl Into<String>) -> Self {
272 self.user_id = Some(user_id.into());
273 self
274 }
275
276 pub fn with_role(mut self, role: impl Into<String>) -> Self {
278 self.roles.push(role.into());
279 self
280 }
281
282 pub fn with_branch(mut self, branch: impl Into<String>) -> Self {
284 self.branch.name = branch.into();
285 self
286 }
287
288 pub fn with_as_of(mut self, timestamp: std::time::SystemTime) -> Self {
290 self.branch.as_of = Some(timestamp);
291 self
292 }
293
294 pub fn with_consistency(mut self, level: ConsistencyLevel) -> Self {
296 self.consistency = level;
297 self
298 }
299
300 pub fn has_role(&self, role: &str) -> bool {
302 self.roles.iter().any(|r| r == role)
303 }
304
305 pub fn is_authenticated(&self) -> bool {
307 self.user_id.is_some()
308 }
309}
310
311#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
313pub enum ErrorCode {
314 ParseError,
316 ValidationError,
318 Unauthorized,
320 Forbidden,
322 NotFound,
324 InternalError,
326 QueryTooComplex,
328 RateLimited,
330 Timeout,
332}
333
334impl ErrorCode {
335 pub fn http_status(&self) -> u16 {
337 match self {
338 ErrorCode::ParseError | ErrorCode::ValidationError => 400,
339 ErrorCode::Unauthorized => 401,
340 ErrorCode::Forbidden => 403,
341 ErrorCode::NotFound => 404,
342 ErrorCode::QueryTooComplex | ErrorCode::RateLimited => 429,
343 ErrorCode::Timeout => 408,
344 ErrorCode::InternalError => 500,
345 }
346 }
347}
348
349pub fn to_pascal_case(s: &str) -> String {
351 s.split('_')
352 .filter(|part| !part.is_empty())
353 .map(|part| {
354 let mut chars = part.chars();
355 match chars.next() {
356 None => String::new(),
357 Some(first) => first.to_uppercase().chain(chars).collect(),
358 }
359 })
360 .collect()
361}
362
363pub fn to_camel_case(s: &str) -> String {
365 let pascal = to_pascal_case(s);
366 let mut chars = pascal.chars();
367 match chars.next() {
368 None => String::new(),
369 Some(first) => first.to_lowercase().chain(chars).collect(),
370 }
371}
372
373pub fn to_snake_case(s: &str) -> String {
375 let mut result = String::with_capacity(s.len() + 4);
376 let mut prev_was_upper = false;
377
378 for (i, c) in s.chars().enumerate() {
379 if c.is_uppercase() {
380 if i > 0 && !prev_was_upper {
381 result.push('_');
382 }
383 result.push(c.to_lowercase().next().unwrap());
384 prev_was_upper = true;
385 } else {
386 result.push(c);
387 prev_was_upper = false;
388 }
389 }
390
391 result
392}
393
394#[cfg(test)]
395mod tests {
396 use super::*;
397
398 #[test]
399 fn test_relation_type_from_str() {
400 assert_eq!(RelationType::from_str("one_to_one"), Some(RelationType::OneToOne));
401 assert_eq!(RelationType::from_str("1:n"), Some(RelationType::OneToMany));
402 assert_eq!(RelationType::from_str("n:1"), Some(RelationType::ManyToOne));
403 assert_eq!(RelationType::from_str("many_to_many"), Some(RelationType::ManyToMany));
404 assert_eq!(RelationType::from_str("invalid"), None);
405 }
406
407 #[test]
408 fn test_relation_type_is_list() {
409 assert!(!RelationType::OneToOne.is_list());
410 assert!(!RelationType::ManyToOne.is_list());
411 assert!(RelationType::OneToMany.is_list());
412 assert!(RelationType::ManyToMany.is_list());
413 }
414
415 #[test]
416 fn test_graphql_scalar_from_sql_type() {
417 assert_eq!(GraphQLScalar::from_sql_type("serial"), GraphQLScalar::ID);
418 assert_eq!(GraphQLScalar::from_sql_type("UUID"), GraphQLScalar::ID);
419 assert_eq!(GraphQLScalar::from_sql_type("INTEGER"), GraphQLScalar::Int);
420 assert_eq!(GraphQLScalar::from_sql_type("BIGINT"), GraphQLScalar::BigInt);
421 assert_eq!(GraphQLScalar::from_sql_type("FLOAT"), GraphQLScalar::Float);
422 assert_eq!(GraphQLScalar::from_sql_type("BOOLEAN"), GraphQLScalar::Boolean);
423 assert_eq!(GraphQLScalar::from_sql_type("TIMESTAMP"), GraphQLScalar::DateTime);
424 assert_eq!(GraphQLScalar::from_sql_type("JSONB"), GraphQLScalar::JSON);
425 assert_eq!(GraphQLScalar::from_sql_type("VARCHAR"), GraphQLScalar::String);
426 }
427
428 #[test]
429 fn test_to_pascal_case() {
430 assert_eq!(to_pascal_case("user_name"), "UserName");
431 assert_eq!(to_pascal_case("users"), "Users");
432 assert_eq!(to_pascal_case("post_comments"), "PostComments");
433 }
434
435 #[test]
436 fn test_to_camel_case() {
437 assert_eq!(to_camel_case("user_name"), "userName");
438 assert_eq!(to_camel_case("Users"), "users");
439 assert_eq!(to_camel_case("post_comments"), "postComments");
440 }
441
442 #[test]
443 fn test_to_snake_case() {
444 assert_eq!(to_snake_case("UserName"), "user_name");
445 assert_eq!(to_snake_case("postComments"), "post_comments");
446 assert_eq!(to_snake_case("ID"), "id");
447 }
448
449 #[test]
450 fn test_execution_context() {
451 let ctx = ExecutionContext::new()
452 .with_user("user123")
453 .with_role("admin")
454 .with_role("reader")
455 .with_branch("development")
456 .with_consistency(ConsistencyLevel::Strong);
457
458 assert_eq!(ctx.user_id, Some("user123".to_string()));
459 assert!(ctx.is_authenticated());
460 assert!(ctx.has_role("admin"));
461 assert!(ctx.has_role("reader"));
462 assert!(!ctx.has_role("writer"));
463 assert_eq!(ctx.branch.name, "development");
464 assert_eq!(ctx.consistency, ConsistencyLevel::Strong);
465 }
466
467 #[test]
468 fn test_error_code_http_status() {
469 assert_eq!(ErrorCode::ParseError.http_status(), 400);
470 assert_eq!(ErrorCode::Unauthorized.http_status(), 401);
471 assert_eq!(ErrorCode::Forbidden.http_status(), 403);
472 assert_eq!(ErrorCode::NotFound.http_status(), 404);
473 assert_eq!(ErrorCode::InternalError.http_status(), 500);
474 }
475}