Skip to main content

fraiseql_core/schema/
compiled.rs

1//! Compiled schema types - pure Rust, no Python/TypeScript references.
2//!
3//! These types represent GraphQL schemas after compilation from authoring languages.
4//! All data is owned by Rust - no `Py<T>` or foreign object references.
5//!
6//! # Schema Freeze Invariant
7//!
8//! After `CompiledSchema::from_json()`, the schema is frozen:
9//! - All data is Rust-owned
10//! - No Python/TypeScript callbacks
11//! - No foreign object references
12//! - Safe to use from any Tokio worker thread
13//!
14//! This enables the Axum server to handle requests without any
15//! interaction with Python/TypeScript runtimes.
16
17use std::collections::HashMap;
18
19use serde::{Deserialize, Serialize};
20
21use super::field_type::{FieldDefinition, FieldType};
22
23/// Role definition for field-level RBAC.
24///
25/// Defines which GraphQL scopes a role grants access to.
26/// Used by the runtime to determine which fields a user can access
27/// based on their assigned roles.
28///
29/// # Example
30///
31/// ```json
32/// {
33///   "name": "admin",
34///   "description": "Administrator with all scopes",
35///   "scopes": ["admin:*"]
36/// }
37/// ```
38#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
39pub struct RoleDefinition {
40    /// Role name (e.g., "admin", "user", "viewer").
41    pub name: String,
42
43    /// Optional role description for documentation.
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub description: Option<String>,
46
47    /// List of scopes this role grants access to.
48    /// Scopes follow the format: `action:resource` (e.g., "read:User.email", "admin:*")
49    pub scopes: Vec<String>,
50}
51
52impl RoleDefinition {
53    /// Create a new role definition.
54    #[must_use]
55    pub fn new(name: String, scopes: Vec<String>) -> Self {
56        Self {
57            name,
58            description: None,
59            scopes,
60        }
61    }
62
63    /// Add a description to the role.
64    pub fn with_description(mut self, description: String) -> Self {
65        self.description = Some(description);
66        self
67    }
68
69    /// Check if this role has a specific scope.
70    ///
71    /// Supports exact matching and wildcard patterns:
72    /// - `read:User.email` matches exactly
73    /// - `read:*` matches any scope starting with "read:"
74    /// - `read:User.*` matches "read:User.email", "read:User.name", etc.
75    /// - `admin:*` matches any admin scope
76    #[must_use]
77    pub fn has_scope(&self, required_scope: &str) -> bool {
78        self.scopes.iter().any(|scope| {
79            if scope == "*" {
80                return true; // Wildcard matches everything
81            }
82
83            if scope == required_scope {
84                return true; // Exact match
85            }
86
87            // Handle wildcard patterns like "read:*" or "admin:*"
88            if scope.ends_with(":*") {
89                let prefix = &scope[..scope.len() - 2]; // Remove ":*"
90                return required_scope.starts_with(prefix) && required_scope.contains(':');
91            }
92
93            // Handle Type.* wildcard patterns like "read:User.*"
94            if scope.ends_with(".*") {
95                let prefix = &scope[..scope.len() - 1]; // Remove "*", keep the dot
96                return required_scope.starts_with(prefix);
97            }
98
99            false
100        })
101    }
102}
103
104/// Security configuration from fraiseql.toml.
105///
106/// Contains role definitions and other security-related settings
107/// that are compiled into schema.compiled.json.
108#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
109pub struct SecurityConfig {
110    /// Role definitions mapping role names to their granted scopes.
111    #[serde(default, skip_serializing_if = "Vec::is_empty")]
112    pub role_definitions: Vec<RoleDefinition>,
113
114    /// Default role when none is specified.
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub default_role: Option<String>,
117
118    /// Additional security settings (rate limiting, audit logging, etc.)
119    #[serde(flatten)]
120    pub additional: HashMap<String, serde_json::Value>,
121}
122
123impl SecurityConfig {
124    /// Create a new empty security configuration.
125    #[must_use]
126    pub fn new() -> Self {
127        Self::default()
128    }
129
130    /// Add a role definition.
131    pub fn add_role(&mut self, role: RoleDefinition) {
132        self.role_definitions.push(role);
133    }
134
135    /// Find a role definition by name.
136    #[must_use]
137    pub fn find_role(&self, name: &str) -> Option<&RoleDefinition> {
138        self.role_definitions.iter().find(|r| r.name == name)
139    }
140
141    /// Get all scopes granted to a role.
142    #[must_use]
143    pub fn get_role_scopes(&self, role_name: &str) -> Vec<String> {
144        self.find_role(role_name).map(|role| role.scopes.clone()).unwrap_or_default()
145    }
146
147    /// Check if a role has a specific scope.
148    #[must_use]
149    pub fn role_has_scope(&self, role_name: &str, scope: &str) -> bool {
150        self.find_role(role_name).map(|role| role.has_scope(scope)).unwrap_or(false)
151    }
152}
153
154/// Complete compiled schema - all type information for serving.
155///
156/// This is the central type that holds the entire GraphQL schema
157/// after compilation from Python/TypeScript decorators.
158///
159/// # Example
160///
161/// ```
162/// use fraiseql_core::schema::CompiledSchema;
163///
164/// let json = r#"{
165///     "types": [],
166///     "queries": [],
167///     "mutations": [],
168///     "subscriptions": []
169/// }"#;
170///
171/// let schema = CompiledSchema::from_json(json).unwrap();
172/// assert_eq!(schema.types.len(), 0);
173/// ```
174#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
175pub struct CompiledSchema {
176    /// GraphQL object type definitions.
177    #[serde(default)]
178    pub types: Vec<TypeDefinition>,
179
180    /// GraphQL enum type definitions.
181    #[serde(default)]
182    pub enums: Vec<EnumDefinition>,
183
184    /// GraphQL input object type definitions.
185    #[serde(default)]
186    pub input_types: Vec<InputObjectDefinition>,
187
188    /// GraphQL interface type definitions.
189    #[serde(default)]
190    pub interfaces: Vec<InterfaceDefinition>,
191
192    /// GraphQL union type definitions.
193    #[serde(default)]
194    pub unions: Vec<UnionDefinition>,
195
196    /// GraphQL query definitions.
197    #[serde(default)]
198    pub queries: Vec<QueryDefinition>,
199
200    /// GraphQL mutation definitions.
201    #[serde(default)]
202    pub mutations: Vec<MutationDefinition>,
203
204    /// GraphQL subscription definitions.
205    #[serde(default)]
206    pub subscriptions: Vec<SubscriptionDefinition>,
207
208    /// Custom directive definitions.
209    /// These are user-defined directives beyond the built-in @skip, @include, @deprecated.
210    #[serde(default, skip_serializing_if = "Vec::is_empty")]
211    pub directives: Vec<DirectiveDefinition>,
212
213    /// Fact table metadata (for analytics queries).
214    /// Key: table name (e.g., `tf_sales`)
215    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
216    pub fact_tables: HashMap<String, serde_json::Value>,
217
218    /// Observer definitions (database change event listeners).
219    #[serde(default, skip_serializing_if = "Vec::is_empty")]
220    pub observers: Vec<ObserverDefinition>,
221
222    /// Federation metadata for Apollo Federation v2 support.
223    #[serde(default, skip_serializing_if = "Option::is_none")]
224    pub federation: Option<serde_json::Value>,
225
226    /// Security configuration (from fraiseql.toml).
227    #[serde(default, skip_serializing_if = "Option::is_none")]
228    pub security: Option<serde_json::Value>,
229
230    /// Raw GraphQL schema as string (for SDL generation).
231    #[serde(default, skip_serializing_if = "Option::is_none")]
232    pub schema_sdl: Option<String>,
233}
234
235impl CompiledSchema {
236    /// Create empty schema.
237    #[must_use]
238    pub fn new() -> Self {
239        Self::default()
240    }
241
242    /// Deserialize from JSON string.
243    ///
244    /// This is the primary way to create a schema from Python/TypeScript.
245    /// The authoring language compiles to JSON, Rust deserializes and owns it.
246    ///
247    /// # Errors
248    ///
249    /// Returns error if JSON is malformed or doesn't match schema structure.
250    ///
251    /// # Example
252    ///
253    /// ```
254    /// use fraiseql_core::schema::CompiledSchema;
255    ///
256    /// let json = r#"{"types": [], "queries": [], "mutations": [], "subscriptions": []}"#;
257    /// let schema = CompiledSchema::from_json(json).unwrap();
258    /// ```
259    pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
260        serde_json::from_str(json)
261    }
262
263    /// Serialize to JSON string.
264    ///
265    /// # Errors
266    ///
267    /// Returns error if serialization fails (should not happen for valid schema).
268    pub fn to_json(&self) -> Result<String, serde_json::Error> {
269        serde_json::to_string(self)
270    }
271
272    /// Serialize to pretty JSON string (for debugging/config files).
273    ///
274    /// # Errors
275    ///
276    /// Returns error if serialization fails.
277    pub fn to_json_pretty(&self) -> Result<String, serde_json::Error> {
278        serde_json::to_string_pretty(self)
279    }
280
281    /// Find a type definition by name.
282    #[must_use]
283    pub fn find_type(&self, name: &str) -> Option<&TypeDefinition> {
284        self.types.iter().find(|t| t.name == name)
285    }
286
287    /// Find an enum definition by name.
288    #[must_use]
289    pub fn find_enum(&self, name: &str) -> Option<&EnumDefinition> {
290        self.enums.iter().find(|e| e.name == name)
291    }
292
293    /// Find an input object definition by name.
294    #[must_use]
295    pub fn find_input_type(&self, name: &str) -> Option<&InputObjectDefinition> {
296        self.input_types.iter().find(|i| i.name == name)
297    }
298
299    /// Find an interface definition by name.
300    #[must_use]
301    pub fn find_interface(&self, name: &str) -> Option<&InterfaceDefinition> {
302        self.interfaces.iter().find(|i| i.name == name)
303    }
304
305    /// Find all types that implement a given interface.
306    #[must_use]
307    pub fn find_implementors(&self, interface_name: &str) -> Vec<&TypeDefinition> {
308        self.types
309            .iter()
310            .filter(|t| t.implements.contains(&interface_name.to_string()))
311            .collect()
312    }
313
314    /// Find a union definition by name.
315    #[must_use]
316    pub fn find_union(&self, name: &str) -> Option<&UnionDefinition> {
317        self.unions.iter().find(|u| u.name == name)
318    }
319
320    /// Find a query definition by name.
321    #[must_use]
322    pub fn find_query(&self, name: &str) -> Option<&QueryDefinition> {
323        self.queries.iter().find(|q| q.name == name)
324    }
325
326    /// Find a mutation definition by name.
327    #[must_use]
328    pub fn find_mutation(&self, name: &str) -> Option<&MutationDefinition> {
329        self.mutations.iter().find(|m| m.name == name)
330    }
331
332    /// Find a subscription definition by name.
333    #[must_use]
334    pub fn find_subscription(&self, name: &str) -> Option<&SubscriptionDefinition> {
335        self.subscriptions.iter().find(|s| s.name == name)
336    }
337
338    /// Find a custom directive definition by name.
339    #[must_use]
340    pub fn find_directive(&self, name: &str) -> Option<&DirectiveDefinition> {
341        self.directives.iter().find(|d| d.name == name)
342    }
343
344    /// Get total number of operations (queries + mutations + subscriptions).
345    #[must_use]
346    pub fn operation_count(&self) -> usize {
347        self.queries.len() + self.mutations.len() + self.subscriptions.len()
348    }
349
350    /// Register fact table metadata.
351    ///
352    /// # Arguments
353    ///
354    /// * `table_name` - Fact table name (e.g., `tf_sales`)
355    /// * `metadata` - Serialized `FactTableMetadata`
356    pub fn add_fact_table(&mut self, table_name: String, metadata: serde_json::Value) {
357        self.fact_tables.insert(table_name, metadata);
358    }
359
360    /// Get fact table metadata by name.
361    ///
362    /// # Arguments
363    ///
364    /// * `name` - Fact table name
365    ///
366    /// # Returns
367    ///
368    /// Fact table metadata if found
369    #[must_use]
370    pub fn get_fact_table(&self, name: &str) -> Option<&serde_json::Value> {
371        self.fact_tables.get(name)
372    }
373
374    /// List all fact table names.
375    ///
376    /// # Returns
377    ///
378    /// Vector of fact table names
379    #[must_use]
380    pub fn list_fact_tables(&self) -> Vec<&str> {
381        self.fact_tables.keys().map(String::as_str).collect()
382    }
383
384    /// Check if schema contains any fact tables.
385    #[must_use]
386    pub fn has_fact_tables(&self) -> bool {
387        !self.fact_tables.is_empty()
388    }
389
390    /// Find an observer definition by name.
391    #[must_use]
392    pub fn find_observer(&self, name: &str) -> Option<&ObserverDefinition> {
393        self.observers.iter().find(|o| o.name == name)
394    }
395
396    /// Get all observers for a specific entity type.
397    #[must_use]
398    pub fn find_observers_for_entity(&self, entity: &str) -> Vec<&ObserverDefinition> {
399        self.observers.iter().filter(|o| o.entity == entity).collect()
400    }
401
402    /// Get all observers for a specific event type (INSERT, UPDATE, DELETE).
403    #[must_use]
404    pub fn find_observers_for_event(&self, event: &str) -> Vec<&ObserverDefinition> {
405        self.observers.iter().filter(|o| o.event == event).collect()
406    }
407
408    /// Check if schema contains any observers.
409    #[must_use]
410    pub fn has_observers(&self) -> bool {
411        !self.observers.is_empty()
412    }
413
414    /// Get total number of observers.
415    #[must_use]
416    pub fn observer_count(&self) -> usize {
417        self.observers.len()
418    }
419
420    /// Get federation metadata from schema.
421    ///
422    /// # Returns
423    ///
424    /// Federation metadata if configured in schema
425    #[must_use]
426    pub fn federation_metadata(&self) -> Option<crate::federation::FederationMetadata> {
427        self.federation
428            .as_ref()
429            .and_then(|fed_json| serde_json::from_value(fed_json.clone()).ok())
430    }
431
432    /// Get security configuration from schema.
433    ///
434    /// # Returns
435    ///
436    /// Security configuration if present (includes role definitions)
437    #[must_use]
438    pub fn security_config(&self) -> Option<SecurityConfig> {
439        self.security
440            .as_ref()
441            .and_then(|sec_json| serde_json::from_value(sec_json.clone()).ok())
442    }
443
444    /// Find a role definition by name.
445    ///
446    /// # Arguments
447    ///
448    /// * `role_name` - Name of the role to find
449    ///
450    /// # Returns
451    ///
452    /// Role definition if found
453    #[must_use]
454    pub fn find_role(&self, role_name: &str) -> Option<RoleDefinition> {
455        self.security_config().and_then(|config| config.find_role(role_name).cloned())
456    }
457
458    /// Get scopes for a role.
459    ///
460    /// # Arguments
461    ///
462    /// * `role_name` - Name of the role
463    ///
464    /// # Returns
465    ///
466    /// Vector of scopes granted to the role
467    #[must_use]
468    pub fn get_role_scopes(&self, role_name: &str) -> Vec<String> {
469        self.security_config()
470            .map(|config| config.get_role_scopes(role_name))
471            .unwrap_or_default()
472    }
473
474    /// Check if a role has a specific scope.
475    ///
476    /// # Arguments
477    ///
478    /// * `role_name` - Name of the role
479    /// * `scope` - Scope to check for
480    ///
481    /// # Returns
482    ///
483    /// true if role has the scope, false otherwise
484    #[must_use]
485    pub fn role_has_scope(&self, role_name: &str, scope: &str) -> bool {
486        self.security_config()
487            .map(|config| config.role_has_scope(role_name, scope))
488            .unwrap_or(false)
489    }
490
491    /// Get raw GraphQL schema SDL.
492    ///
493    /// # Returns
494    ///
495    /// Raw schema string if available, otherwise generates from type definitions
496    #[must_use]
497    pub fn raw_schema(&self) -> String {
498        self.schema_sdl.clone().unwrap_or_else(|| {
499            // Generate basic SDL from type definitions if not provided
500            let mut sdl = String::new();
501
502            // Add types
503            for type_def in &self.types {
504                sdl.push_str(&format!("type {} {{\n", type_def.name));
505                for field in &type_def.fields {
506                    sdl.push_str(&format!("  {}: {}\n", field.name, field.field_type));
507                }
508                sdl.push_str("}\n\n");
509            }
510
511            sdl
512        })
513    }
514
515    /// Validate the schema for internal consistency.
516    ///
517    /// Checks:
518    /// - All type references resolve to defined types
519    /// - No duplicate type/operation names
520    /// - Required fields have valid types
521    ///
522    /// # Errors
523    ///
524    /// Returns list of validation errors if schema is invalid.
525    pub fn validate(&self) -> Result<(), Vec<String>> {
526        let mut errors = Vec::new();
527
528        // Check for duplicate type names
529        let mut type_names: std::collections::HashSet<&str> = std::collections::HashSet::new();
530        for type_def in &self.types {
531            if !type_names.insert(&type_def.name) {
532                errors.push(format!("Duplicate type name: {}", type_def.name));
533            }
534        }
535
536        // Check for duplicate query names
537        let mut query_names: std::collections::HashSet<&str> = std::collections::HashSet::new();
538        for query in &self.queries {
539            if !query_names.insert(&query.name) {
540                errors.push(format!("Duplicate query name: {}", query.name));
541            }
542        }
543
544        // Check for duplicate mutation names
545        let mut mutation_names: std::collections::HashSet<&str> = std::collections::HashSet::new();
546        for mutation in &self.mutations {
547            if !mutation_names.insert(&mutation.name) {
548                errors.push(format!("Duplicate mutation name: {}", mutation.name));
549            }
550        }
551
552        // Check type references in queries
553        for query in &self.queries {
554            if !type_names.contains(query.return_type.as_str())
555                && !is_builtin_type(&query.return_type)
556            {
557                errors.push(format!(
558                    "Query '{}' references undefined type '{}'",
559                    query.name, query.return_type
560                ));
561            }
562        }
563
564        // Check type references in mutations
565        for mutation in &self.mutations {
566            if !type_names.contains(mutation.return_type.as_str())
567                && !is_builtin_type(&mutation.return_type)
568            {
569                errors.push(format!(
570                    "Mutation '{}' references undefined type '{}'",
571                    mutation.name, mutation.return_type
572                ));
573            }
574        }
575
576        if errors.is_empty() {
577            Ok(())
578        } else {
579            Err(errors)
580        }
581    }
582}
583
584/// Check if a type name is a built-in scalar type.
585fn is_builtin_type(name: &str) -> bool {
586    matches!(
587        name,
588        "String"
589            | "Int"
590            | "Float"
591            | "Boolean"
592            | "ID"
593            | "DateTime"
594            | "Date"
595            | "Time"
596            | "JSON"
597            | "UUID"
598            | "Decimal"
599    )
600}
601
602/// A GraphQL type definition compiled from `@fraiseql.type`.
603///
604/// This represents a complete object type with its fields and database binding.
605///
606/// # Example
607///
608/// ```
609/// use fraiseql_core::schema::{TypeDefinition, FieldDefinition, FieldType};
610///
611/// let user_type = TypeDefinition {
612///     name: "User".to_string(),
613///     sql_source: "v_user".to_string(),
614///     jsonb_column: "data".to_string(),
615///     fields: vec![
616///         FieldDefinition::new("id", FieldType::Id),
617///         FieldDefinition::new("email", FieldType::String),
618///     ],
619///     description: Some("A user in the system".to_string()),
620///     sql_projection_hint: None,
621///     implements: vec![],
622/// };
623/// ```
624#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
625pub struct TypeDefinition {
626    /// GraphQL type name (e.g., "User").
627    pub name: String,
628
629    /// SQL source table/view (e.g., `v_user`).
630    pub sql_source: String,
631
632    /// JSONB column name (e.g., "data").
633    #[serde(default = "default_jsonb_column")]
634    pub jsonb_column: String,
635
636    /// Field definitions.
637    #[serde(default)]
638    pub fields: Vec<FieldDefinition>,
639
640    /// Optional description (from docstring).
641    #[serde(default, skip_serializing_if = "Option::is_none")]
642    pub description: Option<String>,
643
644    /// SQL projection hint for PostgreSQL optimization.
645    /// Generated at compile time to reduce payload size for large JSONB objects.
646    /// Example: `jsonb_build_object('id', data->>'id', 'email', data->>'email')`
647    #[serde(default, skip_serializing_if = "Option::is_none")]
648    pub sql_projection_hint: Option<SqlProjectionHint>,
649
650    /// Interfaces this type implements.
651    #[serde(default, skip_serializing_if = "Vec::is_empty")]
652    pub implements: Vec<String>,
653}
654
655/// SQL projection hint for database-specific field projection optimization.
656///
657/// When a type has a large JSONB payload, the compiler can generate
658/// SQL that projects only the requested fields, reducing network payload
659/// and JSON deserialization overhead.
660#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
661pub struct SqlProjectionHint {
662    /// Database type (e.g., "postgresql", "mysql", "sqlite").
663    pub database: String,
664
665    /// The projection SQL template.
666    /// Example for PostgreSQL:
667    /// `jsonb_build_object('id', data->>'id', 'email', data->>'email')`
668    pub projection_template: String,
669
670    /// Estimated reduction in payload size (percentage 0-100).
671    pub estimated_reduction_percent: u32,
672}
673
674fn default_jsonb_column() -> String {
675    "data".to_string()
676}
677
678impl TypeDefinition {
679    /// Create a new type definition.
680    #[must_use]
681    pub fn new(name: impl Into<String>, sql_source: impl Into<String>) -> Self {
682        Self {
683            name:                name.into(),
684            sql_source:          sql_source.into(),
685            jsonb_column:        "data".to_string(),
686            fields:              Vec::new(),
687            description:         None,
688            sql_projection_hint: None,
689            implements:          Vec::new(),
690        }
691    }
692
693    /// Add a field to this type.
694    #[must_use]
695    pub fn with_field(mut self, field: FieldDefinition) -> Self {
696        self.fields.push(field);
697        self
698    }
699
700    /// Set the JSONB column name.
701    #[must_use]
702    pub fn with_jsonb_column(mut self, column: impl Into<String>) -> Self {
703        self.jsonb_column = column.into();
704        self
705    }
706
707    /// Set the description.
708    #[must_use]
709    pub fn with_description(mut self, desc: impl Into<String>) -> Self {
710        self.description = Some(desc.into());
711        self
712    }
713
714    /// Find a field by name (JSONB key).
715    #[must_use]
716    pub fn find_field(&self, name: &str) -> Option<&FieldDefinition> {
717        self.fields.iter().find(|f| f.name == name)
718    }
719
720    /// Find a field by its output name (alias if set, otherwise name).
721    ///
722    /// Useful for resolving field references in GraphQL queries where
723    /// aliases may be used.
724    #[must_use]
725    pub fn find_field_by_output_name(&self, output_name: &str) -> Option<&FieldDefinition> {
726        self.fields.iter().find(|f| f.output_name() == output_name)
727    }
728
729    /// Set SQL projection hint for optimization.
730    #[must_use]
731    pub fn with_sql_projection(mut self, hint: SqlProjectionHint) -> Self {
732        self.sql_projection_hint = Some(hint);
733        self
734    }
735
736    /// Check if type has SQL projection hint.
737    #[must_use]
738    pub fn has_sql_projection(&self) -> bool {
739        self.sql_projection_hint.is_some()
740    }
741
742    /// Get the `__typename` value for this type.
743    ///
744    /// Returns the GraphQL type name, used for type introspection in responses.
745    /// Per GraphQL spec ยง2.7, `__typename` returns the name of the object type.
746    #[must_use]
747    pub fn typename(&self) -> &str {
748        &self.name
749    }
750}
751
752// =============================================================================
753// Enum Definitions
754// =============================================================================
755
756/// A GraphQL enum type definition.
757///
758/// Enums represent a finite set of possible values, useful for
759/// categorization fields like status, role, or priority.
760///
761/// # Example
762///
763/// ```
764/// use fraiseql_core::schema::{EnumDefinition, EnumValueDefinition};
765///
766/// let status_enum = EnumDefinition {
767///     name: "OrderStatus".to_string(),
768///     values: vec![
769///         EnumValueDefinition::new("PENDING"),
770///         EnumValueDefinition::new("PROCESSING"),
771///         EnumValueDefinition::new("SHIPPED"),
772///         EnumValueDefinition::new("DELIVERED"),
773///     ],
774///     description: Some("Possible states of an order".to_string()),
775/// };
776/// ```
777#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
778pub struct EnumDefinition {
779    /// Enum type name (e.g., "OrderStatus").
780    pub name: String,
781
782    /// Possible values for this enum.
783    #[serde(default)]
784    pub values: Vec<EnumValueDefinition>,
785
786    /// Description of the enum type.
787    #[serde(default, skip_serializing_if = "Option::is_none")]
788    pub description: Option<String>,
789}
790
791impl EnumDefinition {
792    /// Create a new enum definition.
793    #[must_use]
794    pub fn new(name: impl Into<String>) -> Self {
795        Self {
796            name:        name.into(),
797            values:      Vec::new(),
798            description: None,
799        }
800    }
801
802    /// Add a value to this enum.
803    #[must_use]
804    pub fn with_value(mut self, value: EnumValueDefinition) -> Self {
805        self.values.push(value);
806        self
807    }
808
809    /// Add multiple values to this enum.
810    #[must_use]
811    pub fn with_values(mut self, values: Vec<EnumValueDefinition>) -> Self {
812        self.values = values;
813        self
814    }
815
816    /// Set description.
817    #[must_use]
818    pub fn with_description(mut self, description: impl Into<String>) -> Self {
819        self.description = Some(description.into());
820        self
821    }
822
823    /// Check if a value exists in this enum.
824    #[must_use]
825    pub fn has_value(&self, name: &str) -> bool {
826        self.values.iter().any(|v| v.name == name)
827    }
828
829    /// Find a value by name.
830    #[must_use]
831    pub fn find_value(&self, name: &str) -> Option<&EnumValueDefinition> {
832        self.values.iter().find(|v| v.name == name)
833    }
834}
835
836/// A single value within a GraphQL enum type.
837///
838/// # Example
839///
840/// ```
841/// use fraiseql_core::schema::EnumValueDefinition;
842///
843/// let value = EnumValueDefinition::new("ACTIVE")
844///     .with_description("The item is currently active");
845/// ```
846#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
847pub struct EnumValueDefinition {
848    /// Value name (e.g., "PENDING").
849    pub name: String,
850
851    /// Description of this value.
852    #[serde(default, skip_serializing_if = "Option::is_none")]
853    pub description: Option<String>,
854
855    /// Deprecation information (if this value is deprecated).
856    #[serde(default, skip_serializing_if = "Option::is_none")]
857    pub deprecation: Option<super::field_type::DeprecationInfo>,
858}
859
860impl EnumValueDefinition {
861    /// Create a new enum value.
862    #[must_use]
863    pub fn new(name: impl Into<String>) -> Self {
864        Self {
865            name:        name.into(),
866            description: None,
867            deprecation: None,
868        }
869    }
870
871    /// Set description.
872    #[must_use]
873    pub fn with_description(mut self, description: impl Into<String>) -> Self {
874        self.description = Some(description.into());
875        self
876    }
877
878    /// Mark this value as deprecated.
879    #[must_use]
880    pub fn deprecated(mut self, reason: Option<String>) -> Self {
881        self.deprecation = Some(super::field_type::DeprecationInfo { reason });
882        self
883    }
884
885    /// Check if this value is deprecated.
886    #[must_use]
887    pub fn is_deprecated(&self) -> bool {
888        self.deprecation.is_some()
889    }
890}
891
892// =============================================================================
893// Input Object Definitions
894// =============================================================================
895
896/// A GraphQL input object type definition.
897///
898/// Input objects are used for complex query arguments like filters,
899/// ordering, and mutation inputs.
900///
901/// # Example
902///
903/// ```
904/// use fraiseql_core::schema::{InputObjectDefinition, InputFieldDefinition};
905///
906/// let user_filter = InputObjectDefinition {
907///     name: "UserFilter".to_string(),
908///     fields: vec![
909///         InputFieldDefinition::new("name", "String"),
910///         InputFieldDefinition::new("email", "String"),
911///         InputFieldDefinition::new("active", "Boolean"),
912///     ],
913///     description: Some("Filter criteria for users".to_string()),
914/// };
915/// ```
916#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
917pub struct InputObjectDefinition {
918    /// Input object type name (e.g., "UserFilter").
919    pub name: String,
920
921    /// Input fields.
922    #[serde(default)]
923    pub fields: Vec<InputFieldDefinition>,
924
925    /// Description of the input type.
926    #[serde(default, skip_serializing_if = "Option::is_none")]
927    pub description: Option<String>,
928}
929
930impl InputObjectDefinition {
931    /// Create a new input object definition.
932    #[must_use]
933    pub fn new(name: impl Into<String>) -> Self {
934        Self {
935            name:        name.into(),
936            fields:      Vec::new(),
937            description: None,
938        }
939    }
940
941    /// Add a field to this input object.
942    #[must_use]
943    pub fn with_field(mut self, field: InputFieldDefinition) -> Self {
944        self.fields.push(field);
945        self
946    }
947
948    /// Add multiple fields to this input object.
949    #[must_use]
950    pub fn with_fields(mut self, fields: Vec<InputFieldDefinition>) -> Self {
951        self.fields = fields;
952        self
953    }
954
955    /// Set description.
956    #[must_use]
957    pub fn with_description(mut self, description: impl Into<String>) -> Self {
958        self.description = Some(description.into());
959        self
960    }
961
962    /// Find a field by name.
963    #[must_use]
964    pub fn find_field(&self, name: &str) -> Option<&InputFieldDefinition> {
965        self.fields.iter().find(|f| f.name == name)
966    }
967}
968
969/// A field within a GraphQL input object type.
970///
971/// # Example
972///
973/// ```
974/// use fraiseql_core::schema::InputFieldDefinition;
975///
976/// let field = InputFieldDefinition::new("email", "String!")
977///     .with_description("User's email address")
978///     .with_default_value("\"user@example.com\"");
979/// ```
980#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
981pub struct InputFieldDefinition {
982    /// Field name.
983    pub name: String,
984
985    /// Field type (e.g., `"String!"`, `"[Int]"`, `"UserFilter"`).
986    pub field_type: String,
987
988    /// Description.
989    #[serde(default, skip_serializing_if = "Option::is_none")]
990    pub description: Option<String>,
991
992    /// Default value (as JSON string).
993    #[serde(default, skip_serializing_if = "Option::is_none")]
994    pub default_value: Option<String>,
995
996    /// Deprecation information (if this field is deprecated).
997    #[serde(default, skip_serializing_if = "Option::is_none")]
998    pub deprecation: Option<super::field_type::DeprecationInfo>,
999}
1000
1001impl InputFieldDefinition {
1002    /// Create a new input field.
1003    #[must_use]
1004    pub fn new(name: impl Into<String>, field_type: impl Into<String>) -> Self {
1005        Self {
1006            name:          name.into(),
1007            field_type:    field_type.into(),
1008            description:   None,
1009            default_value: None,
1010            deprecation:   None,
1011        }
1012    }
1013
1014    /// Set description.
1015    #[must_use]
1016    pub fn with_description(mut self, description: impl Into<String>) -> Self {
1017        self.description = Some(description.into());
1018        self
1019    }
1020
1021    /// Set default value.
1022    #[must_use]
1023    pub fn with_default_value(mut self, value: impl Into<String>) -> Self {
1024        self.default_value = Some(value.into());
1025        self
1026    }
1027
1028    /// Mark this field as deprecated.
1029    #[must_use]
1030    pub fn deprecated(mut self, reason: Option<String>) -> Self {
1031        self.deprecation = Some(super::field_type::DeprecationInfo { reason });
1032        self
1033    }
1034
1035    /// Check if this field is deprecated.
1036    #[must_use]
1037    pub fn is_deprecated(&self) -> bool {
1038        self.deprecation.is_some()
1039    }
1040
1041    /// Check if this field is required (non-nullable without default).
1042    #[must_use]
1043    pub fn is_required(&self) -> bool {
1044        self.field_type.ends_with('!') && self.default_value.is_none()
1045    }
1046}
1047
1048// =============================================================================
1049// Interface Definitions
1050// =============================================================================
1051
1052/// A GraphQL interface type definition.
1053///
1054/// Interfaces define a common set of fields that multiple types can implement.
1055/// They enable polymorphic queries where a field can return any type that
1056/// implements the interface.
1057///
1058/// # Example
1059///
1060/// ```
1061/// use fraiseql_core::schema::{InterfaceDefinition, FieldDefinition, FieldType};
1062///
1063/// let node_interface = InterfaceDefinition {
1064///     name: "Node".to_string(),
1065///     fields: vec![
1066///         FieldDefinition::new("id", FieldType::Id),
1067///     ],
1068///     description: Some("An object with an ID".to_string()),
1069/// };
1070/// ```
1071#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1072pub struct InterfaceDefinition {
1073    /// Interface name (e.g., "Node").
1074    pub name: String,
1075
1076    /// Fields that implementing types must define.
1077    #[serde(default)]
1078    pub fields: Vec<FieldDefinition>,
1079
1080    /// Description of the interface.
1081    #[serde(default, skip_serializing_if = "Option::is_none")]
1082    pub description: Option<String>,
1083}
1084
1085impl InterfaceDefinition {
1086    /// Create a new interface definition.
1087    #[must_use]
1088    pub fn new(name: impl Into<String>) -> Self {
1089        Self {
1090            name:        name.into(),
1091            fields:      Vec::new(),
1092            description: None,
1093        }
1094    }
1095
1096    /// Add a field to this interface.
1097    #[must_use]
1098    pub fn with_field(mut self, field: FieldDefinition) -> Self {
1099        self.fields.push(field);
1100        self
1101    }
1102
1103    /// Add multiple fields to this interface.
1104    #[must_use]
1105    pub fn with_fields(mut self, fields: Vec<FieldDefinition>) -> Self {
1106        self.fields = fields;
1107        self
1108    }
1109
1110    /// Set description.
1111    #[must_use]
1112    pub fn with_description(mut self, description: impl Into<String>) -> Self {
1113        self.description = Some(description.into());
1114        self
1115    }
1116
1117    /// Find a field by name.
1118    #[must_use]
1119    pub fn find_field(&self, name: &str) -> Option<&FieldDefinition> {
1120        self.fields.iter().find(|f| f.name == name)
1121    }
1122}
1123
1124// =============================================================================
1125// Union Definitions
1126// =============================================================================
1127
1128/// A GraphQL union type definition.
1129///
1130/// Unions represent a type that can be one of several possible object types.
1131/// Unlike interfaces, union member types don't need to share any fields.
1132/// Per GraphQL spec ยง3.8, unions are useful for polymorphic returns.
1133///
1134/// # Example
1135///
1136/// ```
1137/// use fraiseql_core::schema::UnionDefinition;
1138///
1139/// let search_result = UnionDefinition {
1140///     name: "SearchResult".to_string(),
1141///     member_types: vec!["User".to_string(), "Post".to_string(), "Comment".to_string()],
1142///     description: Some("Possible search result types".to_string()),
1143/// };
1144/// ```
1145#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1146pub struct UnionDefinition {
1147    /// Union name (e.g., "SearchResult").
1148    pub name: String,
1149
1150    /// Member types that this union can represent.
1151    /// Order is significant for resolution.
1152    pub member_types: Vec<String>,
1153
1154    /// Description of the union.
1155    #[serde(default, skip_serializing_if = "Option::is_none")]
1156    pub description: Option<String>,
1157}
1158
1159impl UnionDefinition {
1160    /// Create a new union definition.
1161    #[must_use]
1162    pub fn new(name: impl Into<String>) -> Self {
1163        Self {
1164            name:         name.into(),
1165            member_types: Vec::new(),
1166            description:  None,
1167        }
1168    }
1169
1170    /// Add a member type to this union.
1171    #[must_use]
1172    pub fn with_member(mut self, type_name: impl Into<String>) -> Self {
1173        self.member_types.push(type_name.into());
1174        self
1175    }
1176
1177    /// Add multiple member types to this union.
1178    #[must_use]
1179    pub fn with_members(mut self, members: Vec<String>) -> Self {
1180        self.member_types = members;
1181        self
1182    }
1183
1184    /// Set description.
1185    #[must_use]
1186    pub fn with_description(mut self, description: impl Into<String>) -> Self {
1187        self.description = Some(description.into());
1188        self
1189    }
1190
1191    /// Check if a type is a member of this union.
1192    #[must_use]
1193    pub fn contains_type(&self, type_name: &str) -> bool {
1194        self.member_types.iter().any(|t| t == type_name)
1195    }
1196}
1197
1198/// A query definition compiled from `@fraiseql.query`.
1199///
1200/// Queries are declarative bindings to database views/tables.
1201/// They describe *what* to fetch, not *how* to fetch it.
1202///
1203/// # Example
1204///
1205/// ```
1206/// use fraiseql_core::schema::{QueryDefinition, AutoParams};
1207///
1208/// let query = QueryDefinition {
1209///     name: "users".to_string(),
1210///     return_type: "User".to_string(),
1211///     returns_list: true,
1212///     nullable: false,
1213///     arguments: vec![],
1214///     sql_source: Some("v_user".to_string()),
1215///     description: Some("Get all users".to_string()),
1216///     auto_params: AutoParams::default(),
1217///     deprecation: None,
1218/// };
1219/// ```
1220#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1221pub struct QueryDefinition {
1222    /// Query name (e.g., "users").
1223    pub name: String,
1224
1225    /// Return type name (e.g., "User").
1226    pub return_type: String,
1227
1228    /// Does this query return a list?
1229    #[serde(default)]
1230    pub returns_list: bool,
1231
1232    /// Is the return value nullable?
1233    #[serde(default)]
1234    pub nullable: bool,
1235
1236    /// Query arguments.
1237    #[serde(default)]
1238    pub arguments: Vec<ArgumentDefinition>,
1239
1240    /// SQL source table/view (for direct table queries).
1241    #[serde(default, skip_serializing_if = "Option::is_none")]
1242    pub sql_source: Option<String>,
1243
1244    /// Description.
1245    #[serde(default, skip_serializing_if = "Option::is_none")]
1246    pub description: Option<String>,
1247
1248    /// Auto-wired parameters (where, orderBy, limit, offset).
1249    #[serde(default)]
1250    pub auto_params: AutoParams,
1251
1252    /// Deprecation information (from @deprecated directive).
1253    /// When set, this query is marked as deprecated in the schema.
1254    #[serde(default, skip_serializing_if = "Option::is_none")]
1255    pub deprecation: Option<super::field_type::DeprecationInfo>,
1256}
1257
1258impl QueryDefinition {
1259    /// Create a new query definition.
1260    #[must_use]
1261    pub fn new(name: impl Into<String>, return_type: impl Into<String>) -> Self {
1262        Self {
1263            name:         name.into(),
1264            return_type:  return_type.into(),
1265            returns_list: false,
1266            nullable:     false,
1267            arguments:    Vec::new(),
1268            sql_source:   None,
1269            description:  None,
1270            auto_params:  AutoParams::default(),
1271            deprecation:  None,
1272        }
1273    }
1274
1275    /// Set this query to return a list.
1276    #[must_use]
1277    pub fn returning_list(mut self) -> Self {
1278        self.returns_list = true;
1279        self
1280    }
1281
1282    /// Set the SQL source.
1283    #[must_use]
1284    pub fn with_sql_source(mut self, source: impl Into<String>) -> Self {
1285        self.sql_source = Some(source.into());
1286        self
1287    }
1288
1289    /// Mark this query as deprecated.
1290    ///
1291    /// # Example
1292    ///
1293    /// ```
1294    /// use fraiseql_core::schema::QueryDefinition;
1295    ///
1296    /// let query = QueryDefinition::new("oldUsers", "User")
1297    ///     .deprecated(Some("Use 'users' instead".to_string()));
1298    /// assert!(query.is_deprecated());
1299    /// ```
1300    #[must_use]
1301    pub fn deprecated(mut self, reason: Option<String>) -> Self {
1302        self.deprecation = Some(super::field_type::DeprecationInfo { reason });
1303        self
1304    }
1305
1306    /// Check if this query is deprecated.
1307    #[must_use]
1308    pub fn is_deprecated(&self) -> bool {
1309        self.deprecation.is_some()
1310    }
1311
1312    /// Get the deprecation reason if deprecated.
1313    #[must_use]
1314    pub fn deprecation_reason(&self) -> Option<&str> {
1315        self.deprecation.as_ref().and_then(|d| d.reason.as_deref())
1316    }
1317}
1318
1319/// A mutation definition compiled from `@fraiseql.mutation`.
1320///
1321/// Mutations are declarative bindings to database functions.
1322/// They describe *which function* to call, not arbitrary logic.
1323///
1324/// # Example
1325///
1326/// ```
1327/// use fraiseql_core::schema::{MutationDefinition, MutationOperation};
1328///
1329/// let mutation = MutationDefinition {
1330///     name: "createUser".to_string(),
1331///     return_type: "User".to_string(),
1332///     arguments: vec![],
1333///     description: Some("Create a new user".to_string()),
1334///     operation: MutationOperation::Insert { table: "users".to_string() },
1335///     deprecation: None,
1336/// };
1337/// ```
1338#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1339pub struct MutationDefinition {
1340    /// Mutation name (e.g., "createUser").
1341    pub name: String,
1342
1343    /// Return type name.
1344    pub return_type: String,
1345
1346    /// Input arguments.
1347    #[serde(default)]
1348    pub arguments: Vec<ArgumentDefinition>,
1349
1350    /// Description.
1351    #[serde(default, skip_serializing_if = "Option::is_none")]
1352    pub description: Option<String>,
1353
1354    /// SQL operation type.
1355    #[serde(default)]
1356    pub operation: MutationOperation,
1357
1358    /// Deprecation information (from @deprecated directive).
1359    /// When set, this mutation is marked as deprecated in the schema.
1360    #[serde(default, skip_serializing_if = "Option::is_none")]
1361    pub deprecation: Option<super::field_type::DeprecationInfo>,
1362}
1363
1364impl MutationDefinition {
1365    /// Create a new mutation definition.
1366    #[must_use]
1367    pub fn new(name: impl Into<String>, return_type: impl Into<String>) -> Self {
1368        Self {
1369            name:        name.into(),
1370            return_type: return_type.into(),
1371            arguments:   Vec::new(),
1372            description: None,
1373            operation:   MutationOperation::default(),
1374            deprecation: None,
1375        }
1376    }
1377
1378    /// Mark this mutation as deprecated.
1379    ///
1380    /// # Example
1381    ///
1382    /// ```
1383    /// use fraiseql_core::schema::MutationDefinition;
1384    ///
1385    /// let mutation = MutationDefinition::new("oldCreateUser", "User")
1386    ///     .deprecated(Some("Use 'createUser' instead".to_string()));
1387    /// assert!(mutation.is_deprecated());
1388    /// ```
1389    #[must_use]
1390    pub fn deprecated(mut self, reason: Option<String>) -> Self {
1391        self.deprecation = Some(super::field_type::DeprecationInfo { reason });
1392        self
1393    }
1394
1395    /// Check if this mutation is deprecated.
1396    #[must_use]
1397    pub fn is_deprecated(&self) -> bool {
1398        self.deprecation.is_some()
1399    }
1400
1401    /// Get the deprecation reason if deprecated.
1402    #[must_use]
1403    pub fn deprecation_reason(&self) -> Option<&str> {
1404        self.deprecation.as_ref().and_then(|d| d.reason.as_deref())
1405    }
1406}
1407
1408/// Mutation operation types.
1409///
1410/// This enum describes what kind of database operation a mutation performs.
1411#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1412#[serde(rename_all = "PascalCase")]
1413pub enum MutationOperation {
1414    /// INSERT into a table.
1415    Insert {
1416        /// Target table name.
1417        table: String,
1418    },
1419
1420    /// UPDATE a table.
1421    Update {
1422        /// Target table name.
1423        table: String,
1424    },
1425
1426    /// DELETE from a table.
1427    Delete {
1428        /// Target table name.
1429        table: String,
1430    },
1431
1432    /// Call a database function.
1433    Function {
1434        /// Function name.
1435        name: String,
1436    },
1437
1438    /// Custom mutation (for complex operations).
1439    #[default]
1440    Custom,
1441}
1442
1443/// A subscription definition.
1444///
1445/// Subscriptions are declarative bindings to event topics.
1446#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1447pub struct SubscriptionDefinition {
1448    /// Subscription name.
1449    pub name: String,
1450
1451    /// Return type name.
1452    pub return_type: String,
1453
1454    /// Arguments.
1455    #[serde(default)]
1456    pub arguments: Vec<ArgumentDefinition>,
1457
1458    /// Description.
1459    #[serde(default, skip_serializing_if = "Option::is_none")]
1460    pub description: Option<String>,
1461
1462    /// Event topic to subscribe to.
1463    #[serde(default, skip_serializing_if = "Option::is_none")]
1464    pub topic: Option<String>,
1465
1466    /// Compiled filter expression for event matching.
1467    /// Maps argument names to JSONB paths in event data.
1468    /// Example: `{"orderId": "$.id", "status": "$.order_status"}`
1469    #[serde(default, skip_serializing_if = "Option::is_none")]
1470    pub filter: Option<SubscriptionFilter>,
1471
1472    /// Fields to project from event data.
1473    /// If empty, all fields are returned.
1474    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1475    pub fields: Vec<String>,
1476
1477    /// Deprecation information (from @deprecated directive).
1478    /// When set, this subscription is marked as deprecated in the schema.
1479    #[serde(default, skip_serializing_if = "Option::is_none")]
1480    pub deprecation: Option<super::field_type::DeprecationInfo>,
1481}
1482
1483/// Filter configuration for subscription event matching.
1484#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1485pub struct SubscriptionFilter {
1486    /// Mapping of argument names to JSONB paths in event data.
1487    /// The path uses JSON pointer syntax (e.g., "/id", "/user/name").
1488    #[serde(default)]
1489    pub argument_paths: std::collections::HashMap<String, String>,
1490
1491    /// Static filter conditions that must always match.
1492    /// Each entry is a path and expected value.
1493    #[serde(default)]
1494    pub static_filters: Vec<StaticFilterCondition>,
1495}
1496
1497/// A static filter condition for subscription matching.
1498#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1499pub struct StaticFilterCondition {
1500    /// JSONB path in event data.
1501    pub path:     String,
1502    /// Comparison operator.
1503    pub operator: FilterOperator,
1504    /// Value to compare against.
1505    pub value:    serde_json::Value,
1506}
1507
1508/// Filter comparison operators.
1509#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1510#[serde(rename_all = "snake_case")]
1511pub enum FilterOperator {
1512    /// Equals (==).
1513    Eq,
1514    /// Not equals (!=).
1515    Ne,
1516    /// Greater than (>).
1517    Gt,
1518    /// Greater than or equal (>=).
1519    Gte,
1520    /// Less than (<).
1521    Lt,
1522    /// Less than or equal (<=).
1523    Lte,
1524    /// Contains (for arrays/strings).
1525    Contains,
1526    /// Starts with (for strings).
1527    StartsWith,
1528    /// Ends with (for strings).
1529    EndsWith,
1530}
1531
1532impl SubscriptionDefinition {
1533    /// Create a new subscription definition.
1534    #[must_use]
1535    pub fn new(name: impl Into<String>, return_type: impl Into<String>) -> Self {
1536        Self {
1537            name:        name.into(),
1538            return_type: return_type.into(),
1539            arguments:   Vec::new(),
1540            description: None,
1541            topic:       None,
1542            filter:      None,
1543            fields:      Vec::new(),
1544            deprecation: None,
1545        }
1546    }
1547
1548    /// Set the event topic for this subscription.
1549    ///
1550    /// # Example
1551    ///
1552    /// ```
1553    /// use fraiseql_core::schema::SubscriptionDefinition;
1554    ///
1555    /// let subscription = SubscriptionDefinition::new("orderCreated", "Order")
1556    ///     .with_topic("order_created");
1557    /// assert_eq!(subscription.topic, Some("order_created".to_string()));
1558    /// ```
1559    #[must_use]
1560    pub fn with_topic(mut self, topic: impl Into<String>) -> Self {
1561        self.topic = Some(topic.into());
1562        self
1563    }
1564
1565    /// Set the description for this subscription.
1566    #[must_use]
1567    pub fn with_description(mut self, description: impl Into<String>) -> Self {
1568        self.description = Some(description.into());
1569        self
1570    }
1571
1572    /// Add an argument to this subscription.
1573    #[must_use]
1574    pub fn with_argument(mut self, arg: ArgumentDefinition) -> Self {
1575        self.arguments.push(arg);
1576        self
1577    }
1578
1579    /// Set the filter configuration for event matching.
1580    #[must_use]
1581    pub fn with_filter(mut self, filter: SubscriptionFilter) -> Self {
1582        self.filter = Some(filter);
1583        self
1584    }
1585
1586    /// Set the fields to project from event data.
1587    #[must_use]
1588    pub fn with_fields(mut self, fields: Vec<String>) -> Self {
1589        self.fields = fields;
1590        self
1591    }
1592
1593    /// Add a field to project from event data.
1594    #[must_use]
1595    pub fn with_field(mut self, field: impl Into<String>) -> Self {
1596        self.fields.push(field.into());
1597        self
1598    }
1599
1600    /// Mark this subscription as deprecated.
1601    ///
1602    /// # Example
1603    ///
1604    /// ```
1605    /// use fraiseql_core::schema::SubscriptionDefinition;
1606    ///
1607    /// let subscription = SubscriptionDefinition::new("oldUserEvents", "User")
1608    ///     .deprecated(Some("Use 'userEvents' instead".to_string()));
1609    /// assert!(subscription.is_deprecated());
1610    /// ```
1611    #[must_use]
1612    pub fn deprecated(mut self, reason: Option<String>) -> Self {
1613        self.deprecation = Some(super::field_type::DeprecationInfo { reason });
1614        self
1615    }
1616
1617    /// Check if this subscription is deprecated.
1618    #[must_use]
1619    pub fn is_deprecated(&self) -> bool {
1620        self.deprecation.is_some()
1621    }
1622
1623    /// Get the deprecation reason if deprecated.
1624    #[must_use]
1625    pub fn deprecation_reason(&self) -> Option<&str> {
1626        self.deprecation.as_ref().and_then(|d| d.reason.as_deref())
1627    }
1628}
1629
1630/// Query/mutation/subscription argument definition.
1631#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1632pub struct ArgumentDefinition {
1633    /// Argument name.
1634    pub name: String,
1635
1636    /// Argument type.
1637    pub arg_type: FieldType,
1638
1639    /// Is this argument optional?
1640    #[serde(default)]
1641    pub nullable: bool,
1642
1643    /// Default value (JSON representation).
1644    #[serde(default, skip_serializing_if = "Option::is_none")]
1645    pub default_value: Option<serde_json::Value>,
1646
1647    /// Description.
1648    #[serde(default, skip_serializing_if = "Option::is_none")]
1649    pub description: Option<String>,
1650
1651    /// Deprecation information (from @deprecated directive).
1652    /// When set, this argument is marked as deprecated in the schema.
1653    /// Per GraphQL spec, deprecated arguments should still be accepted but
1654    /// clients are encouraged to migrate to alternatives.
1655    #[serde(default, skip_serializing_if = "Option::is_none")]
1656    pub deprecation: Option<super::field_type::DeprecationInfo>,
1657}
1658
1659impl ArgumentDefinition {
1660    /// Create a new required argument.
1661    #[must_use]
1662    pub fn new(name: impl Into<String>, arg_type: FieldType) -> Self {
1663        Self {
1664            name: name.into(),
1665            arg_type,
1666            nullable: false,
1667            default_value: None,
1668            description: None,
1669            deprecation: None,
1670        }
1671    }
1672
1673    /// Create a new optional argument.
1674    #[must_use]
1675    pub fn optional(name: impl Into<String>, arg_type: FieldType) -> Self {
1676        Self {
1677            name: name.into(),
1678            arg_type,
1679            nullable: true,
1680            default_value: None,
1681            description: None,
1682            deprecation: None,
1683        }
1684    }
1685
1686    /// Mark this argument as deprecated.
1687    ///
1688    /// # Example
1689    ///
1690    /// ```
1691    /// use fraiseql_core::schema::{ArgumentDefinition, FieldType};
1692    ///
1693    /// let arg = ArgumentDefinition::optional("oldLimit", FieldType::Int)
1694    ///     .deprecated(Some("Use 'first' instead".to_string()));
1695    /// assert!(arg.is_deprecated());
1696    /// ```
1697    #[must_use]
1698    pub fn deprecated(mut self, reason: Option<String>) -> Self {
1699        self.deprecation = Some(super::field_type::DeprecationInfo { reason });
1700        self
1701    }
1702
1703    /// Check if this argument is deprecated.
1704    #[must_use]
1705    pub fn is_deprecated(&self) -> bool {
1706        self.deprecation.is_some()
1707    }
1708
1709    /// Get the deprecation reason if deprecated.
1710    #[must_use]
1711    pub fn deprecation_reason(&self) -> Option<&str> {
1712        self.deprecation.as_ref().and_then(|d| d.reason.as_deref())
1713    }
1714}
1715
1716/// Auto-wired query parameters.
1717///
1718/// These are standard parameters automatically added to list queries.
1719#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1720#[allow(clippy::struct_excessive_bools)] // These are intentional feature flags
1721pub struct AutoParams {
1722    /// Enable `where` filtering.
1723    #[serde(default)]
1724    pub has_where: bool,
1725
1726    /// Enable `orderBy` sorting.
1727    #[serde(default)]
1728    pub has_order_by: bool,
1729
1730    /// Enable `limit` pagination.
1731    #[serde(default)]
1732    pub has_limit: bool,
1733
1734    /// Enable `offset` pagination.
1735    #[serde(default)]
1736    pub has_offset: bool,
1737}
1738
1739impl AutoParams {
1740    /// Create with all auto-params enabled (common for list queries).
1741    #[must_use]
1742    pub fn all() -> Self {
1743        Self {
1744            has_where:    true,
1745            has_order_by: true,
1746            has_limit:    true,
1747            has_offset:   true,
1748        }
1749    }
1750
1751    /// Create with no auto-params (common for single-item queries).
1752    #[must_use]
1753    pub fn none() -> Self {
1754        Self::default()
1755    }
1756}
1757
1758// =============================================================================
1759// Custom Directive Definitions
1760// =============================================================================
1761
1762/// A custom directive definition for schema extension.
1763///
1764/// Allows defining custom directives beyond the built-in `@skip`, `@include`,
1765/// and `@deprecated` directives. Custom directives are exposed via introspection
1766/// and can be evaluated at runtime via registered handlers.
1767///
1768/// # Example
1769///
1770/// ```
1771/// use fraiseql_core::schema::{DirectiveDefinition, DirectiveLocationKind, ArgumentDefinition, FieldType};
1772///
1773/// let rate_limit = DirectiveDefinition {
1774///     name: "rateLimit".to_string(),
1775///     description: Some("Apply rate limiting to this field".to_string()),
1776///     locations: vec![DirectiveLocationKind::FieldDefinition],
1777///     arguments: vec![
1778///         ArgumentDefinition::new("limit", FieldType::Int),
1779///         ArgumentDefinition::optional("window", FieldType::String),
1780///     ],
1781///     is_repeatable: false,
1782/// };
1783/// ```
1784#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1785pub struct DirectiveDefinition {
1786    /// Directive name (e.g., "rateLimit", "auth").
1787    pub name: String,
1788
1789    /// Description of what this directive does.
1790    #[serde(default, skip_serializing_if = "Option::is_none")]
1791    pub description: Option<String>,
1792
1793    /// Valid locations where this directive can be applied.
1794    pub locations: Vec<DirectiveLocationKind>,
1795
1796    /// Arguments this directive accepts.
1797    #[serde(default)]
1798    pub arguments: Vec<ArgumentDefinition>,
1799
1800    /// Whether this directive can be applied multiple times to the same location.
1801    #[serde(default)]
1802    pub is_repeatable: bool,
1803}
1804
1805impl DirectiveDefinition {
1806    /// Create a new directive definition.
1807    #[must_use]
1808    pub fn new(name: impl Into<String>, locations: Vec<DirectiveLocationKind>) -> Self {
1809        Self {
1810            name: name.into(),
1811            description: None,
1812            locations,
1813            arguments: Vec::new(),
1814            is_repeatable: false,
1815        }
1816    }
1817
1818    /// Set the description.
1819    #[must_use]
1820    pub fn with_description(mut self, description: impl Into<String>) -> Self {
1821        self.description = Some(description.into());
1822        self
1823    }
1824
1825    /// Add an argument to this directive.
1826    #[must_use]
1827    pub fn with_argument(mut self, arg: ArgumentDefinition) -> Self {
1828        self.arguments.push(arg);
1829        self
1830    }
1831
1832    /// Add multiple arguments to this directive.
1833    #[must_use]
1834    pub fn with_arguments(mut self, args: Vec<ArgumentDefinition>) -> Self {
1835        self.arguments = args;
1836        self
1837    }
1838
1839    /// Mark this directive as repeatable.
1840    #[must_use]
1841    pub fn repeatable(mut self) -> Self {
1842        self.is_repeatable = true;
1843        self
1844    }
1845
1846    /// Check if this directive can be applied at the given location.
1847    #[must_use]
1848    pub fn valid_at(&self, location: DirectiveLocationKind) -> bool {
1849        self.locations.contains(&location)
1850    }
1851
1852    /// Find an argument by name.
1853    #[must_use]
1854    pub fn find_argument(&self, name: &str) -> Option<&ArgumentDefinition> {
1855        self.arguments.iter().find(|a| a.name == name)
1856    }
1857}
1858
1859/// Directive location kinds for custom directive definitions.
1860///
1861/// This mirrors `DirectiveLocation` in introspection but is used for
1862/// compiled schema definitions. The two types can be converted between
1863/// each other for introspection purposes.
1864///
1865/// Per GraphQL spec ยง3.13, directive locations fall into two categories:
1866/// - Executable locations (operations, fields, fragments)
1867/// - Type system locations (schema definitions)
1868#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1869#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
1870pub enum DirectiveLocationKind {
1871    // Executable directive locations
1872    /// Directive on query operation.
1873    Query,
1874    /// Directive on mutation operation.
1875    Mutation,
1876    /// Directive on subscription operation.
1877    Subscription,
1878    /// Directive on field selection.
1879    Field,
1880    /// Directive on fragment definition.
1881    FragmentDefinition,
1882    /// Directive on fragment spread.
1883    FragmentSpread,
1884    /// Directive on inline fragment.
1885    InlineFragment,
1886    /// Directive on variable definition.
1887    VariableDefinition,
1888
1889    // Type system directive locations
1890    /// Directive on schema definition.
1891    Schema,
1892    /// Directive on scalar type definition.
1893    Scalar,
1894    /// Directive on object type definition.
1895    Object,
1896    /// Directive on field definition.
1897    FieldDefinition,
1898    /// Directive on argument definition.
1899    ArgumentDefinition,
1900    /// Directive on interface definition.
1901    Interface,
1902    /// Directive on union definition.
1903    Union,
1904    /// Directive on enum definition.
1905    Enum,
1906    /// Directive on enum value definition.
1907    EnumValue,
1908    /// Directive on input object definition.
1909    InputObject,
1910    /// Directive on input field definition.
1911    InputFieldDefinition,
1912}
1913
1914impl DirectiveLocationKind {
1915    /// Check if this is an executable directive location.
1916    #[must_use]
1917    pub fn is_executable(&self) -> bool {
1918        matches!(
1919            self,
1920            Self::Query
1921                | Self::Mutation
1922                | Self::Subscription
1923                | Self::Field
1924                | Self::FragmentDefinition
1925                | Self::FragmentSpread
1926                | Self::InlineFragment
1927                | Self::VariableDefinition
1928        )
1929    }
1930
1931    /// Check if this is a type system directive location.
1932    #[must_use]
1933    pub fn is_type_system(&self) -> bool {
1934        !self.is_executable()
1935    }
1936}
1937
1938// =============================================================================
1939// Observer Definitions
1940// =============================================================================
1941
1942/// Observer definition - database change event listener.
1943///
1944/// Observers trigger actions (webhooks, notifications) when database
1945/// changes occur, enabling event-driven architectures.
1946///
1947/// # Example
1948///
1949/// ```
1950/// use fraiseql_core::schema::{ObserverDefinition, RetryConfig};
1951///
1952/// let observer = ObserverDefinition {
1953///     name: "onHighValueOrder".to_string(),
1954///     entity: "Order".to_string(),
1955///     event: "INSERT".to_string(),
1956///     condition: Some("total > 1000".to_string()),
1957///     actions: vec![
1958///         serde_json::json!({
1959///             "type": "webhook",
1960///             "url": "https://api.example.com/high-value-orders"
1961///         }),
1962///     ],
1963///     retry: RetryConfig {
1964///         max_attempts: 3,
1965///         backoff_strategy: "exponential".to_string(),
1966///         initial_delay_ms: 1000,
1967///         max_delay_ms: 60000,
1968///     },
1969/// };
1970/// ```
1971#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1972pub struct ObserverDefinition {
1973    /// Observer name (unique identifier).
1974    pub name: String,
1975
1976    /// Entity type to observe (e.g., "Order", "User").
1977    pub entity: String,
1978
1979    /// Event type: INSERT, UPDATE, or DELETE.
1980    pub event: String,
1981
1982    /// Optional condition expression in FraiseQL DSL.
1983    /// Example: "total > 1000" or "status.changed() and status == 'shipped'"
1984    #[serde(skip_serializing_if = "Option::is_none")]
1985    pub condition: Option<String>,
1986
1987    /// Actions to execute when observer triggers.
1988    /// Each action is a JSON object with a "type" field (webhook, slack, email).
1989    pub actions: Vec<serde_json::Value>,
1990
1991    /// Retry configuration for action execution.
1992    pub retry: RetryConfig,
1993}
1994
1995impl ObserverDefinition {
1996    /// Create a new observer definition.
1997    #[must_use]
1998    pub fn new(
1999        name: impl Into<String>,
2000        entity: impl Into<String>,
2001        event: impl Into<String>,
2002    ) -> Self {
2003        Self {
2004            name:      name.into(),
2005            entity:    entity.into(),
2006            event:     event.into(),
2007            condition: None,
2008            actions:   Vec::new(),
2009            retry:     RetryConfig::default(),
2010        }
2011    }
2012
2013    /// Set the condition expression.
2014    #[must_use]
2015    pub fn with_condition(mut self, condition: impl Into<String>) -> Self {
2016        self.condition = Some(condition.into());
2017        self
2018    }
2019
2020    /// Add an action to this observer.
2021    #[must_use]
2022    pub fn with_action(mut self, action: serde_json::Value) -> Self {
2023        self.actions.push(action);
2024        self
2025    }
2026
2027    /// Add multiple actions to this observer.
2028    #[must_use]
2029    pub fn with_actions(mut self, actions: Vec<serde_json::Value>) -> Self {
2030        self.actions = actions;
2031        self
2032    }
2033
2034    /// Set the retry configuration.
2035    #[must_use]
2036    pub fn with_retry(mut self, retry: RetryConfig) -> Self {
2037        self.retry = retry;
2038        self
2039    }
2040
2041    /// Check if this observer has a condition.
2042    #[must_use]
2043    pub fn has_condition(&self) -> bool {
2044        self.condition.is_some()
2045    }
2046
2047    /// Get the number of actions.
2048    #[must_use]
2049    pub fn action_count(&self) -> usize {
2050        self.actions.len()
2051    }
2052}
2053
2054/// Retry configuration for observer actions.
2055///
2056/// Controls how failed actions are retried with configurable
2057/// backoff strategies.
2058///
2059/// # Example
2060///
2061/// ```
2062/// use fraiseql_core::schema::RetryConfig;
2063///
2064/// let retry = RetryConfig {
2065///     max_attempts: 5,
2066///     backoff_strategy: "exponential".to_string(),
2067///     initial_delay_ms: 1000,
2068///     max_delay_ms: 60000,
2069/// };
2070/// ```
2071#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2072pub struct RetryConfig {
2073    /// Maximum number of retry attempts.
2074    pub max_attempts: u32,
2075
2076    /// Backoff strategy: exponential, linear, or fixed.
2077    pub backoff_strategy: String,
2078
2079    /// Initial delay in milliseconds.
2080    pub initial_delay_ms: u32,
2081
2082    /// Maximum delay in milliseconds (cap for exponential backoff).
2083    pub max_delay_ms: u32,
2084}
2085
2086impl Default for RetryConfig {
2087    fn default() -> Self {
2088        Self {
2089            max_attempts:     3,
2090            backoff_strategy: "exponential".to_string(),
2091            initial_delay_ms: 1000,
2092            max_delay_ms:     60000,
2093        }
2094    }
2095}
2096
2097impl RetryConfig {
2098    /// Create a new retry configuration.
2099    #[must_use]
2100    pub fn new(
2101        max_attempts: u32,
2102        backoff_strategy: impl Into<String>,
2103        initial_delay_ms: u32,
2104        max_delay_ms: u32,
2105    ) -> Self {
2106        Self {
2107            max_attempts,
2108            backoff_strategy: backoff_strategy.into(),
2109            initial_delay_ms,
2110            max_delay_ms,
2111        }
2112    }
2113
2114    /// Create exponential backoff configuration.
2115    #[must_use]
2116    pub fn exponential(max_attempts: u32, initial_delay_ms: u32, max_delay_ms: u32) -> Self {
2117        Self::new(max_attempts, "exponential", initial_delay_ms, max_delay_ms)
2118    }
2119
2120    /// Create linear backoff configuration.
2121    #[must_use]
2122    pub fn linear(max_attempts: u32, initial_delay_ms: u32, max_delay_ms: u32) -> Self {
2123        Self::new(max_attempts, "linear", initial_delay_ms, max_delay_ms)
2124    }
2125
2126    /// Create fixed delay configuration.
2127    #[must_use]
2128    pub fn fixed(max_attempts: u32, delay_ms: u32) -> Self {
2129        Self::new(max_attempts, "fixed", delay_ms, delay_ms)
2130    }
2131
2132    /// Check if backoff strategy is exponential.
2133    #[must_use]
2134    pub fn is_exponential(&self) -> bool {
2135        self.backoff_strategy == "exponential"
2136    }
2137
2138    /// Check if backoff strategy is linear.
2139    #[must_use]
2140    pub fn is_linear(&self) -> bool {
2141        self.backoff_strategy == "linear"
2142    }
2143
2144    /// Check if backoff strategy is fixed.
2145    #[must_use]
2146    pub fn is_fixed(&self) -> bool {
2147        self.backoff_strategy == "fixed"
2148    }
2149}
2150
2151// =============================================================================
2152// Tests
2153// =============================================================================
2154
2155#[cfg(test)]
2156mod tests {
2157    use super::*;
2158
2159    #[test]
2160    fn test_compiled_schema_with_observers() {
2161        let json = r#"{
2162            "types": [],
2163            "enums": [],
2164            "input_types": [],
2165            "interfaces": [],
2166            "unions": [],
2167            "queries": [],
2168            "mutations": [],
2169            "subscriptions": [],
2170            "observers": [
2171                {
2172                    "name": "onHighValueOrder",
2173                    "entity": "Order",
2174                    "event": "INSERT",
2175                    "condition": "total > 1000",
2176                    "actions": [
2177                        {
2178                            "type": "webhook",
2179                            "url": "https://api.example.com/webhook"
2180                        }
2181                    ],
2182                    "retry": {
2183                        "max_attempts": 3,
2184                        "backoff_strategy": "exponential",
2185                        "initial_delay_ms": 1000,
2186                        "max_delay_ms": 60000
2187                    }
2188                }
2189            ]
2190        }"#;
2191
2192        let schema = CompiledSchema::from_json(json).unwrap();
2193
2194        assert!(schema.has_observers());
2195        assert_eq!(schema.observer_count(), 1);
2196
2197        let observer = schema.find_observer("onHighValueOrder").unwrap();
2198        assert_eq!(observer.entity, "Order");
2199        assert_eq!(observer.event, "INSERT");
2200        assert_eq!(observer.condition, Some("total > 1000".to_string()));
2201        assert_eq!(observer.actions.len(), 1);
2202        assert_eq!(observer.retry.max_attempts, 3);
2203        assert!(observer.retry.is_exponential());
2204    }
2205
2206    #[test]
2207    fn test_compiled_schema_backward_compatible() {
2208        // Schema without observers field should still load
2209        let json = r#"{
2210            "types": [],
2211            "enums": [],
2212            "input_types": [],
2213            "interfaces": [],
2214            "unions": [],
2215            "queries": [],
2216            "mutations": [],
2217            "subscriptions": []
2218        }"#;
2219
2220        let schema = CompiledSchema::from_json(json).unwrap();
2221        assert!(!schema.has_observers());
2222        assert_eq!(schema.observer_count(), 0);
2223    }
2224
2225    #[test]
2226    fn test_find_observers_for_entity() {
2227        let schema = CompiledSchema {
2228            observers: vec![
2229                ObserverDefinition::new("onOrderInsert", "Order", "INSERT"),
2230                ObserverDefinition::new("onOrderUpdate", "Order", "UPDATE"),
2231                ObserverDefinition::new("onUserInsert", "User", "INSERT"),
2232            ],
2233            ..Default::default()
2234        };
2235
2236        let order_observers = schema.find_observers_for_entity("Order");
2237        assert_eq!(order_observers.len(), 2);
2238
2239        let user_observers = schema.find_observers_for_entity("User");
2240        assert_eq!(user_observers.len(), 1);
2241    }
2242
2243    #[test]
2244    fn test_find_observers_for_event() {
2245        let schema = CompiledSchema {
2246            observers: vec![
2247                ObserverDefinition::new("onOrderInsert", "Order", "INSERT"),
2248                ObserverDefinition::new("onOrderUpdate", "Order", "UPDATE"),
2249                ObserverDefinition::new("onUserInsert", "User", "INSERT"),
2250            ],
2251            ..Default::default()
2252        };
2253
2254        let insert_observers = schema.find_observers_for_event("INSERT");
2255        assert_eq!(insert_observers.len(), 2);
2256
2257        let update_observers = schema.find_observers_for_event("UPDATE");
2258        assert_eq!(update_observers.len(), 1);
2259    }
2260
2261    #[test]
2262    fn test_observer_definition_builder() {
2263        let observer = ObserverDefinition::new("test", "Order", "INSERT")
2264            .with_condition("total > 1000")
2265            .with_action(serde_json::json!({"type": "webhook", "url": "https://example.com"}))
2266            .with_retry(RetryConfig::exponential(5, 1000, 60000));
2267
2268        assert_eq!(observer.name, "test");
2269        assert_eq!(observer.entity, "Order");
2270        assert_eq!(observer.event, "INSERT");
2271        assert!(observer.has_condition());
2272        assert_eq!(observer.action_count(), 1);
2273        assert_eq!(observer.retry.max_attempts, 5);
2274    }
2275
2276    #[test]
2277    fn test_retry_config_types() {
2278        let exponential = RetryConfig::exponential(3, 1000, 60000);
2279        assert!(exponential.is_exponential());
2280        assert!(!exponential.is_linear());
2281        assert!(!exponential.is_fixed());
2282
2283        let linear = RetryConfig::linear(3, 1000, 60000);
2284        assert!(!linear.is_exponential());
2285        assert!(linear.is_linear());
2286        assert!(!linear.is_fixed());
2287
2288        let fixed = RetryConfig::fixed(3, 5000);
2289        assert!(!fixed.is_exponential());
2290        assert!(!fixed.is_linear());
2291        assert!(fixed.is_fixed());
2292        assert_eq!(fixed.initial_delay_ms, 5000);
2293        assert_eq!(fixed.max_delay_ms, 5000);
2294    }
2295}