Skip to main content

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}