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 /// Named hierarchy reference for ID-based ltree operators.
90 /// References a key in the `hierarchies` config map.
91 #[serde(default, skip_serializing_if = "Option::is_none")]
92 pub hierarchy: Option<String>,
93}
94
95// =============================================================================
96// Enum Definitions
97// =============================================================================
98
99/// GraphQL enum type definition in intermediate format.
100///
101/// Enums represent a finite set of possible values.
102///
103/// # Example JSON
104///
105/// ```json
106/// {
107/// "name": "OrderStatus",
108/// "values": [
109/// {"name": "PENDING"},
110/// {"name": "PROCESSING"},
111/// {"name": "SHIPPED", "description": "Package has been shipped"},
112/// {"name": "DELIVERED"}
113/// ],
114/// "description": "Possible states of an order"
115/// }
116/// ```
117#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
118pub struct IntermediateEnum {
119 /// Enum type name (e.g., "OrderStatus")
120 pub name: String,
121
122 /// Possible values for this enum
123 pub values: Vec<IntermediateEnumValue>,
124
125 /// Enum description (from docstring)
126 #[serde(skip_serializing_if = "Option::is_none")]
127 pub description: Option<String>,
128}
129
130/// A single value within an enum type.
131///
132/// # Example JSON
133///
134/// ```json
135/// {
136/// "name": "ACTIVE",
137/// "description": "The item is currently active",
138/// "deprecated": {"reason": "Use ENABLED instead"}
139/// }
140/// ```
141#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
142pub struct IntermediateEnumValue {
143 /// Value name (e.g., "PENDING")
144 pub name: String,
145
146 /// Value description (from docstring)
147 #[serde(skip_serializing_if = "Option::is_none")]
148 pub description: Option<String>,
149
150 /// Deprecation info (if value is deprecated)
151 #[serde(skip_serializing_if = "Option::is_none")]
152 pub deprecated: Option<IntermediateDeprecation>,
153}
154
155/// Deprecation information for enum values or input fields.
156#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
157pub struct IntermediateDeprecation {
158 /// Deprecation reason (what to use instead)
159 #[serde(skip_serializing_if = "Option::is_none")]
160 pub reason: Option<String>,
161}
162
163// =============================================================================
164// Custom Scalar Definitions
165// =============================================================================
166
167/// Custom scalar type definition in intermediate format.
168///
169/// Custom scalars allow applications to define domain-specific types with validation.
170/// Scalars are defined in language SDKs (Python, TypeScript, Java, Go, Rust)
171/// and compiled into the schema.
172///
173/// # Example JSON
174///
175/// ```json
176/// {
177/// "name": "Email",
178/// "description": "Valid email address",
179/// "specified_by_url": "https://tools.ietf.org/html/rfc5322",
180/// "base_type": "String",
181/// "validation_rules": [
182/// {
183/// "type": "pattern",
184/// "value": {
185/// "pattern": "^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}$"
186/// }
187/// }
188/// ]
189/// }
190/// ```
191#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
192pub struct IntermediateScalar {
193 /// Scalar name (e.g., "Email", "Phone", "ISBN")
194 pub name: String,
195
196 /// Scalar description
197 #[serde(skip_serializing_if = "Option::is_none")]
198 pub description: Option<String>,
199
200 /// URL to specification/RFC (GraphQL spec §3.5.1)
201 #[serde(skip_serializing_if = "Option::is_none")]
202 pub specified_by_url: Option<String>,
203
204 /// Built-in validation rules
205 #[serde(default)]
206 pub validation_rules: Vec<ValidationRule>,
207
208 /// Base type for type aliases (e.g., "String" for Email scalar)
209 #[serde(skip_serializing_if = "Option::is_none")]
210 pub base_type: Option<String>,
211}