Skip to main content

fraiseql_cli/schema/intermediate/
types.rs

1//! Core type structs: `IntermediateType`, `IntermediateField`, `IntermediateEnum`,
2//! `IntermediateEnumValue`, `IntermediateScalar`, `IntermediateDeprecation`.
3
4use fraiseql_core::validation::ValidationRule;
5use serde::{Deserialize, Serialize};
6
7use super::fragments::IntermediateAppliedDirective;
8
9/// Type definition in intermediate format
10#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
11pub struct IntermediateType {
12    /// Type name (e.g., "User")
13    pub name: String,
14
15    /// Type fields
16    pub fields: Vec<IntermediateField>,
17
18    /// Type description (from docstring)
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub description: Option<String>,
21
22    /// Interfaces this type implements (GraphQL spec §3.6)
23    #[serde(default, skip_serializing_if = "Vec::is_empty")]
24    pub implements: Vec<String>,
25
26    /// Role required to see this type in introspection and access queries returning it.
27    #[serde(default, skip_serializing_if = "Option::is_none")]
28    pub requires_role: Option<String>,
29
30    /// Whether this type is a mutation error type (tagged with `@fraiseql.error`).
31    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
32    pub is_error: bool,
33
34    /// Whether this type implements the Relay Node interface.
35    /// When true, the compiler generates global node IDs (`base64("TypeName:uuid")`)
36    /// and validates that `pk_{entity}` (BIGINT) is present in the view's data JSONB.
37    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
38    pub relay: bool,
39}
40
41/// Field definition in intermediate format
42///
43/// **NOTE**: Uses `type` field (not `field_type`)
44/// This is the language-agnostic format. Rust conversion happens in converter.
45#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
46pub struct IntermediateField {
47    /// Field name (e.g., "id")
48    pub name: String,
49
50    /// Field type name (e.g., "Int", "String", "User")
51    ///
52    /// **Language-agnostic**: All languages use "type", not "`field_type`"
53    #[serde(rename = "type")]
54    pub field_type: String,
55
56    /// Is field nullable?
57    pub nullable: bool,
58
59    /// Field description (from docstring)
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub description: Option<String>,
62
63    /// Applied directives (e.g., @deprecated)
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub directives: Option<Vec<IntermediateAppliedDirective>>,
66
67    /// Scope required to access this field (field-level access control)
68    ///
69    /// When set, users must have this scope in their JWT to query this field.
70    /// Supports patterns like "read:Type.field" or custom scopes like "hr:view_pii".
71    ///
72    /// # Example
73    ///
74    /// ```json
75    /// {
76    ///   "name": "salary",
77    ///   "type": "Int",
78    ///   "nullable": false,
79    ///   "requires_scope": "read:Employee.salary"
80    /// }
81    /// ```
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub requires_scope: Option<String>,
84
85    /// Policy when the user lacks `requires_scope`: `"reject"` (default) or `"mask"`.
86    #[serde(default, skip_serializing_if = "Option::is_none")]
87    pub on_deny: Option<String>,
88
89    /// Whether this field is gated by the dynamic field authorizer at runtime.
90    ///
91    /// When `true`, the compiled field is marked policy-gated
92    /// (`FieldDefinition.authorize`) and a configured `FieldAuthorizer` is consulted
93    /// per row. Defaults to `false` when absent.
94    ///
95    /// # Example
96    ///
97    /// ```json
98    /// {
99    ///   "name": "email",
100    ///   "type": "String",
101    ///   "nullable": true,
102    ///   "authorize": true
103    /// }
104    /// ```
105    #[serde(default, skip_serializing_if = "Option::is_none")]
106    pub authorize: Option<bool>,
107
108    /// Named hierarchy reference for ID-based ltree operators.
109    /// References a key in the `hierarchies` config map.
110    #[serde(default, skip_serializing_if = "Option::is_none")]
111    pub hierarchy: Option<String>,
112}
113
114// =============================================================================
115// Enum Definitions
116// =============================================================================
117
118/// GraphQL enum type definition in intermediate format.
119///
120/// Enums represent a finite set of possible values.
121///
122/// # Example JSON
123///
124/// ```json
125/// {
126///   "name": "OrderStatus",
127///   "values": [
128///     {"name": "PENDING"},
129///     {"name": "PROCESSING"},
130///     {"name": "SHIPPED", "description": "Package has been shipped"},
131///     {"name": "DELIVERED"}
132///   ],
133///   "description": "Possible states of an order"
134/// }
135/// ```
136#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
137pub struct IntermediateEnum {
138    /// Enum type name (e.g., "OrderStatus")
139    pub name: String,
140
141    /// Possible values for this enum
142    pub values: Vec<IntermediateEnumValue>,
143
144    /// Enum description (from docstring)
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub description: Option<String>,
147}
148
149/// A single value within an enum type.
150///
151/// # Example JSON
152///
153/// ```json
154/// {
155///   "name": "ACTIVE",
156///   "description": "The item is currently active",
157///   "deprecated": {"reason": "Use ENABLED instead"}
158/// }
159/// ```
160#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
161pub struct IntermediateEnumValue {
162    /// Value name (e.g., "PENDING")
163    pub name: String,
164
165    /// Value description (from docstring)
166    #[serde(skip_serializing_if = "Option::is_none")]
167    pub description: Option<String>,
168
169    /// Deprecation info (if value is deprecated)
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub deprecated: Option<IntermediateDeprecation>,
172}
173
174/// Deprecation information for enum values or input fields.
175#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
176pub struct IntermediateDeprecation {
177    /// Deprecation reason (what to use instead)
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub reason: Option<String>,
180}
181
182// =============================================================================
183// Custom Scalar Definitions
184// =============================================================================
185
186/// Custom scalar type definition in intermediate format.
187///
188/// Custom scalars allow applications to define domain-specific types with validation.
189/// Scalars are defined in language SDKs (Python, TypeScript, Java, Go, Rust)
190/// and compiled into the schema.
191///
192/// # Example JSON
193///
194/// ```json
195/// {
196///   "name": "Email",
197///   "description": "Valid email address",
198///   "specified_by_url": "https://tools.ietf.org/html/rfc5322",
199///   "base_type": "String",
200///   "validation_rules": [
201///     {
202///       "type": "pattern",
203///       "value": {
204///         "pattern": "^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}$"
205///       }
206///     }
207///   ]
208/// }
209/// ```
210#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
211pub struct IntermediateScalar {
212    /// Scalar name (e.g., "Email", "Phone", "ISBN")
213    pub name: String,
214
215    /// Scalar description
216    #[serde(skip_serializing_if = "Option::is_none")]
217    pub description: Option<String>,
218
219    /// URL to specification/RFC (GraphQL spec §3.5.1)
220    #[serde(skip_serializing_if = "Option::is_none")]
221    pub specified_by_url: Option<String>,
222
223    /// Built-in validation rules
224    #[serde(default)]
225    pub validation_rules: Vec<ValidationRule>,
226
227    /// Base type for type aliases (e.g., "String" for Email scalar)
228    #[serde(skip_serializing_if = "Option::is_none")]
229    pub base_type: Option<String>,
230}