spikard_cli/codegen/graphql/mod.rs
1//! GraphQL Schema Definition Language (SDL) and introspection parsing
2//!
3//! This module provides parsing and type extraction for GraphQL schemas in both
4//! SDL (Schema Definition Language) and JSON introspection formats.
5
6pub mod generators;
7pub mod sdl;
8pub mod spec_parser;
9
10// Re-export parser types and functions for public use
11pub use spec_parser::{
12 GraphQLArgument, GraphQLDirective, GraphQLEnumValue, GraphQLField, GraphQLInputField, GraphQLSchema, GraphQLType,
13 TypeKind, parse_graphql_schema, parse_graphql_sdl, parse_graphql_sdl_string,
14};
15
16// Re-export generators trait and implementations
17pub use generators::{GraphQLGenerator, RustGenerator};
18
19// Re-export SDL utilities
20
21use anyhow::Result;
22
23/// Generate Python GraphQL code from a schema
24///
25/// Parses the GraphQL schema string and generates complete Python code with type
26/// definitions, resolver implementations, and schema configuration based on the
27/// target specification. Generated code follows Python 3.10+ conventions using
28/// msgspec for type-safe data serialization and Ariadne for GraphQL schema binding.
29///
30/// # Generated Code Features
31///
32/// * **Type Definitions**: Python dataclasses using `msgspec.Struct` with
33/// `frozen=True` and `kw_only=True` for immutability and explicitness
34/// * **Enums**: Python enums inheriting from `str` for GraphQL enum types
35/// * **Input Objects**: msgspec Structs for GraphQL input types
36/// * **Union Types**: Python type aliases (e.g., `User | Post | Any`)
37/// * **Type Hints**: Full type annotations using Python 3.10+ syntax (e.g., `str | None`
38/// instead of `Optional[str]`, `list[Item]` instead of `List[Item]`)
39/// * **Resolvers**: Async resolver functions with proper type hints and parameter handling
40/// * **Schema Definition**: Ariadne-compatible schema with embedded SDL and resolver setup
41/// * **Docstrings**: NumPy-style docstrings extracted from GraphQL descriptions
42///
43/// # Arguments
44///
45/// * `schema` - GraphQL schema as a string (SDL format)
46/// * `target` - Generation target specifying what to generate:
47/// * `"all"` - Complete code: types, resolvers, and schema definition
48/// * `"types"` - Type definitions and enums only
49/// * `"resolvers"` - Async resolver function stubs with type hints
50/// * `"schema"` - Ariadne schema definition with embedded SDL
51/// * Any other value defaults to "all"
52///
53/// # Returns
54///
55/// Generated Python code as a `String`, or an `anyhow::Error` if parsing or
56/// generation fails (e.g., invalid GraphQL SDL syntax).
57///
58/// # Type Mapping
59///
60/// GraphQL types are mapped to Python as follows:
61/// * `String` → `str`
62/// * `Int` → `int`
63/// * `Float` → `float`
64/// * `Boolean` → `bool`
65/// * `ID` → `str`
66/// * Custom scalars → `str` (unless defined in schema)
67/// * Nullable types → `T | None` (e.g., `str | None`)
68/// * List types → `list[T]` (e.g., `list[str]` or `list[str | None]`)
69/// * Union types → `Type1 | Type2 | ... | Any`
70///
71/// # Examples
72///
73/// Generate all Python code for a simple query:
74/// ```ignore
75/// let schema = r#"
76/// type Query {
77/// hello: String!
78/// user(id: ID!): User
79/// }
80///
81/// type User {
82/// id: ID!
83/// name: String!
84/// email: String
85/// }
86/// "#;
87///
88/// let code = generate_python_graphql(schema, "all")?;
89/// println!("{}", code);
90/// ```
91///
92/// Generate only type definitions:
93/// ```ignore
94/// let code = generate_python_graphql(schema, "types")?;
95/// // Contains: msgspec Structs, Enums, type unions
96/// ```
97///
98/// Generate resolver function stubs:
99/// ```ignore
100/// let code = generate_python_graphql(schema, "resolvers")?;
101/// // Contains: async def resolve_* functions with type hints
102/// ```
103pub fn generate_python_graphql(schema: &str, target: &str) -> Result<String> {
104 use generators::GraphQLGenerator;
105 use generators::python::PythonGenerator;
106
107 // Parse the GraphQL schema string
108 let parsed_schema = parse_graphql_sdl_string(schema)?;
109
110 // Create the Python generator
111 let generator = PythonGenerator;
112
113 // Generate code based on target
114 match target {
115 "all" => generator.generate_complete(&parsed_schema),
116 "types" => generator.generate_types(&parsed_schema),
117 "resolvers" => generator.generate_resolvers(&parsed_schema),
118 "schema" => generator.generate_schema_definition(&parsed_schema),
119 _ => {
120 // Default to complete generation for unknown targets
121 generator.generate_complete(&parsed_schema)
122 }
123 }
124}
125
126/// Generate TypeScript GraphQL code from a schema
127///
128/// Parses the GraphQL schema string and generates complete TypeScript code
129/// based on the target specification.
130///
131/// # Arguments
132///
133/// * `schema` - GraphQL schema as a string (SDL format)
134/// * `target` - Generation target: "all" (complete), "types" (types only),
135/// "resolvers" (resolvers), or "schema" (schema definition)
136///
137/// # Returns
138///
139/// Generated TypeScript code as a string, or an error if parsing/generation fails
140pub fn generate_typescript_graphql(schema: &str, target: &str) -> Result<String> {
141 use generators::GraphQLGenerator;
142 use generators::typescript::TypeScriptGenerator;
143
144 // Parse the GraphQL schema string
145 let parsed_schema = parse_graphql_sdl_string(schema)?;
146
147 // Create the TypeScript generator
148 let generator = TypeScriptGenerator;
149
150 // Generate code based on target
151 match target {
152 "all" => generator.generate_complete(&parsed_schema),
153 "types" => generator.generate_types(&parsed_schema),
154 "resolvers" => generator.generate_resolvers(&parsed_schema),
155 "schema" => generator.generate_schema_definition(&parsed_schema),
156 _ => {
157 // Default to complete generation for unknown targets
158 generator.generate_complete(&parsed_schema)
159 }
160 }
161}
162
163/// Generate Rust GraphQL code from a schema
164///
165/// Parses the GraphQL schema string and generates complete Rust code with async-graphql
166/// types, resolvers, and schema definition based on the target specification.
167///
168/// # Arguments
169///
170/// * `schema` - GraphQL schema as a string (SDL format)
171/// * `target` - Generation target: "all" (complete), "types" (types only),
172/// "resolvers" (Query/Mutation/Subscription), or "schema" (schema builder)
173///
174/// # Returns
175///
176/// Generated Rust code as a string, or an error if parsing/generation fails
177pub fn generate_rust_graphql(schema: &str, target: &str) -> Result<String> {
178 // Parse the GraphQL schema string
179 let parsed_schema = parse_graphql_sdl_string(schema)?;
180
181 // Create the Rust generator
182 let generator = RustGenerator::new();
183
184 // Generate code based on target
185 match target {
186 "all" => generator.generate_complete(&parsed_schema),
187 "types" => generator.generate_types(&parsed_schema),
188 "resolvers" => generator.generate_resolvers(&parsed_schema),
189 "schema" => generator.generate_schema_definition(&parsed_schema),
190 _ => {
191 // Default to complete generation for unknown targets
192 generator.generate_complete(&parsed_schema)
193 }
194 }
195}
196
197/// Generate Ruby GraphQL code from a schema
198///
199/// Parses the GraphQL schema string and generates idiomatic Ruby code with graphql-ruby
200/// class definitions for types, resolvers, and schema configuration based on the
201/// target specification. Supports RBS (Ruby Signature) type definition generation for
202/// integration with Steep static type checker.
203///
204/// # Arguments
205///
206/// * `schema` - GraphQL schema as a string (SDL format)
207/// * `target` - Generation target: "all" (complete), "types" (types only),
208/// "resolvers" (resolver classes), "schema" (schema definition),
209/// or "rbs" (RBS type signatures for Steep)
210///
211/// # Returns
212///
213/// Generated Ruby code or RBS signatures as a string, or an error if parsing/generation fails
214///
215/// # Examples
216///
217/// Generate all Ruby code:
218/// ```ignore
219/// let schema = r#"
220/// type Query {
221/// hello: String!
222/// }
223/// "#;
224/// let code = generate_ruby_graphql(schema, "all")?;
225/// println!("{}", code);
226/// ```
227///
228/// Generate RBS type signatures:
229/// ```ignore
230/// let rbs = generate_ruby_graphql(schema, "rbs")?;
231/// // Output is RBS syntax compatible with Steep type checker
232/// ```
233pub fn generate_ruby_graphql(schema: &str, target: &str) -> Result<String> {
234 use generators::GraphQLGenerator;
235 use generators::ruby::RubyGenerator;
236
237 let parsed_schema = parse_graphql_sdl_string(schema)?;
238 let generator = RubyGenerator;
239
240 match target {
241 "types" => generator.generate_types(&parsed_schema),
242 "resolvers" => generator.generate_resolvers(&parsed_schema),
243 "schema" => generator.generate_schema_definition(&parsed_schema),
244 "rbs" => generator.generate_type_signatures(&parsed_schema),
245 "all" => generator.generate_complete(&parsed_schema),
246 _ => generator.generate_complete(&parsed_schema),
247 }
248}
249
250/// Generate PHP GraphQL code from a schema
251///
252/// Parses the GraphQL schema string and generates complete PHP code with type definitions,
253/// resolver implementations, and schema configuration based on the target specification.
254/// Generated code uses PSR-4 namespacing, `strict_types` declarations, typed properties,
255/// and webonyx/graphql-php library for schema binding.
256///
257/// # Arguments
258///
259/// * `schema` - GraphQL schema as a string (SDL format)
260/// * `target` - Generation target: "all" (complete), "types" (types only),
261/// "resolvers" (resolver classes), or "schema" (schema definition)
262///
263/// # Returns
264///
265/// Generated PHP code as a string with:
266/// - `<?php` opening tag and `declare(strict_types=1);`
267/// - PSR-4 namespace declaration under GraphQL namespace
268/// - Class definitions for object types, input types, and enums (PHP 8.1+)
269/// - Typed properties with appropriate nullability markers
270/// - Resolver method signatures with return type declarations
271/// - Or an error if parsing/generation fails
272///
273/// # Examples
274///
275/// ```ignore
276/// let schema = r#"
277/// type Query {
278/// user(id: ID!): User
279/// }
280/// type User {
281/// id: ID!
282/// name: String!
283/// }
284/// "#;
285/// let code = generate_php_graphql(schema, "all")?;
286/// println!("{}", code);
287/// ```
288pub fn generate_php_graphql(schema: &str, target: &str) -> Result<String> {
289 use generators::GraphQLGenerator;
290 use generators::php::PhpGenerator;
291
292 // Parse the GraphQL schema string
293 let parsed_schema = parse_graphql_sdl_string(schema)?;
294
295 // Create the PHP generator
296 let generator = PhpGenerator;
297
298 // Generate code based on target
299 match target {
300 "all" => generator.generate_complete(&parsed_schema),
301 "types" => generator.generate_types(&parsed_schema),
302 "resolvers" => generator.generate_resolvers(&parsed_schema),
303 "schema" => generator.generate_schema_definition(&parsed_schema),
304 _ => {
305 // Default to complete generation for unknown targets
306 generator.generate_complete(&parsed_schema)
307 }
308 }
309}
310
311/// Generate Elixir GraphQL code from a schema.
312///
313/// Parses GraphQL SDL and emits `Spikard.Router`-based scaffolding with typed
314/// schema modules, resolver stubs, and an embedded SDL definition.
315pub fn generate_elixir_graphql(schema: &str, target: &str) -> Result<String> {
316 use generators::GraphQLGenerator;
317 use generators::elixir::ElixirGenerator;
318
319 let parsed_schema = parse_graphql_sdl_string(schema)?;
320 let generator = ElixirGenerator;
321
322 match target {
323 "all" => generator.generate_complete(&parsed_schema),
324 "types" => generator.generate_types(&parsed_schema),
325 "resolvers" => generator.generate_resolvers(&parsed_schema),
326 "schema" => generator.generate_schema_definition(&parsed_schema),
327 _ => generator.generate_complete(&parsed_schema),
328 }
329}